diff --git a/lib/bufio/bufio.ww b/lib/bufio/bufio.ww index a0e4c9e1..5b55ac6a 100644 --- a/lib/bufio/bufio.ww +++ b/lib/bufio/bufio.ww @@ -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) ---------------- diff --git a/lib/bufio/bufiotest.ww b/lib/bufio/bufiotest.ww index db1d6857..16ad6ed1 100644 --- a/lib/bufio/bufiotest.ww +++ b/lib/bufio/bufiotest.ww @@ -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 = {