summaryrefslogtreecommitdiff
path: root/src/selection_pipe.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-02 00:50:41 -0300
committerGabriel Schneider <[email protected]>2026-10-02 01:43:04 -0300
commit97c939b981bc7fbf1a58511a9d83d9f1e12a8f16 (patch)
tree168f1c1cb7005d4b684be312a36be205e642f7df /src/selection_pipe.zig
parentd65ec3394ff619118d9208718477469c673a5598 (diff)
downloadpardes-97c939b981bc7fbf1a58511a9d83d9f1e12a8f16.tar.gz
pardes-97c939b981bc7fbf1a58511a9d83d9f1e12a8f16.zip
An Edit filter that can never be reaped no longer holds the Edit's answer: at the limit the command's group is killed and reaped off the answer's path, the stdin writer owning its input, so a filter writing its own ctl with > or >> answers EIO at 10 s instead of hanging; a running Edit keeps its own copies of the names it reports, and the panes it changes refuse Del, delete, rmdir, Undo, Redo, Get and Zerox (busy, EBUSY); e and r say unreadable files in Get's words; the reference says both sides of a filter's own ctl write need <>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/selection_pipe.zig')
-rw-r--r--src/selection_pipe.zig74
1 files changed, 60 insertions, 14 deletions
diff --git a/src/selection_pipe.zig b/src/selection_pipe.zig
index 66444b04..c3e5e3a6 100644
--- a/src/selection_pipe.zig
+++ b/src/selection_pipe.zig
@@ -214,17 +214,53 @@ pub const Tasks = struct {
}
};
+/// The stdin writer's own: its copy of the input, freed by the writer when
+/// it is done, so a writer stuck on a command that never reads (and cannot
+/// be killed, below) can be let go of without its input dying under it.
const WriterContext = struct {
+ gpa: std.mem.Allocator,
io: std.Io,
file: std.Io.File,
- input: []const u8,
- ok: bool = false,
+ input: []u8,
};
fn writeInput(context: *WriterContext) void {
- defer context.file.close(context.io);
+ defer {
+ context.file.close(context.io);
+ context.gpa.free(context.input);
+ context.gpa.destroy(context);
+ }
context.file.writeStreamingAll(context.io, context.input) catch return;
- context.ok = true;
+}
+
+/// Kills the command's group, then `abandon`s it.
+fn abandonNow(io: std.Io, child: *std.process.Child, pid: std.posix.pid_t) void {
+ // Before the shell is reaped, while its group id cannot be another's.
+ std.posix.kill(-pid, .KILL) catch {};
+ std.posix.kill(pid, .KILL) catch {};
+ abandon(io, child.*);
+ child.id = null;
+ child.stdout = null;
+ child.stderr = null;
+}
+
+/// A command given up on, killed and reaped where nothing waits for it: a
+/// process in uninterruptible sleep (a write to a file of this very
+/// session, through its mount, waiting on the request whose answer is
+/// waiting on this command) dies only when that request is answered, and
+/// the answer must not wait on its death.
+fn abandon(io: std.Io, child: std.process.Child) void {
+ const Reap = struct {
+ fn run(i: std.Io, c: std.process.Child) void {
+ var reaped = c;
+ reaped.kill(i);
+ }
+ };
+ const thread = std.Thread.spawn(.{}, Reap.run, .{ io, child }) catch {
+ var c = child;
+ return c.kill(io);
+ };
+ thread.detach();
}
/// Run one POSIX shell command with exact stdin, concurrently draining stdout
@@ -350,23 +386,33 @@ pub fn runIn(
}) catch return fail.k(.spawn);
const pid = child.id.?;
- var writer_context: WriterContext = .{
+ const writer_context = gpa.create(WriterContext) catch {
+ abandonNow(io, &child, pid);
+ return fail.k(.spawn);
+ };
+ writer_context.* = .{
+ .gpa = gpa,
.io = io,
.file = child.stdin.?,
- .input = input,
+ .input = gpa.dupe(u8, input) catch {
+ gpa.destroy(writer_context);
+ abandonNow(io, &child, pid);
+ return fail.k(.spawn);
+ },
};
child.stdin = null; // writer_context owns and closes this endpoint
- var writer: ?std.Thread = std.Thread.spawn(.{}, writeInput, .{&writer_context}) catch {
+ var writer: ?std.Thread = std.Thread.spawn(.{}, writeInput, .{writer_context}) catch {
writer_context.file.close(io);
- child.kill(io);
+ gpa.free(writer_context.input);
+ gpa.destroy(writer_context);
+ abandonNow(io, &child, pid);
return fail.k(.spawn);
};
- // On every early return kill first, unblocking a command which never read
- // stdin, then join the short-lived writer before its borrowed input dies.
- defer if (writer) |thread| thread.join();
- defer child.kill(io);
- // Before the shell is reaped, while its group id cannot be another's.
- defer if (child.id != null) std.posix.kill(-pid, .KILL) catch {};
+ // On every early return (a timeout, a ceiling) the answer goes at once:
+ // the group is killed and the shell left to be reaped elsewhere
+ // (abandon), and the writer, which owns its input, let go.
+ defer if (writer) |thread| thread.detach();
+ defer if (child.id != null) abandonNow(io, &child, pid);
defer if (token != 0) Running.remove(pid);
if (token != 0 and !Running.add(token, pid)) return fail.k(.signal);