bufio: bclose flushes only, does not close the underlying (Hare flag::NONE)

This commit is contained in:
2026-06-15 02:09:59 +09:00
parent 9c10a731b0
commit a8b43b8f4b
2 changed files with 73 additions and 14 deletions

View File

@@ -46,8 +46,10 @@
// singletons (vtable_r / vtable_w / vtable_rw); ww's vtable always
// carries all three callbacks (a zero-length rbuf/wbuf degenerates the
// matching callback in-cb). Defer-handle (MANAGED_* ownership bits)
// also deferred — caller owns rbuf/wbuf/src; bclose flushes + forwards
// close but frees nothing. Both graduate with io fold-2 (#5).
// also deferred — caller owns rbuf/wbuf/src; bclose flushes only and
// neither frees nor closes (close-propagation rides #5
// MANAGED_HANDLE, ref/hare/bufio/stream.ha:194). Both graduate with io
// fold-2 (#5).
package bufio;
@@ -292,20 +294,15 @@ fn bwrite(s: io.stream, buf: []u8) (size | io.error) = {
return buf.len: size;
};
// bclose — flush pending wbuf, forward close to src via io.close.
// Caller-owned buffers/src are not freed (drew defer-handle deferral).
// bclose — flush pending wbuf only; the underlying src is NOT closed.
// Hare's close_buffered closes src solely under flag::MANAGED_HANDLE
// (ref/hare/bufio/stream.ha:194); init's default flag::NONE
// (stream.ha:73) flushes and leaves src to its owner. Close-propagation
// rides the io fold-2 MANAGED_HANDLE machinery (#5), per the ownership
// header (bufio.ww:46-50) — caller owns src, bclose frees/closes nothing.
fn bclose(s: io.stream) (void | io.error) = {
let b: *stream = s: *stream;
let fr: (void | io.error) = flush(b);
match (fr) {
case void => { };
case let e: io.error => return e;
};
let cr: (void | io.error) = io.close(b.src);
match (cr) {
case void => return void;
case let e: io.error => return e;
};
return flush(b);
};
// ---- scanner (read-ahead tokenizer over an io.stream src) ----------------

View File

@@ -43,6 +43,27 @@ fn errsource() io.stream = {
return &errvt;
};
// ---- a close-recording source for the no-propagate test ---------------
//
// closevt forwards writes to closesink (so a flush is observable) and
// records whether its closer fired in srcclosed.
let srcclosed: bool = false;
let closevt: io.vtable;
let closesink: memio.stream;
fn closewrite(s: io.stream, buf: []u8) (size | io.error) = {
return io.write(&closesink.vt, buf);
};
fn closeclose(s: io.stream) (void | io.error) = {
srcclosed = true;
return void;
};
fn closesource() io.stream = {
closevt.writer = (&closewrite): *io.writer;
closevt.closer = (&closeclose): *io.closer;
return &closevt;
};
// ---- scanbyte: drain four bytes through a 2-byte window ---------------
@test fn scanbytecases() void = {
@@ -732,6 +753,47 @@ fn errsource() io.stream = {
assert(!(raw[1] != 81u8));
};
// ---- bclose flushes but does NOT close the underlying ----------------
//
// Hare's close_buffered closes src only under flag::MANAGED_HANDLE
// (ref/hare/bufio/stream.ha:194); init's default flag::NONE flushes
// only. The close-recording closesource lets us assert both: the
// buffered byte reaches the sink (flush) AND the closer never fires.
@test fn streamclosenopropagate() void = {
srcclosed = false;
let raw: [8]u8;
closesink = memio.fixed(raw[0:8]);
let src: io.stream = closesource();
let rb: [4]u8;
let wb: [4]u8;
let b: bufio.stream = bufio.init(src, rb[0:4], wb[0:4]);
let nilbs: [1]u8;
bufio.setflush(&b, nilbs[0:0]); // suppress the default '\n' detector
let p: io.stream = &b.vt;
let h: [2]u8;
h[0] = 80u8; h[1] = 81u8; // "PQ"
let r: (size | io.error) = io.write(p, h[0:2]);
match (r) {
case let n: size => { assert(!(n: i32 != 2)); };
case let e: io.error => abort();
};
assert(!(closesink.pos != 0)); // still buffered
let cr: (void | io.error) = io.close(p);
match (cr) {
case void => { };
case let e: io.error => abort();
};
// Flush happened: the 2 buffered bytes reached the sink.
assert(!(closesink.pos != 2));
assert(!(raw[0] != 80u8));
assert(!(raw[1] != 81u8));
// But the underlying was NOT closed (Hare flag::NONE default).
assert(!srcclosed);
};
// ---- setflush with a custom non-empty byte-set -----------------------
@test fn streamsetflushcustom() void = {