summaryrefslogtreecommitdiff
path: root/src/9p_io.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 20:50:48 -0300
committerGabriel Schneider <[email protected]>2026-10-01 22:07:48 -0300
commit406f483288c7689bdd2e1f3f68b276c61ca9412a (patch)
treeb6398fb9974b91441563fd655d18ddaf182a2a5a /src/9p_io.zig
parentb55cb01d2777760ed79b545dfca7115837591577 (diff)
downloadpardes-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.zig73
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.