Skip to content

Fix lgrep pool wedge from abandoned index#3

Merged
JRedeker merged 15 commits into
mainfrom
change/fixLgrepPoolWedgeAbandoned
Jun 10, 2026
Merged

Fix lgrep pool wedge from abandoned index#3
JRedeker merged 15 commits into
mainfrom
change/fixLgrepPoolWedgeAbandoned

Conversation

@JRedeker

Copy link
Copy Markdown
Contributor

Problem

lgrep's shared MCP daemon permanently wedged its 2-worker bounded executor when a search-triggered auto-index was abandoned at the 8s tool timeout. The thread kept running with no cooperative cancellation, so abandoned index_all jobs exhausted the pool and every subsequent pokeedge search timed out.

Fix

Cooperative cancellation threaded to every blocking seam, plus a wall-clock backstop:

  • OperationCancelled moved to lgrep/exceptions.py (breaks indexing↔embeddings import cycle; re-exported from indexing.py for back-compat)
  • cancel_event threaded through index_allindex_file (pre-embed/pre-storage) → embed_documents (per-batch) → _embed_batch_with_retry (per-attempt + cancel_event.wait(timeout=delay) replacing an up-to-31s un-cancellable retry sleep)
  • RuntimeSupervisor.run_blocking sets the event on asyncio.CancelledError before re-raising
  • LGREP_INDEX_MAX_WALL_S (default 60s) wall-clock backstop on index_all
  • _check_staleness bounded by LGREP_STALENESS_DEADLINE_S (default 4.0s)
  • Search-path wiring fix: _auto_index_project_single_flight now passes cancel_event INTO index_all (caught in acceptance review)

Verification

  • 9 dedicated cancellation regression tests (tests/test_runtime_cancellation.py)
  • Full suite: 606 passed, 2 skipped, 0 failed; ruff clean
  • Live probe vs pokeedge (2157 files): abandoned index_all reached terminal failed_after_abandon and freed its worker — permanent wedge resolved
  • Spec lgrepDaemonOperationalSafety (rq-daemon-cancel01.1/.2/.3, rq-daemon-executor01.1/.2) honored + strengthened

Follow-ups (out of scope)

  • Voyage client.embed() HTTP timeout for sub-30s abandoned-job termination (change addVoyageEmbedHttpTimeout created)
  • Thread cancel_event through index_semantic tool + watcher paths
  • Background-index redesign to eliminate cold-repo search timeouts

🤖 Generated with ADV

JRedeker added 15 commits June 10, 2026 14:00
Change: fixLgrepPoolWedgeAbandoned
Task: tk-45441be6f257
Mode: complete
Verification: TDD red: 4 tests in tests/test_runtime_cancellation.py, all fail against current code. Failures: ImportError on OperationCancelled (AC1, doesn't exist); AssertionError on cancel_event not set by run_blocking (AC2, current code doesn't propagate); AttributeError on state.db (AC3, mock setup needs more attrs).
Change: fixLgrepPoolWedgeAbandoned
Task: tk-cd9d5fa9cb07
Mode: complete
Verification: Added OperationCancelled exception and threading.Event kwarg to Indexer.index_all. Loop checks cancel_event at top of each iteration. AC1 tests pass (2/2).
Change: fixLgrepPoolWedgeAbandoned
Task: tk-dafdc284c4ca
Mode: complete
Verification: RuntimeSupervisor.run_blocking now accepts cancel_event kwarg and sets it on asyncio.CancelledError before propagating. AC2 test passes.
Change: fixLgrepPoolWedgeAbandoned
Task: tk-f9aa510faf46
Mode: complete
Verification: _auto_index_project_single_flight now constructs cancel_event per attempt, passes it to runtime.run_blocking, and catches OperationCancelled to return state cleanly. AC1 + AC2 tests pass.
Change: fixLgrepPoolWedgeAbandoned
Task: tk-3e97da692b6b
Mode: complete
Verification: _check_staleness now bounded by LGREP_STALENESS_DEADLINE_S (default 4.0s). On deadline, returns (False, 0) and emits staleness_check_deadline_exceeded log. All 4 tests pass.
Change: fixLgrepPoolWedgeAbandoned
Task: tk-a66cd1a02904
Mode: complete
Verification: Updated skills/lgrep/SKILL.md with staleness deadline section. Updated instructions/lgrep-tools.md with one-line fallback note. No code changes.
Change: fixLgrepPoolWedgeAbandoned
Task: tk-98253123e32d
Mode: complete
Verification: Full pytest: 601 passed, 2 skipped, 0 failed. Ruff: clean.
Change: fixLgrepPoolWedgeAbandoned
Task: tk-7e160f0d3360
Mode: complete
Verification: TDD red: added 5 v2 tests covering AC8 (embed_documents between-batch cancel + _embed_batch_with_retry immediate wait-abort), AC9 (index_file pre-embed + pre-storage cancel), AC10 (index_all wall-clock backstop). All 5 FAIL against v1 code (ImportError on lgrep.exceptions for AC8; no cancel_event on index_file/embed_documents for AC9; no wall-clock backstop for AC10). Confirmed 5 failed.
Change: fixLgrepPoolWedgeAbandoned
Task: tk-2f2785109a4a
Mode: complete
Verification: Created src/lgrep/exceptions.py with OperationCancelled; indexing.py now imports + re-exports it (from lgrep.exceptions import OperationCancelled, plus __all__). Verified `is` identity between lgrep.indexing.OperationCancelled and lgrep.exceptions.OperationCancelled; v1 tests (AC1 index_all cancel + mid-loop) still pass 2/2. Circular import broken: embeddings can now import from lgrep.exceptions.
…checks)

