diff options
Diffstat (limited to 'features.txt')
| -rw-r--r-- | features.txt | 56 |
1 files changed, 56 insertions, 0 deletions
diff --git a/features.txt b/features.txt index fa1c0f6b..0db7c497 100644 --- a/features.txt +++ b/features.txt @@ -202,3 +202,59 @@ means what it says when fed back as configuration -- the symmetry the report was Left failing, deliberately: `nested-optout`. See docs/divergences.md -- two levels of nesting prepend U+E016 (vaxis's F3) to typed lines, which is a real bug in the key-forwarding path and not a stale expectation. + +9P is answered on the connection's task now, not by the editor's loop. The loop used to be the only thing that could answer a +request, which made the editor's own syscalls through a mount of its own tree -- a Look at /mnt/9p/pardes/<me>/anything under a +`9ns --mntgen` view, a Save into it -- a request only the blocked loop could serve. The name-based refusal that followed +(ownMountSuffix) and the in-process routing of a mount of oneself (Client.sameSession) were patches over that, and both are gone. +What replaced them is one rule, `pardes.turn`: the core is single-threaded, the editor's thread has the turn by default and gives it +up in two kinds of gap -- while it waits for input (`rest`/`wake`, one site per shell) and while a step is out in a host syscall +(`yield`/`back`, inside the leaf calls in fs.zig and the few host-side ones: the file watcher's marks, a shell's cwd stat) -- and a +cloud9 connection task takes it in those gaps to answer. Which gap it is matters: waiting, anything goes; out in a syscall +mid-step, the core reads consistently but the step still holds pointers into it, so a request that would change a pane +(tree.needsQuiet: writes, truncations, /pane/new, rmdir, and the screen open because it renders) is parked in the engine with +Status.again and retried when the turn is next given up quiet. That needed cloud9's engine to park an open, a truncating wstat, a +clunk and a remove, not only reads and writes -- a parked job goes back into the job slot on retry, and a walk still cannot park +because its names live in the input frame. A changing request that queued effects waits (`awaitSettled`) until the editor's pump has +performed them before it is answered, so `echo Save > exec` still returns with the file written, the way `put` does in acme; and a +request settles the way a step does (`sync`, `fsReport`, `events.announce`), because without that a `/log` reader waited for the +user's next keystroke to hear about the pane the request made. + +Two traps that cost real time. The first end-to-end run hung anyway: the Look completed on the connection task, then the editor woke +to perform the new pane's watch effect, and `inotify_add_watch` on a FUSE path is itself a request -- made while holding the turn. +The rule is therefore every host syscall on a user path, not fs.zig's alone; PDFs are read whole and opened from memory for the same +reason (MuPDF reads a file lazily at every page). The second: a parked write one byte longer than `park_data_max` fails EAGAIN, and +the client's largest write is msize less the 23-byte Twrite header, not less iohdrsz; a large Save to a peer's /os path came back +"save: Remote" until the engine's room was sized from a client. test/selfmount.py runs the editor under `9ns --mntgen` and Looks at, +reads and Saves its own tree through the mount; a unit test in 9p_io.zig pins that a change parks while the editor is out mid-step +and lands when it rests, while a read is answered in the window. + +Performance, same machine, Debug builds, 9P over the Unix socket against a tty session: a read of /index 278us -> 61us and a pane +body 283us -> 64us, because a read no longer waits for the editor to wake; a truncating body write 1184us -> 609us; exec Msg 696us +-> 543us; pane/new + rmdir 1535us -> 1295us; exec Save 718us -> 583us. A change still costs one editor frame, since the editor +draws it before it rests again. + +Left as they are: QUIC still runs on the editor's thread (cloud9's QUIC adapter is not std.Io based), so a QUIC client of a session +that Looks at its own QUIC-mounted tree would still wait on the loop; the Tty9p kernel mount lives in the helper's private +namespace, so a Look on `$PARDES_MOUNT/...` from that shell opens nothing in the editor -- no longer a deadlock, only a namespace +the editor is not in; and a message wider than its pane now keeps its tail rather than its head (msg.snap `msgtail`). + +Two adversarial reviews of that design. The concurrency one found the lost wake for real: a connection task's own yield saved the +editor's mid-step "not quiet" and restored it after the editor had rested, so a resting editor looked busy forever and every change +parked with nobody to wake it -- `quiet` is now `out`, a count of steps out in a syscall from any thread, and nothing saves or +restores it. Behind it, three more: the editor could take its turn back while a connection task's step was still out (that step's +pointers dangling when it returned) -- `wake` now waits for the count to reach zero; the core's allocator was a stack-fallback +over a FixedBufferAllocator, thread-safe only in Debug where the DebugAllocator wrapped it -- the fixed buffer is taken through +its lock-free interface now; and a Restore let a task waiting on the old core wake after that core was freed -- `reset` swaps the +listener's core first and releases every waiter, which then finds the core changed and answers nothing. It also showed that +parking `pane/new` and `screen` would have hung a Look at exactly those two paths in one's own mount (the editor's own open would +park, waiting for a rest that cannot come), so those are answered mid-step: every yield sits before its step's mutation, so the +layout and the surface are whole under it. Images are read at open for the same reason PDFs are. + +The rendering one found the chips drawn out from under: a placed image or PDF page is drawn after the cells (the GUI's image pass, +kitty's z-order), so a chip over a picture was painted over -- pictures give up the rows now, text keeps the overlay -- and in the +GUI a tree-sitter context band was emitted after the tag layers and painted over the chip, so body layers go first. A message is +one row of printable text (a language server's multi-line report read as one line with U+FFFD per newline), the 256-byte cut +never leaves half a glyph, and an over-wide message cut inside a wide glyph now drops that glyph rather than clipping its last. +The simplicity one cut the vestiges: `ninep_identity`, the restore's copy of listener addresses, `setWake`, the mailbox's +`Drained` name, the 64K read buffer, and the two macOS entry points whose multi-line signatures the wrapping had missed. |
