Node's writable streams (files, sockets, HTTP responses) don't let a fast
producer overwhelm a slow destination -- every writable stream tracks how
much data is sitting in its internal buffer, and once that crosses a
configured highWaterMark, write() starts returning false as a signal
to the producer: "you can keep calling me, but you should really pause."
Once the destination catches up and the buffer drops back under the
threshold, the stream emits a 'drain' event telling the producer it's
safe to resume.
This exercise models that mechanism with no real I/O at all -- write()
and consume() are explicit calls standing in for "more data arrived" and
"the destination finished flushing the oldest queued chunk."
Implement:
WritableStream.write(chunk_size)-- appendchunk_sizeto the internal queue and add it toself.buffered. ReturnFalseonceself.bufferedhas reachedself.high_water_mark(and remember, viaself._needs_drain, that a drain is now owed); otherwise returnTrue.WritableStream.consume()-- pop the OLDEST queued chunk (FIFO -- data finishes flushing in the order it was written) and subtract its size fromself.buffered. If a drain was owed (self._needs_drain) andself.bufferedhas now dropped belowself.high_water_mark, clear_needs_drain, append the newself.bufferedvalue toself.drain_events, and returnTrue. Otherwise returnFalse.
Example
stream = WritableStream(high_water_mark=100)
stream.write(60) # -> True (buffered=60, still under 100)
stream.write(50) # -> False (buffered=110, at/over 100 -- pause!)
stream.consume() # buffered=50, dropped below 100 -- 'drain' fires
stream.drain_events # -> [50]
Constraints
consume()only has something to do when the queue is non-empty -- calling it on an empty stream should just returnFalse.- A drain event fires at most once per "crossing" -- consuming further
chunks while already under
high_water_markmust NOT append additional entries todrain_eventsuntil anotherwrite()pushes the buffer back over the threshold.
Real-world grounding
- Node's writable stream internals --
write()'shighWaterMarkcheck and the'drain'event:lib/internal/streams/writable.js
The two tests at the end of this exercise are the actual point: a producer
that ignores write()'s False return and keeps writing anyway drives the
buffer to unbounded size (nothing is holding it back), while a producer
that pauses on False and waits for a 'drain'-equivalent signal before
resuming keeps the buffer bounded near high_water_mark no matter how much
data is waiting to go out. That's the entire reason backpressure exists.