#1674 added js_poll_interrupts() to the element loops of indexOf, lastIndexOf, includes, every/some/forEach/map/filter, reduce/reduceRight and the find family. Several other Array builtins walk [0, length) in C the same way, without calling into JS, and still never poll. Neither Ctrl-C in the REPL nor an embedder's interrupt handler can stop them.
On a sparse array, or an array-like with a large length and no elements, most of these loops allocate nothing per iteration, so a memory limit does not stop them either. At a length of 232-1 they run for minutes (join took four to five minutes here), and the ones that also accept array-likes with lengths up to 253-1 effectively never return.
To reproduce, start the v0.17.0 qjs REPL, enter a line and press Ctrl-C after a second:
while (true) {} // InternalError: interrupted
Array.prototype.join.call({length: 2**32-1}, "") // Ctrl-C has no effect
Array.prototype.slice.call({length: 2**32-1}) // Ctrl-C has no effect
new Array(2**32-1).reverse() // Ctrl-C has no effect
The last three keep running after Ctrl-C, so each one needs a fresh qjs. The same can be seen with run-test262 and the qjs:set-interrupt-handler flag from #1674.
These are the loops without a poll, with line numbers at v0.17.0. Master has the same line numbers, since the only change to quickjs.c after the tag is in js_string_repeat.
| builtins |
loop |
allocates per element |
join, toLocaleString |
js_array_join, quickjs.c:44093 |
no with an empty separator, otherwise one character |
reverse |
js_array_reverse, 44271 (generic path) |
no |
copyWithin, shift, unshift, splice |
JS_CopySubArray, 43023 |
no |
slice, splice |
js_array_slice, 44425 (generic copy) and 44445 (splice deleting the old tail) |
no |
concat |
js_array_concat, 43446 (spreadable argument) |
no |
sort |
js_array_sort, 44777 (gather) and 44824 (deleting trailing holes) |
no |
flat, flatMap |
JS_FlattenIntoArray, 44594 |
no |
fill |
js_array_fill, 43799 |
yes |
Array.from (array-like source) |
js_array_from, 43181 |
yes |
fill and Array.from define a property per index, so a memory limit stops them eventually, but until then they ignore the handler too. pop and push do not loop over the length. with, toReversed, toSorted and toSpliced allocate a dense result of the full length up front (above 2**31-1 that is a RangeError), so their loops are bounded by what can actually be allocated.
The fix is a poll at the top of each of these loops, as in #1674. I have a patch with tests and can open a PR.
#1674 added
js_poll_interrupts()to the element loops ofindexOf,lastIndexOf,includes,every/some/forEach/map/filter,reduce/reduceRightand thefindfamily. Several other Array builtins walk[0, length)in C the same way, without calling into JS, and still never poll. Neither Ctrl-C in the REPL nor an embedder's interrupt handler can stop them.On a sparse array, or an array-like with a large
lengthand no elements, most of these loops allocate nothing per iteration, so a memory limit does not stop them either. At a length of 232-1 they run for minutes (jointook four to five minutes here), and the ones that also accept array-likes with lengths up to 253-1 effectively never return.To reproduce, start the v0.17.0
qjsREPL, enter a line and press Ctrl-C after a second:The last three keep running after Ctrl-C, so each one needs a fresh
qjs. The same can be seen withrun-test262and theqjs:set-interrupt-handlerflag from #1674.These are the loops without a poll, with line numbers at v0.17.0. Master has the same line numbers, since the only change to
quickjs.cafter the tag is injs_string_repeat.join,toLocaleStringjs_array_join, quickjs.c:44093reversejs_array_reverse, 44271 (generic path)copyWithin,shift,unshift,spliceJS_CopySubArray, 43023slice,splicejs_array_slice, 44425 (generic copy) and 44445 (splice deleting the old tail)concatjs_array_concat, 43446 (spreadable argument)sortjs_array_sort, 44777 (gather) and 44824 (deleting trailing holes)flat,flatMapJS_FlattenIntoArray, 44594filljs_array_fill, 43799Array.from(array-like source)js_array_from, 43181fillandArray.fromdefine a property per index, so a memory limit stops them eventually, but until then they ignore the handler too.popandpushdo not loop over the length.with,toReversed,toSortedandtoSplicedallocate a dense result of the full length up front (above 2**31-1 that is a RangeError), so their loops are bounded by what can actually be allocated.The fix is a poll at the top of each of these loops, as in #1674. I have a patch with tests and can open a PR.