Skip to content

fix(cypher): add a wall-clock execution deadline to abort runaway queries (#601)#1109

Merged
DeusData merged 1 commit into
mainfrom
fix/601-cypher-exec-deadline
Jul 16, 2026
Merged

fix(cypher): add a wall-clock execution deadline to abort runaway queries (#601)#1109
DeusData merged 1 commit into
mainfrom
fix/601-cypher-exec-deadline

Conversation

@DeusData

Copy link
Copy Markdown
Owner

Problem (#601)

An unbounded whole-graph OPTIONAL MATCH — or a GROUP BY that yields roughly one group per node — does O(bindings × groups) work in execute_return_agg and can run for minutes on a large graph (reporter: 93k nodes / 488k edges) before producing a single row. The existing 100k row ceiling never fires because no rows are produced, so query_graph just hangs — no partial result, no error, the caller waits indefinitely.

MATCH (a)
OPTIONAL MATCH (a)-[:CALLS]->(b)
RETURN a.qualified_name, count(b)

Fix

Add a monotonic wall-clock deadline alongside the row ceiling (same guard philosophy, complementary trigger):

  • Arm a budget (default 30s) at query entry via cbm_now_ms().
  • Check it — throttled, every 1024 iterations — in the three hot loops that can blow up before rows exist: the initial node scan, relationship expansion (expand_pattern_rels), and aggregation grouping (execute_return_agg).
  • On expiry, abort and return a clear, actionable error instead of hanging:

    query exceeded the execution time limit — narrow the pattern with a WHERE filter, use a directed MATCH instead of an unbounded OPTIONAL MATCH, or add LIMIT

State is _Thread_local (per-query, concurrency-safe). No new MCP tool / env var / config knob — a hardcoded budget, consistent with the existing hardcoded row ceiling.

Tests (reproduce-first)

  • cypher_exec_deadline_aborts_runaway_query_issue601 — RED-verified: with enforcement neutralized the query completes (rc==0) exactly as today; with the fix it aborts with the time-limit error and 0 rows.
  • cypher_exec_deadline_allows_normal_query_issue601 — the default budget does not false-positive on a normal small query.

A thread-local test hook (cbm_cypher_test_set_deadline_ms) forces the budget so the guard is covered deterministically without needing a giant graph.

Full cypher (152) and mcp (165) suites pass; lint-ci clean.

Closes #601.

…ries

An unbounded whole-graph OPTIONAL MATCH or a GROUP BY that produces roughly one
group per node does O(bindings x groups) work in execute_return_agg and can run
for minutes on a large graph before emitting a single row. The 100k row ceiling
never fires (no rows are produced), so query_graph just hangs with no partial
result and no error (#601).

Arm a monotonic wall-clock budget (default 30s) at query entry and check it
(throttled, every 1024 iterations) in the scan, relationship-expansion and
aggregation hot loops. On expiry the query aborts with a clear, actionable error
instead of hanging. A thread-local test hook forces the budget so the guard is
covered by a deterministic reproduce-first test; a companion test confirms the
default budget does not false-positive on a normal query.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData
DeusData enabled auto-merge July 16, 2026 01:10
@DeusData
DeusData merged commit 5d150ea into main Jul 16, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] query_graph hangs on whole-graph OPTIONAL MATCH (no execution timeout / degradation) on large graphs

1 participant