diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-27 20:09:02 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 28c814aa5cfea23e8950ef007916ffc5d089288d (patch) | |
| tree | 6e8c017c2140e1e7e8d0f3ee584a7f4aee1050f8 /src/ninep/tree.zig | |
| parent | c1be5b6b7c11f5dc17fd221c8d01b5693d0ffd0b (diff) | |
| download | pardes-28c814aa5cfea23e8950ef007916ffc5d089288d.tar.gz pardes-28c814aa5cfea23e8950ef007916ffc5d089288d.zip | |
Hold a read that has nothing yet and answer it when its file has news
A following log, event, pty/data or a pty/run before its answer used to
answer .again and wait for a wakeAll, which only the parked-write path
asked for, so the band-aid had every queue push set turn.parked. Now the
core keeps such a read (ctlfs.hold) and, as the turn is given up after
anything that queued a record, ran a command out or closed a pane,
answers it on its own connection, the way factotum answers the log reads
it keeps and acme an event read. Only a read the engine still holds
parked is answered, because cloud9 tells the backend nothing of a
Tflush, so a flushed read spends no record.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/tree.zig')
| -rw-r--r-- | src/ninep/tree.zig | 31 |
1 files changed, 31 insertions, 0 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index e8bcad2f..3ebea669 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -107,6 +107,32 @@ pub fn changesPane(req: Req) bool { pub const out_reserve = 4 * 1024; +/// A read that found nothing yet (`.again`), kept to be answered when what it +/// waits on has something, the way factotum keeps its log's waiting reads +/// and answers them on append (security/auth/factotum/log.c:4-52) and acme +/// an event read (editors/acme/xfid.c:994). `asker` is the connection it +/// came on, which only the listener knows (src/9p_io.zig, `answerHeld`). +pub const Held = struct { asker: *anyopaque, req: Req }; + +/// Keeps a read that answered `.again`. An open waits with one read at a +/// time, as acme's window keeps one `eventx`: a newer read on the same open +/// takes the place of the older. +pub fn hold(p: *Pardes, req: Req, asker: *anyopaque) void { + var free: ?*?Held = null; + for (&p.fs.held) |*slot| { + const held = slot.* orelse { + if (free == null) free = slot; + continue; + }; + if (held.req.node == req.node and held.req.handle == req.handle) { + slot.* = .{ .asker = asker, .req = req }; + return; + } + } + // One slot per open that can wait, so a free one is always there. + if (free) |slot| slot.* = .{ .asker = asker, .req = req }; +} + // ---- nodes ---- pub const TopFile = enum(u4) { @@ -559,6 +585,11 @@ fn remove(p: *Pardes, req: Req) Reply { } fn release(p: *Pardes, req: Req) Reply { + // A read held on this open goes with it: a hangup pays its releases + // before the connection's slot is reused, so none outlives its asker. + for (&p.fs.held) |*slot| if (slot.*) |held| { + if (held.req.node == req.node and held.req.handle == req.handle) slot.* = null; + }; // The handle's bookkeeping runs whether or not the removal is allowed. const done = releaseHandle(p, req); return if (req.remove) remove(p, req) else done; |
