Skip to content

Commit 40c60c8

Browse files
committed
Clarify strict/block backpressure handling
1 parent 7e7c35c commit 40c60c8

1 file changed

Lines changed: 16 additions & 7 deletions

File tree

index.bs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,13 @@ Backpressure {#concept-backpressure}
142142

143143
Backpressure is the mechanism by which a consumer signals to a producer that it should slow down. In this API, backpressure is managed through [=backpressure policies=]:
144144

145-
* **`"strict"`** (default): Catches producers that ignore backpressure. Properly awaited writes wait for buffer space; un-awaited writes exceeding the buffer limit cause a rejection. Both the slots buffer and pending writes queue are limited by `highWaterMark`.
145+
* **`"strict"`** (default): Catches producers that ignore backpressure. Properly awaited asynchronous writes wait for buffer space; un-awaited writes exceeding the buffer limit cause a rejection. Both the slots buffer and pending writes queue are limited to `highWaterMark` entries. Synchronous writes (`writeSync`, `writevSync`) only attempt the slots buffer; if it is full, they return `false` without placing data into the pending writes queue.
146146

147-
* **`"block"`**: Async writes wait for buffer space. Sync writes return `false` when the buffer is full. The pending writes queue is unbounded.
147+
* **`"block"`**: Asynchronous writes that cannot be placed in the slots buffer are placed into the pending writes queue, which is unbounded, and resolve when promoted to the slots buffer. Synchronous writes return `false` when the slots buffer is full — the data is not accepted. The caller should fall back to the asynchronous method, which handles the pending queue.
148148

149-
* **`"drop-oldest"`**: Oldest buffered data is discarded to make room for new data. Writes never block.
149+
* **`"drop-oldest"`**: Oldest buffered data is discarded to make room for new data. Writes never block or wait.
150150

151-
* **`"drop-newest"`**: Incoming data is silently discarded when buffer is full. Writes never block.
151+
* **`"drop-newest"`**: Incoming data is silently discarded when buffer is full. Writes never block or wait.
152152

153153
The `highWaterMark` option controls the buffer size in slots (chunk batches), not bytes. It is clamped to a minimum of 1. Implementations may also apply a reasonable upper limit; the specific maximum is implementation-defined, but must be at least 1024.
154154

@@ -367,7 +367,7 @@ Like {{Writer}}, {{SyncWriter}} is an interface, not a concrete class. Implement
367367

368368
{{SyncWriter}} follows the same [=backpressure policies=] as {{Writer}} with adaptations for synchronous operation:
369369

370-
* With `"block"`, {{SyncWriter/writeSync()}} and {{SyncWriter/writevSync()}} always enqueue the chunk(s) and return `true` when the buffer has space, or enqueue and return `false` when the buffer is full. The `false` return is a backpressure signal; the data is still accepted, but the caller should slow down.
370+
* With `"block"`, {{SyncWriter/writeSync()}} and {{SyncWriter/writevSync()}} enqueue the chunk(s) and return `true` when the buffer has space, or return `false` when the buffer is full. The data is not accepted; the caller should retry when capacity becomes available.
371371
* With `"strict"`, writes that exceed the buffer capacity throw a {{RangeError}}.
372372
* With `"drop-oldest"` and `"drop-newest"`, writes behave as described in [[#concept-backpressure]]: the oldest or newest data is discarded respectively. Writes never fail.
373373
* {{SyncWriter/endSync()}} throws a {{TypeError}} if the writer is already closed or errored.
@@ -519,7 +519,7 @@ When called, it performs the following steps:
519519
<li>Let |backpressure| be |options|["{{PushStreamOptions/backpressure}}"].
520520
<li>Let |signal| be |options|["{{PushStreamOptions/signal}}"] if present; otherwise `undefined`.
521521
<li>Create an internal slots buffer with capacity |highWaterMark|.
522-
<li>Create a pending writes queue. If |backpressure| is `"strict"`, limit its capacity to |highWaterMark|.
522+
<li>Create a pending writes queue. If |backpressure| is `"strict"`, limit its capacity to |highWaterMark|. The pending writes queue is used exclusively by the asynchronous write methods ({{Writer/write()}}, {{Writer/writev()}}); the synchronous methods ({{Writer/writeSync()}}, {{Writer/writevSync()}}) do not interact with it.
523523
<li>Let |writer| be a new {{Writer}} backed by the internal buffer, the pending writes queue, the [=backpressure policy=] |backpressure|, and |signal| if present.
524524
<li>Let |pipelineController| be a new {{AbortController}}. If |signal| is not `undefined`, set |pipelineController|'s signal to follow |signal|.
525525
<li>Let |bufferIterable| be an async iterable that dequeues the next batch from the slots buffer on each step (waiting if empty).
@@ -538,6 +538,15 @@ The Writer interface {#writer-section}
538538

539539
A {{Writer}} provides the interface for producing data. Implementations of {{Writer}} are returned by {{Stream/push()}}, {{Stream/broadcast()}}, and {{Stream/duplex()}}, but any object conforming to this interface can serve as a writer.
540540

541+
A {{Writer}} backed by a [=push stream=] maintains two internal queues:
542+
543+
* The **slots buffer**: a bounded queue of batches ready for the consumer to read. Its capacity is `highWaterMark` entries.
544+
* The **pending writes queue**: a queue of writes waiting for space in the slots buffer. Under `"strict"`, its capacity is also `highWaterMark` entries. Under `"block"`, it is unbounded. Under `"drop-oldest"` and `"drop-newest"`, it is not used (writes are resolved immediately).
545+
546+
The synchronous methods ({{Writer/writeSync()}}, {{Writer/writevSync()}}) operate exclusively on the slots buffer. If the slots buffer has space, the batch is placed directly into it and `true` is returned. If the slots buffer is full, the synchronous method returns `false` — it never places data into the pending writes queue.
547+
548+
The asynchronous methods ({{Writer/write()}}, {{Writer/writev()}}) first attempt to place the batch into the slots buffer. If the slots buffer is full, the write is placed into the pending writes queue (for `"strict"` and `"block"` policies). When the consumer reads and frees slots, pending writes are promoted from the pending queue into the slots buffer in order, and their promises resolve.
549+
541550
### `Writer.desiredSize` ### {#writer-desiredsize}
542551

543552
<div algorithm>
@@ -592,7 +601,7 @@ The <dfn method for="Writer">writeSync(chunk)</dfn> method attempts a synchronou
592601
<li>If `"drop-oldest"`: dequeue the oldest batch, enqueue |batch|, and return `true`.
593602
<li>If `"drop-newest"`: discard |batch| and return `true`.
594603
<li>If `"strict"`: return `false`.
595-
<li>If `"block"`: enqueue |batch| and return `false`. The data is accepted, but the `false` return signals backpressure.
604+
<li>If `"block"`: return `false`.
596605
</ol>
597606
</ol>
598607

0 commit comments

Comments
 (0)