Skip to content

fix(progress): IndexProgressRenderer.apply() can run concurrently with drain thread on Ctrl+C abort #418

Description

@HumanBean17

Problem

Latent thread-safety concern surfaced by #414. Low severity (abort path, process exiting), tracked separately so it isn't lost.

Detail

Before #414, on Ctrl+C the drain threads were already joined (dead) when the `_run_cocoindex_update_impl` `finally` ran its terminal `on_progress(...) → renderer.apply()`.

After #414 the abort path deliberately re-raises without joining the drain threads (joining is what hung). So a still-live drain thread's `ProgressRelay._route_line → apply` can run concurrently with the main thread's `finally → apply`. `rich.Progress` is not documented thread-safe.

Mitigations already in place

  • Bounded: abort path only, process exits via `os._exit` immediately after.
  • `ProgressRelay._route_line` guards its `apply` with `except Exception` (`src/java_codebase_rag/progress.py`), so the drain side can't crash.

Fix idea (if pursued)

Give `IndexProgressRenderer.apply` an internal lock so concurrent callers serialize. Low priority given the abort context.

Other minor items noted in the #414 review (bundled here to avoid spam)

  • Footer/exit-code mismatch: `cli._run_pipeline` renders a footer from `code = 2` in its `finally`, but the process actually exits `130` via the new handler. Cosmetic, cli indexing path only.
  • Exit code 130 (indexing) vs 2 (interactive `install` prompt) differ by phase — pre-existing; document if exit-code SLAs matter to operators/scripts.
  • Test coverage gaps: `_abort_child` non-`KeyboardInterrupt` abort exception; `_popen_capturing_stderr` normal capture under `verbose=False` (`_LineFilter`) and `on_progress` (`ProgressRelay`) branches.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions