is the data you try to write really "rejected" when the #write method returns false ? Or is it buffered (or something else) ?
The data is buffered. However, excessive calls to write()
without allowing the buffer to drain will cause high memory usage, poor garbage collector performance, and could even cause Node.js to crash with an Allocation failed - JavaScript heap out of memory
error. See this related question:
Node: fs write() doesn't write inside loop. Why not?
For reference, here are some relevant details on highWaterMark
and backpressure from the current docs (v8.4.0):
The return value is true
if the internal buffer is less than the highWaterMark
configured when the stream was created after admitting chunk
. If false
is returned, further attempts to write data to the stream should stop until the 'drain'
event is emitted.
While a stream is not draining, calls to write()
will buffer chunk
, and return false
. Once all currently buffered chunks are drained (accepted for delivery by the operating system), the 'drain'
event will be emitted. It is recommended that once write()
returns false
, no more chunks be written until the 'drain'
event is emitted. While calling write()
on a stream that is not draining is allowed, Node.js will buffer all written chunks until maximum memory usage occurs, at which point it will abort unconditionally. Even before it aborts, high memory usage will cause poor garbage collector performance and high RSS (which is not typically released back to the system, even after the memory is no longer required).
In any scenario where the data buffer has exceeded the highWaterMark
or the write queue is currently busy, .write()
will return false
.
When a false
value is returned, the backpressure system kicks in. It will pause the incoming Readable
stream from sending any data and wait until the consumer is ready again. Once the data buffer is emptied, a .drain()
event will be emitted and resume the incoming data flow.
Once the queue is finished, backpressure will allow data to be sent again. The space in memory that was being used will free itself up and prepare for the next batch of data.
+-------------------+ +=================+
| Writable Stream +---------> .write(chunk) |
+-------------------+ +=======+=========+
|
+------------------v---------+
+-> if (!chunk) | Is this chunk too big? |
| emit .end(); | Is the queue busy? |
+-> else +-------+----------------+---+
| emit .write(); | |
^ +--v---+ +---v---+
^-----------------------------------< No | | Yes |
+------+ +---v---+
|
emit .pause(); +=================+ |
^-----------------------+ return false; <-----+---+
+=================+ |
|
when queue is empty +============+ |
^-----------------------< Buffering | |
| |============| |
+> emit .drain(); | ^Buffer^ | |
+> emit .resume(); +------------+ |
| ^Buffer^ | |
+------------+ add chunk to queue |
| <---^---------------------<
+============+