Noticed while working on #6296 / #6289 (PR #6298). #6216 made the knob-driven loops abortable and #6266 extended arcadedb.command.timeout coverage; both reasoned that time, unlike memory, has no honest ceiling to pick and so a long run should be abortable rather than forbidden. That reasoning was applied to the procedures whose work is multiplied by a knob. It was never applied to the ones whose work is multiplied by the graph, which are the slowest in the package.
19 of the 68 algo.* procedures create a WorkGuard. These four do not:
| Procedure |
Cost |
Knob? |
algo.apsp |
O(V³) Floyd-Warshall |
none - the graph alone sizes it |
algo.kShortestPaths |
O(k x pathLength x V²) Yen's over a dense matrix |
k, deliberately left unbounded |
algo.steinerTree |
O(t x (V² + E)) Dijkstra per terminal |
terminal list, caller-supplied |
algo.mst |
O(E log E) |
none |
Why algo.apsp makes the case on its own
The #6263 budget caps its distance matrix at arcadedb.cypher.algoMaxWorkingMemory, whose floor of 64 MB admits n ≈ 2890. So the budget's own answer to "is this graph acceptable?" is yes at 2890 nodes - and 2890³ is ~2.4 x 10¹⁰ inner iterations of the Floyd-Warshall triple loop. That is minutes of CPU on one query thread, during which:
Thread.interrupt() does nothing - there is no interruption check in the loop;
arcadedb.command.timeout does nothing - the deadline is never consulted;
- the client cancelling the query does nothing.
The budget and the timeout are meant to be complementary halves of the same guarantee, and here the memory half explicitly waves through a run the time half cannot stop. A user whose call the budget rejects gets an immediate, actionable error; a user whose call the budget accepts can lose a query thread for minutes with no way back. That is an odd place for the sharp edge to sit.
Suggested fix, and a better framing
Mechanically it is final WorkGuard guard = newWorkGuard(context); plus a guard.check() at the outer loop of each, and checkPeriodically(...) where a single outer iteration is itself large - algo.apsp's k loop iterates only V times while the body is V², so the i loop is the right checkpoint there, not the k loop. WorkGuard was built for exactly this and costs one flag test per iteration when no deadline is configured.
The better framing, worth deciding once rather than per-procedure: the guard belongs wherever the work is unbounded, and "unbounded" should mean unbounded by anything the caller controls - not just unbounded by a knob. The 19 procedures that got a guard were chosen by "does it have an iteration knob?", which is a proxy that misses every graph-driven loop. A sweep over all 68 asking "can one call of this run for minutes on an accepted input?" would settle the remaining 49 in one pass and stop the next such procedure from being written without one. #6264 did precisely that sweep for the knobs and found fourteen, not the three the issue predicted.
Worth pairing with a test in the shape Issue6216AlgoWorkKnobBoundsTest already uses, so the guarantee is pinned rather than asserted.
Related
#6216, #6266, #6263, #6264, PR #6298, #6300, #6301.
Noticed while working on #6296 / #6289 (PR #6298). #6216 made the knob-driven loops abortable and #6266 extended
arcadedb.command.timeoutcoverage; both reasoned that time, unlike memory, has no honest ceiling to pick and so a long run should be abortable rather than forbidden. That reasoning was applied to the procedures whose work is multiplied by a knob. It was never applied to the ones whose work is multiplied by the graph, which are the slowest in the package.19 of the 68
algo.*procedures create aWorkGuard. These four do not:algo.apspalgo.kShortestPathsk, deliberately left unboundedalgo.steinerTreealgo.mstWhy
algo.apspmakes the case on its ownThe #6263 budget caps its distance matrix at
arcadedb.cypher.algoMaxWorkingMemory, whose floor of 64 MB admits n ≈ 2890. So the budget's own answer to "is this graph acceptable?" is yes at 2890 nodes - and 2890³ is ~2.4 x 10¹⁰ inner iterations of the Floyd-Warshall triple loop. That is minutes of CPU on one query thread, during which:Thread.interrupt()does nothing - there is no interruption check in the loop;arcadedb.command.timeoutdoes nothing - the deadline is never consulted;The budget and the timeout are meant to be complementary halves of the same guarantee, and here the memory half explicitly waves through a run the time half cannot stop. A user whose call the budget rejects gets an immediate, actionable error; a user whose call the budget accepts can lose a query thread for minutes with no way back. That is an odd place for the sharp edge to sit.
Suggested fix, and a better framing
Mechanically it is
final WorkGuard guard = newWorkGuard(context);plus aguard.check()at the outer loop of each, andcheckPeriodically(...)where a single outer iteration is itself large -algo.apsp'skloop iterates only V times while the body is V², so theiloop is the right checkpoint there, not thekloop.WorkGuardwas built for exactly this and costs one flag test per iteration when no deadline is configured.The better framing, worth deciding once rather than per-procedure: the guard belongs wherever the work is unbounded, and "unbounded" should mean unbounded by anything the caller controls - not just unbounded by a knob. The 19 procedures that got a guard were chosen by "does it have an iteration knob?", which is a proxy that misses every graph-driven loop. A sweep over all 68 asking "can one call of this run for minutes on an accepted input?" would settle the remaining 49 in one pass and stop the next such procedure from being written without one. #6264 did precisely that sweep for the knobs and found fourteen, not the three the issue predicted.
Worth pairing with a test in the shape
Issue6216AlgoWorkKnobBoundsTestalready uses, so the guarantee is pinned rather than asserted.Related
#6216, #6266, #6263, #6264, PR #6298, #6300, #6301.