Repository navigation
fix: recover contended session activity writes - #289
Conversation
There was a problem hiding this comment.
I found one blocking regression in the stream-draining change.
src/github_agent_bridge/dispatch.py:837 now waits indefinitely for the stdout/stderr reader threads after the OpenClaw CLI process has exited. Because those readers block until EOF on the pipe, any child/grandchild process that inherits stdout or stderr can keep the pipe open and pin the executor worker forever, even though the main CLI already returned successfully. The previous bounded join(timeout=1) avoided that failure mode. I reproduced this locally with a fake OpenClaw executable that prints once, exits, and leaves a background child sleeping with stdout inherited; dispatch() returned only after the child closed the descriptor (elapsed=3.01s, despite cli_grace_seconds=0.1). In production, a long-lived inherited fd would stall the job thread and prevent completion/follow-up.
Please keep the new decoupled activity delivery, but restore a bounded drain/cleanup path for reader threads after the main process exits, or explicitly close/terminate the process group so inherited pipe descriptors cannot hold the worker indefinitely.
What I checked:
- Reviewed the diff in
dispatch.py,executor.py, the new regressions, anddocs/persistence.md. - Local focused regressions:
pytest -q tests/test_executor.py::test_session_activity_recovers_after_transient_database_lock tests/test_executor.py::test_session_activity_does_not_hide_non_contention_database_errors tests/test_modes_cli.py::test_live_dispatch_propagates_activity_callback_failure tests/test_modes_cli.py::test_live_dispatch_drains_process_while_activity_callback_is_blocked --tb=short-> 4 passed. - Local affected suites:
pytest -q tests/test_executor.py tests/test_modes_cli.py --tb=short-> 73 passed. - GitHub CI is green for dashboard and pytest 3.11/3.12 at head
7ed9ddb4a419d3067def68a0d16e0dcf11fc91c5.
Co-authored-by: pilipilisbot <pilipilisbot@users.noreply.github.com>
|
Resolved the blocking stream-drain review in After the main CLI exits, stdout/stderr readers now get a shared one-second drain window using interruptible pipe polling. If a descendant still holds either descriptor, the bridge stops the readers and closes its local pipe ends instead of pinning the executor worker. The serialized activity-delivery path remains intact. Added a regression that starts a descendant inheriting both streams and verifies
The PR description now documents the lifecycle behavior and residual output-truncation risk. Ready for re-review. |
pilipilisbot
left a comment
There was a problem hiding this comment.
Approved. The follow-up commit fixes the blocking reader lifecycle issue I raised earlier while preserving the intended decoupling between pipe draining and SQLite session-event persistence.
What I checked:
- Re-read the PR and issue #287 context, including the previous blocking review and the follow-up note.
- Reviewed the changes in
dispatch.py,executor.py,docs/persistence.md, and the added regressions. - Verified that session activity writes now retry only SQLite busy/locked contention, while non-contention
OperationalErrors still propagate. - Verified that stdout/stderr readers keep draining independently of activity persistence and that the post-exit drain path is bounded when descendants inherit the pipes.
- Local focused regressions:
pytest -q tests/test_executor.py::test_session_activity_recovers_after_transient_database_lock tests/test_executor.py::test_session_activity_does_not_hide_non_contention_database_errors tests/test_modes_cli.py::test_live_dispatch_propagates_activity_callback_failure tests/test_modes_cli.py::test_live_dispatch_drains_process_while_activity_callback_is_blocked tests/test_modes_cli.py::test_live_dispatch_bounds_pipe_drain_when_descendant_inherits_streams --tb=short-> 5 passed. - Local affected suites:
pytest -q tests/test_executor.py tests/test_modes_cli.py --tb=short-> 74 passed. - Local full suite:
pytest -q --tb=short-> 529 passed, 1 pre-existing Starlette/httpx deprecation warning. - GitHub checks at head
2c090bb889f70f2432e5e46c5bef72cde097188e: dashboard, pytest 3.11, and pytest 3.12 are green; PR is mergeable.
|
Post-merge follow-up: PR #289 is merged as |
|
Post-merge cleanup follow-up: I found one remaining dedicated PR worktree that the previous cleanup note did not cover. |
|
Follow-up verification: the merged PR remains at |
Summary
SQLITE_BUSY/SQLITE_LOCKEDsession-event writes and expose recovered contention in the worker error countCause
The stdout and stderr reader threads persisted every chunk synchronously. A transient SQLite writer lock raised inside
read_stream, terminated that reader thread, and could leave the OpenClaw subprocess blocked on a full pipe. The exception was also detached from the executor's normal job-failure path.Decoupling persistence exposed a second lifecycle edge: an unbounded reader join could wait forever after the main CLI exited if a descendant inherited stdout or stderr. Readers now use interruptible pipe polling and receive a bounded one-second drain window before cleanup.
Validation
pytest -q --tb=short— 529 passed, 1 pre-existing Starlette/httpx deprecation warningBEGIN IMMEDIATElock proves the event is persisted after contention clearsdispatch()indefinitely after the main CLI exitsRisk
Low and limited to live subprocess activity delivery and final pipe cleanup. Existing callback timing remains live, but callbacks are now serialized. Retry classification reuses the executor's existing SQLite busy/locked predicate; schema and storage errors remain fatal. Output written by lingering descendants after the bounded post-exit drain window is deliberately not retained.
Closes #287
Requested by: trusted GitHub Agent Bridge automation for issue #287 (no human requester identified)