summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-27 15:32:02 -0300
committerGabriel Schneider <[email protected]>2026-08-27 16:15:35 -0300
commitf5927a033f0c83753b5cc004e514568eec8c24f8 (patch)
treee31d0f29aac1d4266c42dac99fcdd0013bfbe64d /docs
parent5f4719da21f06b58694d52d354f5fda431ff8543 (diff)
downloadpardes-f5927a033f0c83753b5cc004e514568eec8c24f8.tar.gz
pardes-f5927a033f0c83753b5cc004e514568eec8c24f8.zip
detached: drop /dev/fuse from the poll set once the connection dies
Review fixes to steps 1 and 2, found by an adversarial pass over the committed chain. One is a real bug and the rest are comments that were false. THE BUG. `waitInput` put the FUSE descriptor in the poll set whenever the mount existed, and the `.fuse` arm ignored every revent. Linux's `fuse_dev_poll` answers EPOLLERR once the connection is gone, and POSIX reports POLLERR whatever the events mask asked for -- so an external `fusermount3 -u`, a sysfs abort, or systemd taking /run/user/$UID away at final logout (exactly when a detached session is supposed to keep running) made poll(2) return instantly, forever. The daemon then spun the whole pump at 100% of a core for the rest of its life, and re-offered every parked slot to the core at that rate. Measured on the unfixed commit: 0 CPU ticks over 10 s idle, then 1000 ticks over the next 10 s after unmounting its own mount point. Measured after the fix: 0 ticks over 8 s in the same scenario, process alive and in state S. fuse.zig's own poll thread has carried the equivalent guard all along, which is why the desktop shells never showed this and the daemon did. THE COMMENTS, each checkable and each wrong: * `Source.fuse` said the drain is not done in `dispatch` to avoid re-entering the core. Both `pull_wait_input` and `push_poll_frame` are called from inside `pump`, so either re-enters. The real reason is that `dispatch` is mid-iteration over a SNAPSHOT of the descriptors, and one `push_spawn` replaces a pane's master under it. * `fuse.zig`'s new import said "no cycle". There is a cycle: fs_service imports fuse.zig back and still names `*Fs` in three signatures. * `Transport`'s rationale said tty.zig and gui.zig store one in a struct field. Neither does; all four call sites build it inline. * `deinit` justified its ordering against "as long as the harvest takes". `harvest` is waitpid(WNOHANG) and blocks for nothing. The real reason to go first is that aborting the connection wakes a parked reader while its own shell is still alive to run its exit path. * `harvest` claimed pane shells are the only children this process forks. Since step 1 it also forks `fusermount3`, three times over -- reaped by its own spawner, so the conclusion holds and the premise did not. * docs/detached.md, docs/lsp.md and docs/design.typ still said the daemon implements sixteen of twenty-one methods and mounts no /dev/fuse.
Diffstat (limited to 'docs')
-rw-r--r--docs/design.typ5
-rw-r--r--docs/detached.md15
-rw-r--r--docs/lsp.md14
3 files changed, 16 insertions, 18 deletions
diff --git a/docs/design.typ b/docs/design.typ
index d322920a..ccb94a08 100644
--- a/docs/design.typ
+++ b/docs/design.typ
@@ -1157,8 +1157,9 @@ running in pane 3 is still running and has been scrolling into the core the whol
time.
Of the twenty-one `VTable` methods, the detached core's `Session` implements
-sixteen and leaves five null: `push_post_present`, `pull_gpio_toggle`,
-`pull_lsp`, `pull_pipe`, `push_fs_reply`.
+seventeen and leaves four null: `push_post_present`, `pull_gpio_toggle`,
+`pull_lsp`, `pull_pipe`. It mounts its own `/dev/fuse` and polls it in the same
+`poll(2)` as its frontends, so `--fs` works in a daemon and needs no thread.
Nothing blocks indefinitely, and that property is what a detached session is
*for*. Every descriptor is non-blocking; the single `poll(2)` is the only place
diff --git a/docs/detached.md b/docs/detached.md
index b4dde59c..d849c810 100644
--- a/docs/detached.md
+++ b/docs/detached.md
@@ -255,14 +255,13 @@ Stated rather than papered over:
* **The screen is shared, at the smallest common grid.** Two frontends of
different sizes converge on the smaller; the larger window letterboxes. Same
semantics as tmux.
-* **No LSP, no selection pipe, no `--fs` control filesystem** in a detached
- session. The daemon implements **sixteen** of `Host.VTable`'s **twenty-one**
- methods — FEWER than the tty and SDL shells, which install nineteen each,
- everything but `pull_gpio_toggle` and `push_detach` — and it is the only host
- that implements `push_detach` at all. The five it leaves null divide cleanly.
- Three are real losses: `pull_lsp` and `pull_pipe` want a
- worker pool this deliberately single-threaded loop has not got, and
- `push_fs_reply` wants a `/dev/fuse` this process never mounted. They fall back
+* **No LSP and no selection pipe** in a detached session. The daemon implements
+ **seventeen** of `Host.VTable`'s **twenty-one** methods — fewer than the tty
+ and SDL shells, which install nineteen each, everything but
+ `pull_gpio_toggle` and `push_detach` — and it is the only host that
+ implements `push_detach` at all. The four it leaves null divide cleanly.
+ Two are real losses: `pull_lsp` and `pull_pipe` want a worker pool this
+ deliberately single-threaded loop has not got. They fall back
to the core's in-process defaults rather than failing, so the features are
quiet rather than broken. The other two are not losses at all: there is no
moment "after the frame is on screen" for a process with no screen
diff --git a/docs/lsp.md b/docs/lsp.md
index 1adb397b..b35c8eeb 100644
--- a/docs/lsp.md
+++ b/docs/lsp.md
@@ -39,8 +39,8 @@ shell and the ESP32-P4 object: there `lsp.supports` is empty, which makes
response; a host that links a backend would add both.
**In a DETACHED session every query answers EMPTY.**
-`src/detached/server.zig`'s vtable implements sixteen of `host.zig`'s
-twenty-one methods, and `pull_lsp` is one of the five it leaves null — a
+`src/detached/server.zig`'s vtable implements seventeen of `host.zig`'s
+twenty-one methods, and `pull_lsp` is one of the four it leaves null — a
worker pool is precisely what its deliberately single-threaded loop does not
have. A null method is NOT automatically a dropped effect: `perform` decides
that per arm, and the `.lsp` arm's answer is to synthesise one on the spot —
@@ -69,16 +69,14 @@ has moved by the time the prong runs, the guard fails, and the Tab really is
eaten. The local shells have the same race over a wider window, so this is a
property of the late-indent repair rather than of detaching.
-None of the detached core's five null methods silently drops a reachable
+None of the detached core's four null methods silently drops a reachable
effect. `pull_lsp` and
`pull_pipe` have the fallbacks above; `pull_gpio_toggle` is
`orelse return Error.NoPads` (`board_memory.zig`), which lands on the message
row; `push_post_present` is a `pump` hook fired after presenting, and there is
-nothing to notify in a process with no screen; and `push_fs_reply` is the one
-`perform` arm with no fallback at all, but it is only ever emitted in answer to
-an `Event.fs_req`, which is not on the wire (`wire.zig`: it has no `ClientTag`,
-because neither half of that pair may cross an attachment) and which the
-daemon raises none of, mounting no `/dev/fuse` by its own vtable comment.
+nothing to notify in a process with no screen. `push_fs_reply` was listed here
+too until the daemon began mounting its own `/dev/fuse`; it implements that one
+now, and `--fs` works in a detached session.
Four rules make it safe: