summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/9p_io.zig8
-rw-r--r--src/main.zig24
-rw-r--r--test/fs.py23
3 files changed, 36 insertions, 19 deletions
diff --git a/src/9p_io.zig b/src/9p_io.zig
index 6cf75c99..d80d7b30 100644
--- a/src/9p_io.zig
+++ b/src/9p_io.zig
@@ -1937,6 +1937,8 @@ test "9P shell environment states being inside pardes apart from how to reach it
pub const Client = struct {
pub const budget_ms: i64 = 2000;
+ /// Each write of a long one has this long to be answered (transact).
+ pub const write_budget_ms: i64 = 30_000;
pub const max_depth: usize = 2 * ninep.max_welem;
@@ -2211,6 +2213,12 @@ pub const Client = struct {
const count = (try s.ask(.{ .write = .{ .fid = cur, .offset = written, .data = chunk } }, remote)).write;
if (count == 0 or count > chunk.len) return Error.Botch;
written += count;
+ // A long write is timed by its progress: each answer gives
+ // the next its own budget, so a large body (`pardes -`) goes
+ // in through one open, whole, however long it takes. The
+ // budget is a write's: a server putting in tens of MiB it
+ // held (pane.zig batchFor) answers that one write late.
+ s.deadline = nowMs() +| write_budget_ms;
}
return;
}
diff --git a/src/main.zig b/src/main.zig
index 6627bece..1075b596 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -101,8 +101,8 @@ const help_text =
;
/// `pardes -` inside a session: `text` into the directory's one +Pager
-/// (root ctl `pager <dir>`, refilled when it is there), streamed into its
-/// body a bounded chunk at a time as one undo step, and left clean. A text
+/// (root ctl `pager <dir>`, refilled when it is there), written into its
+/// body through one open, as one undo step, and left clean. A text
/// the session would not take is never lost: it goes to stderr, with why,
/// and the exit is 1.
fn pageInto(io: std.Io, gpa: std.mem.Allocator, dial: []const u8, text: []const u8) void {
@@ -129,25 +129,11 @@ fn pageInto(io: std.Io, gpa: std.mem.Allocator, dial: []const u8, text: []const
ninep_io.Client.write(gpa, dial, "/ctl", ctl_line) catch |err| Fail.with(io, text, "no +Pager pane", err);
const answer = ninep_io.Client.read(gpa, dial, "/exec", "/exec") catch |err| Fail.with(io, text, "no +Pager pane", err);
const serial = std.fmt.parseInt(u32, std.mem.trim(u8, answer, " \n"), 10) catch |err| Fail.with(io, text, "no +Pager pane", err);
- var path_buf: [64]u8 = undefined;
- const mark = std.fmt.bufPrint(&path_buf, "/pane/{d}/mark", .{serial}) catch unreachable;
- // One undo step for the whole text, however many writes carry it.
- ninep_io.Client.write(gpa, dial, mark, "0\n") catch |err| Fail.with(io, text, "the +Pager pane went", err);
+ // The whole text through one open of the body (truncated once, then
+ // written at rising offsets): one undo step, and every byte of it.
var body_buf: [64]u8 = undefined;
const body = std.fmt.bufPrint(&body_buf, "/pane/{d}/body", .{serial}) catch unreachable;
- // A chunk ends on a line's end (never inside a character), each its own
- // write with its own deadline, so a large text arrives whole.
- const chunk_max = 8 * 1024 * 1024;
- var at: usize = 0;
- while (at < text.len) {
- var end = @min(text.len, at + chunk_max);
- if (end < text.len) if (std.mem.lastIndexOfScalar(u8, text[at..end], '\n')) |nl| {
- end = at + nl + 1;
- };
- ninep_io.Client.write(gpa, dial, body, text[at..end]) catch |err| Fail.with(io, text[at..], "the +Pager pane would not take it", err);
- at = end;
- }
- ninep_io.Client.write(gpa, dial, mark, "1\n") catch {};
+ ninep_io.Client.write(gpa, dial, body, text) catch |err| Fail.with(io, text, "the +Pager pane would not take it", err);
var ctl_buf: [64]u8 = undefined;
ninep_io.Client.write(gpa, dial, std.fmt.bufPrint(&ctl_buf, "/pane/{d}/ctl", .{serial}) catch unreachable, "clean\n") catch {};
}
diff --git a/test/fs.py b/test/fs.py
index b2f52c3e..789021bd 100644
--- a/test/fs.py
+++ b/test/fs.py
@@ -1467,6 +1467,29 @@ def test(binary, quic=False):
assert serials() - before == {made_now}, (serials(), before)
assert client.read(f'/pane/{made_now}/body') == b'again\nprogress 100%\n'
client.remove(f'/pane/{made_now}')
+ # More than one 8 MiB write's worth arrives whole, byte for byte,
+ # in the one +Pager (each write once replaced the last).
+ big = b''.join(b'line %08d of the paged text\n' % i for i in range(400000)) # ~11 MiB
+ assert len(big) > 8 * 1024 * 1024
+ fed = subprocess.run([binary, '-'], cwd=root, env=env, input=big,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=120)
+ assert fed.returncode == 0, fed.stderr
+ big_pane, = serials() - before
+ assert client.read(f'/pane/{big_pane}/body') == big, 'the paged body is not the text'
+ # Past the 256 MiB cap: the first 256 MiB, then the cut note.
+ cap = 256 * 1024 * 1024
+ huge = subprocess.Popen(['sh', '-c', f'yes 0123456789abcdef | head -c {cap + 4096}'], stdout=subprocess.PIPE)
+ fed = subprocess.run([binary, '-'], cwd=root, env=env, stdin=huge.stdout,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=600)
+ huge.wait()
+ assert fed.returncode == 0, fed.stderr
+ assert serials() - before == {big_pane}
+ got = client.read(f'/pane/{big_pane}/body')
+ note = b'\n[pardes: the rest of stdin is cut: a pane holds at most a file\'s limit]\n'
+ assert got.endswith(note) and len(got) == cap + len(note), (len(got), got[-120:])
+ assert got[:cap] == (b'0123456789abcdef\n' * (cap // 17 + 1))[:cap]
+ del got, big
+ client.remove(f'/pane/{big_pane}')
# From a directory whose name could not be a ctl line: the
# session's directory's +Pager, the text whole, no stray +New.
odd = root / 'two\nlines'