- Add cancel_event kwarg to index_file signature and docstring
- Raise OperationCancelled before embed step when event is set
- Pass cancel_event into embed_documents call
- Raise OperationCancelled before storage step when event is set
- Wire cancel_event through index_all per-file calls
- Update test monkey-patch to accept **kwargs for new signature

Verification: 2 AC9 tests + 2 existing index tests pass; ruff clean.
- embed_documents: accept cancel_event, check between batches
- _embed_batch_with_retry: accept cancel_event, check before each attempt,
  pass through recursive token-split calls, replace time.sleep(delay) with
  interruptible cancel_event.wait(timeout=delay) for immediate backoff abort
- Imports: threading (TYPE_CHECKING), OperationCancelled from lgrep.exceptions

Verification:
- pytest tests/test_runtime_cancellation.py -k embed  : 3 passed
- pytest tests/ -k embed -q                            : 23 passed
- ruff check src/lgrep/embeddings.py                   : clean
Change: fixLgrepPoolWedgeAbandoned
Task: tk-d738b1eb0c9a
Mode: complete
Verification: AC10 wall-clock backstop added to index_all (indexing.py:92 reads LGREP_INDEX_MAX_WALL_S default 60.0s; line 126 emits index_all_wall_clock_exceeded warning + raises OperationCancelled when budget exceeded, placed right after the cancel_event check at the loop top). Verified via adv_run_test: all 9 tests in test_runtime_cancellation.py pass (AC1,AC2,AC3,AC8,AC9,AC10 + existing). Engineer's checkpoint did not land so committing here.
Change: fixLgrepPoolWedgeAbandoned
Task: tk-14cc20156f84
Mode: complete
Verification: AC7 verified via adv_run_test: full suite `uv run pytest` = 606 passed, 2 skipped, 0 failed (up from 601; +5 v2 cancellation tests). `uv run ruff check src tests` = All checks passed. AC6 satisfied: tests/test_runtime_cancellation.py now covers AC1,AC2,AC3,AC8,AC9,AC10 + rq-daemon-cancel01.3 (9 tests, all green). Also updated skills/lgrep/SKILL.md with the cooperative-cancellation + LGREP_INDEX_MAX_WALL_S backstop documentation.
Acceptance review caught that _auto_index_project_single_flight passed
cancel_event to run_blocking (which only .set()s it on cancellation) but
did NOT forward it into index_all() itself — so the per-file + mid-embed
cooperative cancel checks never fired in the live search path; only the
wall-clock backstop could terminate an abandoned index. Wrap the call in a
lambda that passes cancel_event=cancel_event into index_all so AC1/AC8/AC9
cooperative cancellation is effective in production, not just unit tests.

Also update two test_server.py doubles to accept ignored kwargs.
@JRedeker
JRedeker merged commit ac393ee into main Jun 10, 2026
1 of 4 checks passed
JRedeker added a commit that referenced this pull request Jun 10, 2026
Bump version 3.1.0→3.1.3 (aligns to next tag after v3.1.2) and add the
CHANGELOG entry for the lgrep pool wedge fix (PR #3, merged as ac393ee).
The auto-release workflow skipped a release for that merge because the
squash-merge title was not a conventional commit; this cuts v3.1.3
explicitly.
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.

1 participant