-
-
Notifications
You must be signed in to change notification settings - Fork 804
fix(websocket): apply WebSocketStream receive backpressure #5562
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,7 +47,7 @@ class WebSocketStream { | |
| onConnectionEstablished: (response, extensions) => this.#onConnectionEstablished(response, extensions), | ||
| onMessage: (opcode, data) => this.#onMessage(opcode, data), | ||
| onParserError: (err) => failWebsocketConnection(this.#handler, null, err.message), | ||
| onParserDrain: () => this.#handler.socket.resume(), | ||
| onParserDrain: () => this.#resumeSocketIfNeeded(), | ||
| onSocketData: (chunk) => { | ||
| if (!this.#parser.write(chunk)) { | ||
| this.#handler.socket.pause() | ||
|
|
@@ -117,7 +117,7 @@ class WebSocketStream { | |
| this.#closedPromise = Promise.withResolvers() | ||
|
|
||
| // 7. Apply backpressure to the WebSocket. | ||
| // TODO | ||
| // This is done when the socket becomes available. | ||
|
|
||
| // 8. If options [" signal "] exists , | ||
| if (options.signal != null) { | ||
|
|
@@ -195,6 +195,22 @@ class WebSocketStream { | |
|
|
||
| // 3. Close the WebSocket with this , code , and reason . | ||
| closeWebSocketConnection(this.#handler, code, reason, true) | ||
| this.#resumeSocketIfNeeded() | ||
| } | ||
|
|
||
| #resumeSocketIfNeeded () { | ||
| const socket = this.#handler.socket | ||
|
|
||
| if (socket == null) { | ||
| return | ||
| } | ||
|
|
||
| if ( | ||
| this.#handler.readyState === states.CLOSING || | ||
| (this.#readableStreamController?.desiredSize > 0 && !this.#parser.writableNeedDrain) | ||
| ) { | ||
| socket.resume() | ||
| } | ||
| } | ||
|
|
||
| #write (chunk) { | ||
|
|
@@ -257,6 +273,7 @@ class WebSocketStream { | |
| /** @type {import('../websocket').Handler['onConnectionEstablished']} */ | ||
| #onConnectionEstablished (response, parsedExtensions) { | ||
| this.#handler.socket = response.socket | ||
| this.#handler.socket.pause() | ||
|
|
||
| // Get options from dispatcher options | ||
| const maxFragments = this.#handler.controller.dispatcher?.webSocketOptions?.maxFragments | ||
|
|
@@ -291,6 +308,7 @@ class WebSocketStream { | |
| start: (controller) => { | ||
| this.#readableStreamController = controller | ||
| }, | ||
| pull: () => this.#resumeSocketIfNeeded(), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. " // 5. Let pullAlgorithm be an action that pulls bytes from stream ." #resumeSocketIfNeeded does not do that
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. doesn't it make sense to handle backpressure in the WHATWG stream instead of the node socket? This implementation doesn't look correct to me |
||
| cancel: (reason) => this.#cancel(reason) | ||
| }) | ||
|
|
||
|
|
@@ -301,7 +319,10 @@ class WebSocketStream { | |
| // 13. Set up writable with writeAlgorithm , closeAlgorithm , and abortAlgorithm . | ||
| const writable = new WritableStream({ | ||
| write: (chunk) => this.#write(chunk), | ||
| close: () => closeWebSocketConnection(this.#handler, null, null), | ||
| close: () => { | ||
| closeWebSocketConnection(this.#handler, null, null) | ||
| this.#resumeSocketIfNeeded() | ||
| }, | ||
| abort: (reason) => this.#closeUsingReason(reason) | ||
| }) | ||
|
|
||
|
|
@@ -350,6 +371,9 @@ class WebSocketStream { | |
| this.#readableStreamController.enqueue(chunk) | ||
|
|
||
| // 4. Apply backpressure to the WebSocket. | ||
| if (this.#readableStreamController.desiredSize <= 0) { | ||
| this.#handler.socket.pause() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto wrt node socket vs whatwg stream. |
||
| } | ||
| } | ||
|
|
||
| /** @type {import('../websocket').Handler['onSocketClose']} */ | ||
|
|
@@ -441,6 +465,7 @@ class WebSocketStream { | |
| // 4. Close the WebSocket with stream , code , and reasonString . If this throws an exception, | ||
| // discard code and reasonString and close the WebSocket with stream . | ||
| closeWebSocketConnection(this.#handler, code, reasonString) | ||
| this.#resumeSocketIfNeeded() | ||
| } | ||
|
|
||
| // To cancel a WebSocketStream stream given reason , close using reason giving stream and reason . | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong and could possibly lead to dropped messages.
new WebSocketStream(...)any messages between the connection opening and reading messages will get "dropped" (stream will pull them even if the user isn't)