fix(opencode): keep the session container off the recursive watch budget - #1355
Conversation
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
roborev: Combined Review (
|
A recursive registration counted every directory its walk visited against the shared watch budget, including directories an earlier root already watched natively. Overlapping roots spent budget on kernel watches the process was already paying for, and a later root could be refused coverage that already existed. RecursiveWatchResult now reports Allocated alongside Watched: Watched stays the count of directories the root covers, Allocated counts the native watches the registration installed, and RegisterRoots subtracts Allocated. Reuse is settled before the budget check, so a root whose directories are already watched registers cleanly against an exhausted budget, and its ownership is recorded so removing the first root does not drop coverage the second still needs. Watch cost is recorded once per native watch rather than once per registration, so reclaiming a shared watch returns one budget slot instead of one per root that had walked it. Refs kenn-io#1208
dfdd275 to
bbed1ef
Compare
roborev: Combined Review (
|
bbed1ef to
fe91f6a
Compare
roborev: Combined Review (
|
…e units OpenCode's SQLite container, its WAL, and the storage archive shared one recursive watch unit over the configured root, so the live container drew on the same shared recursive budget as the archive. A root reached after that budget was spent registered no native watch at all, and exhaustion inside the root's own walk marked the container's coverage degraded along with the archive's. The plan now emits a shallow container unit at the root, covering the database and its WAL, and a recursive unit at <root>/storage. A shallow watch never draws on the recursive budget, so the container's coverage no longer depends on the archive's size. The split applies only where it is representable. A root with no storage directory keeps the single recursive unit: naming an absent watch root would plan a polling obligation probed on a path that may never appear, and a probe that cannot be satisfied defers every other obligation on the same configured dir, leaving it with neither native coverage nor polling. A symlinked root also keeps it, because the daemon refuses to watch a recursive root through a symlink and gates the configured dir's reconciliation on the link target instead, and that check reads the unit's Recursive flag. Two units share one configured root and the engine dispatches every changed path once per emitted watch root, so unitScopeAllows scopes classification to the units of the configured root that owns the path; without it a WAL write would run the container's session listing twice. Configured roots can nest, so the deepest one containing the path owns it, and a virtual member path resolves to its physical container before the check. The container unit does not defer storage paths to the storage unit: whether that unit exists is a filesystem fact, while the engine resolves each provider's watch roots once and reuses that set, so a storage tree created afterwards would be dispatched only against the container root and claimed by nothing. An empty watch root stays unscoped for callers that do not dispatch per watch root. Non-OpenCode plans and the Darwin backend keep their current contracts. Refs kenn-io#1208
The daemon keeps one watch root per path, so two providers naming the same directory merge into a single root that is recursive if either of them was. A shallow registration installs its watch unconditionally, but a recursive one walked the whole tree under the shared budget and installed nothing at all once that budget was spent, so merging a shallow unit into a recursive one left the shared directory with no native watch. The root's own directory is now the mandatory part of a recursive registration and its subtree the discretionary part, which is what a shallow registration already does for the same path. An exhausted budget still reports BudgetExhausted for the subtree and hands it to polling. A mandatory watch installed past the budget stays outside the accounting entirely, the way a shallow root's already does: it is not charged, so removing it cannot refund a slot the process never spent and leave headroom above the cap for runtime subtree adds to claim. Refs kenn-io#1208
fe91f6a to
93f6486
Compare
roborev: Combined Review (
|
OpenCode's SQLite container, its WAL, and its storage archive share one recursive watch unit over the configured root, so the live container draws on the same shared recursive budget as the archive. A root reached after that budget is spent registers no native watch at all, and exhaustion inside the root's own walk marks the container's coverage degraded along with the archive's.
This separates each OpenCode-family root into a shallow container unit covering the database and its WAL and a recursive unit at
<root>/storage. A shallow watch never draws on the recursive budget, so the container's coverage no longer depends on the archive's size. The split applies only where it is representable: a root with no storage directory keeps the single recursive unit, because naming an absent watch root would plan a polling obligation probed on a path that may never appear, and a probe that cannot be satisfied defers every other obligation on the same configured dir. A symlinked root also keeps it, since the daemon refuses to watch a recursive root through a symlink and gates the configured dir's reconciliation on the link target instead.A native watch shared by overlapping roots is now charged to the shared budget once.
RecursiveWatchResult.Watchedstill counts the directories a root covers;Allocatedcounts the watches a registration installed, and registration subtracts that. Reuse is settled before the budget check, so a root whose directories are already watched registers cleanly against an exhausted budget and records its ownership, and reclaiming a shared watch returns one budget slot rather than one per root that walked it.Two units share one configured root and the engine dispatches every changed path once per emitted watch root, so
unitScopeAllowsscopes classification to the units of the root that owns the path; without it a WAL write would run the shared container's whole session listing twice. Configured roots can nest, so the deepest one containing the path owns it, and a virtual member path resolves to its physical container before the check. The container unit still claims storage paths: whether the storage unit exists is a filesystem fact, while the engine resolves each provider's watch roots once and reuses that set, so deferring would leave a storage tree created afterwards claimed by nothing. That costs one repeated source lookup per storage event, which the engine's per-pass source set discards; the database fan-out cannot double, because a storage watch root never contains the database or its WAL.This PR stands alone and supersedes PR #1318, whose conditional storage-unit split covered the same provider topology. Portable lifecycle ownership of a missing watch root is not attempted here. The bounded parser feed that motivated PR #1331 is abandoned rather than shipped: OpenCode exposes no producer-owned cross-drain journal cursor, so that work was closed and this slice is the salvage.
Refs #1208