You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
tail() and reverse() copy a lazy range() into an ArrayList, so a big range exhausts the heap again #6353
Found while fixing #6323, which removed the same failure from the slice operator: range(0, 999999999)[0..1000000000] copied the slice into an ArrayList and exhausted the heap, and LongRangeList.subList already answered it in constant space. The slice was not the only copier.
tail() and reverse() copy the whole range
Both materialise, so with arcadedb.queryMaxRangeSize disabled (or set high) a single authenticated request allocates a billion boxed longs - tens of GB - which is advisory GHSA-xmjm-8q85-g778 in a second dress:
TailFunction.execute (engine/src/main/java/com/arcadedb/function/coll/TailFunction.java): new ArrayList<>(list.subList(1, list.size())). The copy is what costs; list.subList(1, size) on a LongRangeList is already a lazy LongRangeList, so the new ArrayList<>(...) around it is the whole bug.
ReverseFunction.execute (engine/src/main/java/com/arcadedb/function/misc/ReverseFunction.java): new ArrayList<>(list) followed by Collections.reverse.
Both have exact lazy answers, because a range reversed or beheaded is still an arithmetic progression:
tail(range) is range.subList(1, size) - already constant space, just do not copy it.
reverse(range) is new LongRangeList(start + (size - 1) * step, -step, size).
head(), last(), size() and indexing are already fine - they index rather than copy - and IN and slicing were fixed in #6344.
The coll.* family copies too
CollSort, CollRemove, CollFlatten and CollInsert each build an ArrayList of the whole input. Most of them genuinely need one - you cannot insert into a progression and get a progression - so the ask there is different: either bound the copy by arcadedb.queryMaxRangeSize and fail as a client error the way range() itself does, or answer lazily where the answer is still a range. sort(range) is one of those: an ascending range sorted is itself, and a descending one is its reverse.
What to check while in there
Whether any other consumer of a List argument copies it: the test to add is the shape CypherRangeHeapExhaustionTest already uses - call the function on range(0, 999999999) with the limit disabled, inside StallAwareStopwatch.assertStayedUnder, and let a copy fail as a timeout rather than as an OutOfMemoryError that takes the fork with it.
Activity
fix(#6353, #6354): a range beheaded or reversed is still a range, and…