diff options
| author | Gabriel Schneider <[email protected]> | 2026-10-01 20:50:48 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 22:07:48 -0300 |
| commit | 406f483288c7689bdd2e1f3f68b276c61ca9412a (patch) | |
| tree | b6398fb9974b91441563fd655d18ddaf182a2a5a /src/9p_io.zig | |
| parent | b55cb01d2777760ed79b545dfca7115837591577 (diff) | |
| download | pardes-406f483288c7689bdd2e1f3f68b276c61ca9412a.tar.gz pardes-406f483288c7689bdd2e1f3f68b276c61ca9412a.zip | |
An Edit's held write: < | > commands no longer block their 9P connection (answered like a held read, so a status read or a filter reading the session's own mount runs beside it), a Tflush or hang-up of the write kills the commands' process groups and changes nothing; e loads by Get's way (asked once, clean after, undo puts the name back); ~ in e r w f B; B checks every name first; an Edit that runs commands is a write of its own, refused up front with other lines; Edit's +Errors output keeps the keyboard where it was; X goes in pane order; a refused write open says its errno; the reference's Coming from acme rows say what Get file's undo, failure and directory refusal, Putall's answer, Zerox across Dump and Incl's directories now do
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/9p_io.zig')
| -rw-r--r-- | src/9p_io.zig | 73 |
1 files changed, 66 insertions, 7 deletions
diff --git a/src/9p_io.zig b/src/9p_io.zig index 46bbb2ba..a01eb491 100644 --- a/src/9p_io.zig +++ b/src/9p_io.zig @@ -180,6 +180,10 @@ pub const Listener = struct { restore_writer: std.atomic.Value(bool) = .init(false), /// Clients turned away with every slot taken, since the log last said so. refused: std.atomic.Value(u32) = .init(0), + /// Writes being served right now, a retry of a parked one included: a + /// held Edit write a retry has taken out of its park is not flushed + /// (watchEdit). + writes_out: std.atomic.Value(u32) = .init(0), /// Connections accepted so far, and the count each slot's connection /// was accepted at: a Restore cuts those accepted before it (`reset`), /// and a new client in a freed slot has a later stamp. Written on the @@ -241,6 +245,10 @@ pub const Listener = struct { fn onServe(ctx: ?*anyopaque, conn: *Runner.Conn, req: pardes.ctlfs.Req) void { const l = of(ctx); + if (req.op == .write) _ = l.writes_out.fetchAdd(1, .acq_rel); + defer if (req.op == .write) { + _ = l.writes_out.fetchSub(1, .acq_rel); + }; // Once `stop` has begun the editor is tearing down and may never // rest again: answer without waiting on it. if (l.stopping.load(.acquire)) { @@ -268,9 +276,11 @@ pub const Listener = struct { const changes = pardes.ctlfs.changesPane(core, req); const fills = pardes.ctlfs.handsOff(core, req); const reply = core.serveFs(req); - // Taken now: a request served while this one waits starts afresh. - const edit_at = core.fs.edit_answer_at; - core.fs.edit_answer_at = null; + const edit_started = core.fs.edit_started; + core.fs.edit_started = false; + // The held Edit write asked again by a retry, its Edit still + // running: parked again, as it was. + if (reply.status == .again and req.op == .write) return conn.reply(&reply, ""); if (req.op == .release and quiet) l.collectOs(); // A close's held last line runs now when no step is out, so its err // is logged before the close is answered, as before; with a step @@ -335,6 +345,19 @@ pub const Listener = struct { // is on its way out and will settle nothing this could wait for, // and `deinit` lets the answer out before it cuts the connections. if (core.quit) return conn.reply(&reply, ""); + // An Edit whose `<`, `|` or `>` commands run off the loop: the write + // is held, as a read with nothing yet is, and answered when the Edit + // is done (answerHeld), so the connection serves on meanwhile -- a + // filter that reads this session through a mount among what it + // serves. A flush of it, or a hang-up, stops the Edit (watchEdit). + if (edit_started and core.pipe.edit_run != null) { + const later: pardes.ctlfs.Reply = .{ .tag = req.tag, .status = .again }; + const ticket = conn.hold(&later) orelse return; + core.fs.edit_hold = .{ .asker = conn, .slot = ticket.slot, .seq = ticket.seq, .tag = req.tag, .node = req.node, .handle = req.handle, .written = reply.written }; + const watcher = std.Thread.spawn(.{}, watchEdit, .{ l, conn, ticket }) catch return; + watcher.detach(); + return; + } const restoring = core.restore_req != null; // Set with the turn still held, so `reset` sees it and waits. if (restoring) l.restore_writer.store(true, .release); @@ -360,10 +383,6 @@ pub const Listener = struct { } }; } - // An Edit whose `<`, `|` or `>` commands run off the loop: the write - // is answered once they have and the Edit is applied, or failed - // (late_failure), as `echo Edit , | sort > ctl` must return sorted. - if (edit_at) |n| if (l.core == core) pardes.turn.awaitEdit(n); core.fs.write_waits = false; // ponytail: one slot, so a failure of another client's effects that // settle in the same wait is told to this write too. @@ -418,6 +437,46 @@ pub const Listener = struct { const conn: *Runner.Conn = @ptrCast(@alignCast(held.asker)); if (!conn.answerWith(held.ticket, Held{ .core = core, .req = held.req }, Held.make)) o.held = null; } + // An Edit's held write, its Edit done. Not waiting: a retry has it + // out, and its asking answers it (Pardes.serveFs); or it was + // flushed, and watchEdit lets the record go. + if (core.fs.edit_hold) |h| if (h.done) { + const conn: *Runner.Conn = @ptrCast(@alignCast(h.asker)); + _ = conn.answerWith(.{ .slot = h.slot, .seq = h.seq }, core, struct { + fn make(c: *pardes.Pardes) ?Runner.Conn.Answer { + return .{ .reply = pardes.filesystem.EditHold.answer(&c.fs) }; + } + }.make); + }; + } + + /// While an Edit's write is held: when nobody waits for it any more -- + /// flushed (an interrupted writer), its fid clunked, its connection + /// hung up -- the Edit is stopped, its commands killed, nothing + /// changed (edit_cmd.cancel). The engine tells the backend nothing of a + /// flush, so it is looked for, every 100 ms; a park out for a retry is + /// not waiting either, so only three looks in a row with no write being + /// served count. + fn watchEdit(l: *Listener, conn: *Runner.Conn, ticket: cloud9.fs.Ticket) void { + const io = pardes.turn.io orelse return; + var misses: u8 = 0; + while (!l.stopping.load(.acquire)) { + io.sleep(.fromMilliseconds(100), .awake) catch return; + _ = pardes.turn.take(); + defer pardes.turn.give(); + const core = l.core; + const h = core.fs.edit_hold orelse return; + if (h.slot != ticket.slot or h.seq != ticket.seq or h.asker != @as(*anyopaque, conn)) return; + if (conn.waiting(ticket) or l.writes_out.load(.acquire) != 0) { + misses = 0; + continue; + } + misses += 1; + if (misses < 3) continue; + if (h.done) core.fs.edit_hold = null else @import("edit_cmd.zig").cancel(core); + l.kick(); + return; + } } /// A held read asked again, to make its answer while its park waits. |
