fix(gateway): release hygiene lock after timeout - #71569
Open
ciabata-git wants to merge 1 commit into
Open
Conversation
Signed-off-by: Hermes Agent <hermes-agent@users.noreply.github.com>
Collaborator
Related to merged #69866. This is a follow-up for the remaining durable-lock cleanup race after cancellation, with a distinct lock-publication fence and release-executor mechanism. |
teknium1
added a commit
that referenced
this pull request
Aug 1, 2026
…ering (review F4) A host timeout previously left the timed-out worker holding the durable per-session compression lock AND refreshing its lease indefinitely, so a truly hung summary blocked every later compression attempt; and a LATE successful summary could clear the failure cooldown the host had just recorded. Transplant the lease-cancellation invariants from PR #71569 (@ciabata-git): the worker publishes an idempotent, holder-scoped release hook on the fence once it owns the durable lock (begin_lock_setup / register_cancelled_lock_release close the acquire→publish race), the refresher start is serialized against the release path, and the host invokes the hook on idle timeout, hygiene timeout, and every unwind (revoke_commit_admission now also releases). ABA safety: the SessionDB release is holder-qualified (DELETE ... WHERE holder = ?), so a stale release can never free a replacement holder's lease. State ordering: the compressor consults a fence-cancellation check BEFORE clearing the failure cooldown, so a late worker cannot undo the host's timeout cooldown; the check is installed only for the fenced call and removed in a finally. Regression implements the reviewer's exact 5-step scenario: summary blocked indefinitely → host timeout → a NEW compressor acquires the durable lock while the old summary is STILL blocked → old worker released → it cannot clear cooldown, release the new holder's lease, or publish stale state. PR #76354 review, blocking finding 4 / merge gates 4 + 5. Co-authored-by: ciabata-git <ciabata-git@users.noreply.github.com>
teknium1
added a commit
that referenced
this pull request
Aug 2, 2026
…ering (review F4) A host timeout previously left the timed-out worker holding the durable per-session compression lock AND refreshing its lease indefinitely, so a truly hung summary blocked every later compression attempt; and a LATE successful summary could clear the failure cooldown the host had just recorded. Transplant the lease-cancellation invariants from PR #71569 (@ciabata-git): the worker publishes an idempotent, holder-scoped release hook on the fence once it owns the durable lock (begin_lock_setup / register_cancelled_lock_release close the acquire→publish race), the refresher start is serialized against the release path, and the host invokes the hook on idle timeout, hygiene timeout, and every unwind (revoke_commit_admission now also releases). ABA safety: the SessionDB release is holder-qualified (DELETE ... WHERE holder = ?), so a stale release can never free a replacement holder's lease. State ordering: the compressor consults a fence-cancellation check BEFORE clearing the failure cooldown, so a late worker cannot undo the host's timeout cooldown; the check is installed only for the fenced call and removed in a finally. Regression implements the reviewer's exact 5-step scenario: summary blocked indefinitely → host timeout → a NEW compressor acquires the durable lock while the old summary is STILL blocked → old worker released → it cannot clear cooldown, release the new holder's lease, or publish stale state. PR #76354 review, blocking finding 4 / merge gates 4 + 5. Co-authored-by: ciabata-git <ciabata-git@users.noreply.github.com>
teknium1
added a commit
that referenced
this pull request
Aug 2, 2026
…ering (review F4) A host timeout previously left the timed-out worker holding the durable per-session compression lock AND refreshing its lease indefinitely, so a truly hung summary blocked every later compression attempt; and a LATE successful summary could clear the failure cooldown the host had just recorded. Transplant the lease-cancellation invariants from PR #71569 (@ciabata-git): the worker publishes an idempotent, holder-scoped release hook on the fence once it owns the durable lock (begin_lock_setup / register_cancelled_lock_release close the acquire→publish race), the refresher start is serialized against the release path, and the host invokes the hook on idle timeout, hygiene timeout, and every unwind (revoke_commit_admission now also releases). ABA safety: the SessionDB release is holder-qualified (DELETE ... WHERE holder = ?), so a stale release can never free a replacement holder's lease. State ordering: the compressor consults a fence-cancellation check BEFORE clearing the failure cooldown, so a late worker cannot undo the host's timeout cooldown; the check is installed only for the fenced call and removed in a finally. Regression implements the reviewer's exact 5-step scenario: summary blocked indefinitely → host timeout → a NEW compressor acquires the durable lock while the old summary is STILL blocked → old worker released → it cannot clear cooldown, release the new holder's lease, or publish stale state. PR #76354 review, blocking finding 4 / merge gates 4 + 5. Co-authored-by: ciabata-git <ciabata-git@users.noreply.github.com>
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…ering (review F4) A host timeout previously left the timed-out worker holding the durable per-session compression lock AND refreshing its lease indefinitely, so a truly hung summary blocked every later compression attempt; and a LATE successful summary could clear the failure cooldown the host had just recorded. Transplant the lease-cancellation invariants from PR NousResearch#71569 (@ciabata-git): the worker publishes an idempotent, holder-scoped release hook on the fence once it owns the durable lock (begin_lock_setup / register_cancelled_lock_release close the acquire→publish race), the refresher start is serialized against the release path, and the host invokes the hook on idle timeout, hygiene timeout, and every unwind (revoke_commit_admission now also releases). ABA safety: the SessionDB release is holder-qualified (DELETE ... WHERE holder = ?), so a stale release can never free a replacement holder's lease. State ordering: the compressor consults a fence-cancellation check BEFORE clearing the failure cooldown, so a late worker cannot undo the host's timeout cooldown; the check is installed only for the fenced call and removed in a finally. Regression implements the reviewer's exact 5-step scenario: summary blocked indefinitely → host timeout → a NEW compressor acquires the durable lock while the old summary is STILL blocked → old worker released → it cannot clear cooldown, release the new holder's lease, or publish stale state. PR NousResearch#76354 review, blocking finding 4 / merge gates 4 + 5. Co-authored-by: ciabata-git <ciabata-git@users.noreply.github.com>
33hodl
pushed a commit
to 33hodl/hermes-agent
that referenced
this pull request
Aug 12, 2026
…ering (review F4) A host timeout previously left the timed-out worker holding the durable per-session compression lock AND refreshing its lease indefinitely, so a truly hung summary blocked every later compression attempt; and a LATE successful summary could clear the failure cooldown the host had just recorded. Transplant the lease-cancellation invariants from PR NousResearch#71569 (@ciabata-git): the worker publishes an idempotent, holder-scoped release hook on the fence once it owns the durable lock (begin_lock_setup / register_cancelled_lock_release close the acquire→publish race), the refresher start is serialized against the release path, and the host invokes the hook on idle timeout, hygiene timeout, and every unwind (revoke_commit_admission now also releases). ABA safety: the SessionDB release is holder-qualified (DELETE ... WHERE holder = ?), so a stale release can never free a replacement holder's lease. State ordering: the compressor consults a fence-cancellation check BEFORE clearing the failure cooldown, so a late worker cannot undo the host's timeout cooldown; the check is installed only for the fenced call and removed in a finally. Regression implements the reviewer's exact 5-step scenario: summary blocked indefinitely → host timeout → a NEW compressor acquires the durable lock while the old summary is STILL blocked → old worker released → it cannot clear cooldown, release the new holder's lease, or publish stale state. PR NousResearch#76354 review, blocking finding 4 / merge gates 4 + 5. Co-authored-by: ciabata-git <ciabata-git@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Fixes a gateway session-hygiene timeout race where cancellation prevented a late database commit but left the timed-out worker's durable per-session compression lock held until the synchronous auxiliary request returned. During that window the live turn could not compress and could send an oversized request.
Current
mainnow uses progress-aware inactivity timeouts, which correctly protect slow-but-streaming summary models. Genuine inactivity and the total-ceiling path still cancel a live synchronous worker, however, and retain the same durable-lock race. This PR preserves the progress-aware wait unchanged and closes that remaining cancellation path.The fix adds a narrow lock-setup boundary to
CompressionCommitFence. Cancellation cannot win between durable lock acquisition and publication of the exact-holder cleanup callback. Once cancellation wins, the gateway releases that holder off the event loop before continuing to the live agent. Release uses a small gateway-owned executor independent of the default pool occupied by hygiene workers, so a blocked summary cannot starve its own mandatory cleanup. The worker's eventual cleanup remains idempotent and cannot release a newer holder's lock.Related Issue
No public issue; reproduced from a production gateway incident.
Type of Change
Changes Made
agent/conversation_compression.pygateway/run.pytests/gateway/test_session_hygiene.pyexecutor=Nonework with the blocked hygiene worker and require live-agent entry plus exact lock reacquisition before releasing it;How to Test
origin/main, apply only the regression-test patch and run:.venv/bin/python -m pytest tests/gateway/test_session_hygiene.py::test_session_hygiene_timeout_continues_to_agent_and_sets_cooldown -qIt fails because
try_cancel_before_commit()returnsTruewhile hook publication is still blocked.scripts/run_tests.sh tests/gateway/test_session_hygiene.py tests/gateway/test_session_env.py tests/agent/test_compression_concurrent_fork.py tests/gateway/test_compression_concurrent_sessions.py tests/agent/test_aux_progress_streaming.py tests/agent/test_auxiliary_client.py tests/test_hermes_state_compression_locks.py tests/run_agent/test_compression_lock_defer.py -qExpected: 504 passed, including current-main progress streaming, gateway executor lifecycle, and durable-lock coverage.
.venv/bin/python -m py_compile agent/conversation_compression.py gateway/run.py tests/gateway/test_session_hygiene.py.venv/bin/ruff check agent/conversation_compression.py gateway/run.py tests/gateway/test_session_hygiene.pygit diff --checkChecklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — N/A; behavior is internal and documented in codecli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/AScreenshots / Logs
Behavioral RED on the current compression-progress base (
32fd9d65cf091269709c5a6301b25aadac681aa8) with only the final regression-test patch:The RED teardown consumed the handler and worker cleanly: no orphan future, destroyed task, secondary worker assertion, or gateway agent error.
Executor-starvation RED against the pre-V5 candidate:
Publication-base compatibility GREEN (
78c06525e8e955e06a007b07b347c679f3977c3e):Repository-wide sweep and isolated reruns:
Review history: V4 was rejected for default-executor starvation and incomplete RED teardown; both findings are addressed in V5. Independent immutable V5 reviews all passed with no findings or over-scope: specification/blocker resolution, concurrency/starvation/shutdown lifecycle, and code/test quality/scope.