diff --git a/index.bs b/index.bs index 533461d..53f044f 100644 --- a/index.bs +++ b/index.bs @@ -69,6 +69,8 @@ spec:fetch; type:dfn; for:/; text:fetch spec:url; type:dfn; text:scheme spec:url; type:dfn; text:fragment spec:infra; type:dfn; for:/; text:ASCII case-insensitive +spec:infra; type:dfn; text:list +spec:streams; type:dict-member; text:write
 url: https://html.spec.whatwg.org/multipage/origin.html#concept-origin; type: dfn; text: origin; for:/
@@ -1426,6 +1428,7 @@ data to the server.
 interface WebTransportSendStream : WritableStream {
   attribute long long? sendOrder;
   Promise<WebTransportSendStreamStats> getStats();
+  WebTransportWriter getWriter();
 };
 
@@ -1461,6 +1464,12 @@ The {{WebTransportSendStream}}'s [=transfer steps=] and 1. [=Resolve=] |p| with |stats|. 1. Return |p|. +: getWriter() +:: This method must be implemented the same as {{WritableStream/getWriter}} + inherited from {{WritableStream}}, except in place of creating a + {{WritableStreamDefaultWriter}}, it must instead + [=WebTransportWriter/create=] a {{WebTransportWriter}} with [=this=]. + ## Internal Slots ## {#send-stream-internal-slots} A {{WebTransportSendStream}} has the following internal slots. @@ -1490,6 +1499,11 @@ A {{WebTransportSendStream}} has the following internal slots. `[[SendOrder]]` An optional send order number, or null. + + `[[AtomicWriteRequests]]` + A [=list=] of promises, each representing an in-flight atomic + write request to be processed by the underlying sink. + @@ -1510,6 +1524,8 @@ To create a :: |transport| : {{WebTransportSendStream/[[SendOrder]]}} :: |sendOrder| + : {{WebTransportSendStream/[[AtomicWriteRequests]]}} + :: An empty [=list=] of promises. 1. Let |writeAlgorithm| be an action that [=writes=] |chunk| to |stream|, given |chunk|. 1. Let |closeAlgorithm| be an action that [=closes=] |stream|. 1. Let |abortAlgorithm| be an action that [=aborts=] |stream| with |reason|, given |reason|. @@ -1537,6 +1553,14 @@ To write |chunk| to a {{WebTransportSend 1. Let |promise| be a new promise. 1. Let |bytes| be a copy of the [=byte sequence=] which |chunk| represents. 1. Set |stream|.{{[[PendingOperation]]}} to |promise|. +1. Let |inFlightWriteRequest| be + |stream|.inFlightWriteRequest. + + Note: Find a [better way](https://streams.spec.whatwg.org/#other-specs) to integrate + with the streams spec to identify the write request in flight. + +1. Let |atomic| be true if |inFlightWriteRequest| [=list/exists=] + in [=stream=].{{WebTransportSendStream/[[AtomicWriteRequests]]}}, otherwise false. 1. Run the following steps [=in parallel=]: 1. [=stream/Send=] |bytes| on |stream|.{{WebTransportSendStream/[[InternalStream]]}} and wait for the operation to complete. @@ -1552,6 +1576,12 @@ To write |chunk| to a {{WebTransportSend [=WritableStream/Error | errored=] nor blocked by [=flow control=], have been sent. + Whenever the sending of |bytes| becomes blocked by [=flow control=], + [=queue a network task=] with |transport| to [=abort all atomic write requests=] on |stream|. + + If the sending of |bytes| becomes blocked by [=flow control=] and |atomic| is true, + then give up on sending |bytes| and proceed immediately to the next step without failing. + The user agent SHOULD divide bandwidth fairly between all streams that aren't starved. Note: The definition of fairness here is [=implementation-defined=]. @@ -1567,13 +1597,15 @@ To write |chunk| to a {{WebTransportSend 1. [=Queue a network task=] with |transport| to run these steps: 1. Set |stream|.{{[[PendingOperation]]}} to null. + 1. If |atomic| is true, [=map/remove=] |inFlightWriteRequest| from + |stream|.{{WebTransportSendStream/[[AtomicWriteRequests]]}}. 1. [=Resolve=] |promise| with undefined. 1. Return |promise|. Note: The user-agent MAY have a buffer to improve the transfer performance. Such a buffer SHOULD have a fixed upper limit, to carry the backpressure information to the user of {{WebTransportSendStream}}. This also means the [=fulfilled|fulfillment=] of the promise returned from this algorithm (or, -{{WritableStreamDefaultWriter/write|WritableStreamDefaultWriter.write}}) does **NOT** necessarily mean that the chunk is acked by +{{WritableStreamDefaultWriter/write(chunk)}}) does **NOT** necessarily mean that the chunk is acked by the server [[!QUIC]]. It may just mean that the chunk is appended to the buffer. To make sure that the chunk arrives at the server, use an application-level protocol. @@ -1619,6 +1651,16 @@ To abort a {{WebTransportSendStream}} |s +
+To abort all atomic write requests on a {{WebTransportSendStream}} |stream|, run these steps: + 1. Let |requestsToAbort| be [=stream=].{{WebTransportSendStream/[[AtomicWriteRequests]]}}. + 1. [=map/Clear=] [=stream=].{{WebTransportSendStream/[[AtomicWriteRequests]]}}. + 1. [=For each=] |promise| in |requestsToAbort|, [=reject=] |promise| with {{TransactionInactiveError}}. + 1. [=In parallel=], [=for each=] |promise| in |requestsToAbort|, abort the sending of data + associated with |promise|. + +
+ ## STOP_SENDING signal coming from the server ## {#send-stream-STOP_SENDING}
@@ -1970,6 +2012,72 @@ object |transport|, and a |sendOrder|, run these steps.
+# `WebTransportWriter` Interface # {#web-transport-writer-procedures-interface} + +{{WebTransportWriter}} is a subclass of {{WritableStreamDefaultWriter}} that +overloads one method and adds another. + +A {{WebTransportWriter}} is always created by the +[=WebTransportWriter/create=] procedure. + +
+[Exposed=*, SecureContext]
+interface WebTransportWriter : WritableStreamDefaultWriter {
+  Promise<undefined> write(optional any chunk);
+  Promise<undefined> atomicWrite(optional any chunk);
+};
+
+ +## Methods ## {#web-transport-writer-procedures-methods} + +: write(chunk) +:: The {{write}} method will not reject on [=flow control=] blockage + (unless queued behind one or more outstanding calls to {{atomicWrite}}). + This behavior is designed to satisfy most applications. + + When called, run the following steps: + 1. Let |stream| be the {{WebTransportSendStream}} associated with [=this=]. + 1. If |stream|.{{WebTransportSendStream/[[AtomicWriteRequests]]}} is not empty, + return the result of [=writing atomically=] on |stream| with |chunk|. + 1. Return the result of {{WritableStreamDefaultWriter/write(chunk)}} + on {{WritableStreamDefaultWriter}} with |chunk|. + +: atomicWrite(chunk) +:: The {{atomicWrite}} method will reject if the chunk given to it + cannot be sent in its entirety without blocking on [=flow control=]. + This behavior is designed to satisfy niche transactional applications + sensitive to [=flow control=] deadlocks ([[RFC9308]] + [Section 4.4](https://datatracker.ietf.org/doc/html/rfc9308#section-4.4)). + + When called, return the result of [=writing atomically=] on the + {{WebTransportSendStream}} associated with [=this=], with |chunk|. + +## Procedures ## {#web-transport-writer-procedures-procedures} + +
+ +To create a +{{WebTransportWriter}}, with a {{WebTransportSendStream}} |stream|, run these +steps: +1. Let |writer| be a [=new=] {{WebTransportWriter}}. +1. Run the [new WritableStreamDefaultWriter(stream)](https://streams.spec.whatwg.org/#default-writer-constructor) + constructor steps passing |writer| as this, and |stream| as the constructor argument. +1. Return |writer|. + +
+ +
+ +To write atomically +on a {{WebTransportSendStream}} |stream|, given |chunk|, run these steps: +1. Let |p| be the result of {{WritableStreamDefaultWriter/write(chunk)}} + on {{WritableStreamDefaultWriter}} with |chunk|. +1. Set |stream|.{{WebTransportSendStream/[[AtomicWriteRequests]]}} to |p|. +1. Return |p|. + +
+ + # `WebTransportError` Interface # {#web-transport-error-interface} WebTransportError is a subclass of {{DOMException}} that represents @@ -2093,7 +2201,7 @@ converted to an httpErrorCode, and vice versa, as specified in [[!WEB-TRANSPORT- [=stream/Send|sends=] STREAM with FIN bit set - {{WebTransportBidirectionalStream/writable}}.getWriter().{{WritableStreamDefaultWriter/write}}() + {{WebTransportBidirectionalStream/writable}}.getWriter().{{WritableStreamDefaultWriter/write(chunk)}}() [=stream/Send|sends=] STREAM @@ -2535,6 +2643,31 @@ async function receiveText(url, createWritableStreamForTextData) { } +## Sending a transactional chunk on a stream ## {#example-transactional-stream} + +*This section is non-normative.* + +Sending a transactional piece of data on a one-way stream only if it can be done +entirely without blocking on [=flow control=], can be achieved by using the +{{WebTransportSendStream/getWriter}} function and the resulting writer. + +
+async function sendTransactionalData(wt, bytes) {
+  const writable = await wt.createUnidirectionalStream();
+  const writer = writable.getWriter();
+  await writer.ready;
+  try {
+    await writer.atomicWrite(bytes);
+  } catch (e) {
+    if (e.name != "TransactionInactiveError") throw e;
+    // rejected to avoid blocking on flow control
+    // The writable remains un-errored unlike with regular writes
+  } finally {
+    writer.releaseLock();
+  }
+}
+
+ ## Complete example ## {#example-complete} *This section is non-normative.*