Repository navigation
fix: persist daemon watches across client disconnects - #1833
Conversation
|
Thanks for opening this — it has been seen, and it is queued. This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence. Current review status: working through a backlog. What that means for this PR, concretely:
Things that will genuinely speed it up whenever review does happen:
If this fixes a bug, a reproduction we can run is worth more than a description of the symptom. Thanks for contributing, and sorry in advance for the wait. |
c3140ba to
ff916e8
Compare
Signed-off-by: LeoLan Digital <deine-email@example.com> Signed-off-by: LeoLan Digital <DEINE_ECHTE_GITHUB_EMAIL> Signed-off-by: LeoLan Digital <business@leolan.net>
ff916e8 to
5ca234c
Compare
|
Thank you for keeping the change focused on permanent-daemon watch ownership and preserving the non-permanent lifecycle. Session disconnects, watcher ownership, pruning, and background jobs form a sensitive lifecycle boundary, so we need more time to review it carefully. The queue is quite full, but we will come back with grounded feedback as soon as possible. |
|
Reviewed. The change is sound and the design question it turns on is answered well. Two lint items stand between it and merge, and the second one deserves care rather than the obvious fix. The ownership shift is the right shape. Treating the physical watch as daemon-owned rather than session-owned only in permanent mode, with non-permanent behaviour untouched, keeps the blast radius to the mode that actually wants it. And you answered the question I would have asked — if the watch outlives the session, who removes it — by leaving project pruning and daemon shutdown as the lifecycle paths that still tear it down. A watch nothing owns is a leak; a watch owned by the daemon in permanent mode is the point. The two lint failures
The flag itself is load-bearing: it is read at the The reason to be careful is that this sits inside the So the question to settle is narrow: can the loop re-reach that
You are much better placed to answer that than I am from the diff, since the loop body is mostly context here. OtherwiseYour baseline comparison is the right method — Please also rebase: |
|
Thanks for your patience on this one, @LeoLan-Digital. The permanent-daemon watch-ownership change is still the right shape. To close the loop on the open lint question from our last note, we traced it: the The other red is clang-format on |
|
Thanks again for the permanent-watch fix. The September 27 clarification still applies: remove the dead reset, keep the ownership flag and its read, and address the formatting and placeholder sign-offs. Please let us know when that revision is ready for another look. |
Bring the permanent-daemon watch ownership change up to current main. Three conflicts in src/daemon/application.c: - Struct comments: keep main's index-record bookkeeping struct as is, then the branch's comment on watch-job subscriptions describing the permanent-mode ownership. - application_release_session_watch_locked: main now releases every watch membership a session holds. Keep that loop and apply the branch's rule inside it: a permanent daemon keeps the physical watch when its last subscriber leaves; non-permanent applications still remove it. - application_background_index_admit: watch_live keeps main's application_watch_root_matches() and the index-args encoding that follows it, and additionally admits a permanent watch with no subscribers. Ownership hand-off: main split application_background_index into _admit and _wait, so the watcher-ownership flag set at admission could no longer reach the terminal unsubscribe in the wait loop. _admit now reports it through a bool out-parameter, application_background_index passes it to _wait, and _wait releases the retained subscription once when the job is terminal. The terminal block always breaks out of the loop, so the old reset of the flag after that release is gone. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
|
Thank you, @LeoLan-Digital, for this fix and for your patience while it waited in the queue. Making the physical watch daemon-owned only under Since the PR allows maintainer edits, we merged current main into your branch so it can move forward without another round trip on your side. Main reworked this part of
Your commit is unchanged; everything above is one signed-off merge commit (a728eeb) on top of it. We do not rewrite contributor history, so the extra sign-off lines in your commit simply stay, and there is nothing you need to do about them. A second, smaller step follows on top of this one: a few lifecycle edges of the permanent watch, the matching docs, and more tests. We will explain each change when it lands. If you push to this branch in the meantime, please pull first so nothing gets lost. Thanks again for the careful work! |
Under `daemon start` a watch now outlives its last session. delete_project from another session or the CLI removes the project's DB but meets no owner of the watch: daemon MCP sessions carry no watcher pointer, so nothing unwatches it, and the next change in that root re-indexed the deleted project. When a permanent watch has no subscribers, no job for its project is running and its DB is gone, the watcher callback now removes the watch and skips, the rule a session refresh already applies to a deleted project. The running-job guard is needed because a full reindex unlinks the DB while it rebuilds it: a second change during that rebuild must join the job, not end the watch. Session-owned watches are unchanged. Tests: daemon_application_permanent_watch_ends_when_project_db_is_deleted (RED before: the deleted project was re-indexed) and daemon_application_permanent_watch_kept_while_its_reindex_runs (RED without the running-job guard: the second change ended the watch). Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Under `daemon start` the session watch release kept every watch whose last subscriber left, so the daemon could keep watching after the session ended. The same release also runs when a session's root stops being allowed by workspace policy (an approval revoked, a boundary declared). That is not a disconnect: the session is still open and its root is refused, so keeping the watch there let a permanent daemon go on indexing a root the user had just withdrawn. application_release_session_watch_locked() now takes session_ending. The cancel and close paths pass true and keep the daemon-owned watch exactly as before; the workspace-policy branch of the watch refresh passes false and removes the watch in both modes, as it did before permanent watches. Test: daemon_application_permanent_workspace_release_still_ends_watch revokes the sensitive-root approval of a permanent session's root and sends one request; its synchronous refresh must end the watch (RED when the policy branch passes true). Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A daemon started with `daemon start` now keeps a project watched after its last owning MCP session closes, until the daemon stops, the project is deleted, its directory disappears, or auto_watch is set to false. README (the watcher paragraph and the index_repository row), the watcher_enabled row of docs/CONFIGURATION.md and the comment above the index_status watch provider still said every watch ends with its last session. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
|
Thank you again, @LeoLan-Digital. As promised, here is the second step on top of your change. Your core idea carries all of it: under We pushed three small commits:
Every new test was checked in both directions: it fails with its production piece reverted and passes with it. The same check now also covers the two parts of your change that did not yet have a failing test of their own, the If you push to this branch again, please pull first, since it now carries these commits on top of the merge. Thanks again for a well-scoped contribution and for your patience while it worked its way through the queue! |
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
|
Thank you for your patience, @LeoLan-Digital. CI went green on this branch, but this evening's merges on main touched files this pull request also changes, so that green no longer covered today's main. We merged current main into the branch once more (one merge commit, 545c1bd; your changes are untouched) and will merge once the fresh checks are green. Please pull before pushing again. |
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
|
Thank you for your patience, @LeoLan-Digital. A pull request that merged into main a few minutes ago (#2717) changes README.md, which this pull request also changes, so the CI run in progress here could no longer vouch for today's main. We merged current main into your branch once more (one merge commit, ed17097; it merged cleanly and your own change is untouched) and started a fresh run. We will merge once the required checks are green. Please pull before pushing again. |
|
A small correction to my note above, @LeoLan-Digital: CI had already finished green on this branch, and #2717 landed on main earlier tonight rather than a few minutes ago. It added one line to README.md, which this pull request also edits. The two changes do not touch each other, but a green only counts for merging here when the files it covers are unchanged on main, hence the fresh run. Everything else in the note stands - sorry for the noise. |
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
|
Thank you for your patience, @LeoLan-Digital. A pull request that merged into main tonight (#2017) changes docs/CONFIGURATION.md, which this pull request also changes, so CI on this branch could no longer vouch for today's main. We merged current main into your branch once more (one merge commit, 9c83689; it merged cleanly and your own change is untouched) and started a fresh run. We will merge once the required checks are green. Please pull before pushing again. |
test_daemon_application.c: the new auto-watch test passes false for the approve_broad parameter that DeusData#2017 added to cbm_workspace_grant_add, the same value the existing callers use; the merged tree did not compile without it. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
|
Thank you for your patience, @LeoLan-Digital. The last CI run on this branch failed to compile on every platform, and not because of anything you wrote: #2017 landed on main and added an We pushed one merge commit to your branch (b33a645, signed off): it merges current main and passes |
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
|
Thank you for your patience, @LeoLan-Digital. The macOS legs of the last CI run here were cancelled by GitHub itself: it is retiring its macOS 14 runners on November 2 and ran scheduled brownouts beforehand, so those jobs never got a runner. Nothing in your change caused it. Main now runs CI on macOS 15 (#2849), so we merged current main into your branch (one merge commit, 2609e56; it merged cleanly and your own change is untouched) to get a run on the new runners. Please pull before pushing again. |
|
Merged as d1d8bbf - thank you, @LeoLan-Digital! Under daemon start a project's watch now belongs to the daemon and survives client disconnects, so background reindexing keeps running with no client connected; the follow-up commits close the edges that a longer-lived watch opens (deleted projects, workspace-policy releases, docs). Thank you for a well-scoped contribution and for your patience while it worked through the queue. |
Summary
Keep project watches alive in permanent daemon mode after the last client session disconnects.
This allows daemon-owned background file watching and indexing to continue independently of client session lifetime.
What changed
Tests
Focused daemon application suite:
Full CLI baseline comparison:
tests/test_cli.c:1748git diff --checkpasses.Behavior
Non-permanent applications retain the existing session-scoped watch lifecycle.
Permanent daemon mode now treats the physical watch as daemon-owned rather than session-owned, while project pruning and daemon shutdown still remove the watch through their existing lifecycle paths.