Conversation
|
Thank you for your contribution to Dash! 🎉 This PR is exempt from requiring a linked issue due to its labels. |
|
First, I want to say that this is a great step at the flexibility of Dash and reducing network load. I have some concerns with security of the app level websockets that are currently implemented:
|
| use_async: Optional[bool] = None, | ||
| health_endpoint: Optional[str] = None, | ||
| websocket_callbacks: Optional[bool] = False, | ||
| allowed_websocket_origins: Optional[List[str]] = None, |
There was a problem hiding this comment.
Will this be able to be set as an environment variable?
There was a problem hiding this comment.
Yes, this is intended for embedded support.
KoolADE85
left a comment
There was a problem hiding this comment.
This is an awesome feature, and it's working well overall! 🎉
I left a couple comments around scalability/performance that I think we should look into.
| response_data = ctx.run(partial_func) | ||
| if inspect.iscoroutine(response_data): | ||
| response_data = await response_data |
There was a problem hiding this comment.
This execution flow blocks the ws event loop. If the callback is synchronous and slow, all other callbacks are blocked. And if the callback takes longer than the heartbeat, the client will disconnect and lose all pending callbacks.
You can see this by adding a simple time.sleep(15) to one of the callbacks in wsapp.py
I know FastAPI supplies a run_in_threadpool which may help - or perhaps we could leverage our own background callback code?
| case WorkerMessageType.DISCONNECTED: | ||
| this.isConnected = false; | ||
| if (this.onDisconnected) { | ||
| this.onDisconnected(message.payload?.reason); | ||
| } | ||
| break; |
There was a problem hiding this comment.
When a disconnect happens, we should also cancel any pending callbacks. Currently, the loading state will stay on forever even if the socket reconnects.
| // Trigger reconnect if we have a server URL but aren't connected/connecting | ||
| if (this.serverUrl && !this.isConnecting) { | ||
| this.isConnecting = true; | ||
| this.createConnection(); | ||
| } |
There was a problem hiding this comment.
This reconnection path should reset the retryCount before creating the connection.
| if self.backend.websocket_capability: | ||
| self.backend.serve_websocket_callback(self) |
There was a problem hiding this comment.
This silently no-ops on Flask - I think we should raise or warn in this case since none of the websocket features they asked for are actually turned on.
| task = asyncio.create_task( | ||
| execute_callback_task(message, renderer_id, request_id) | ||
| ) |
There was a problem hiding this comment.
I'm a little concerned that this is executed with no queue or cap on the number of callbacks that can be executed at once.
I think with the REST callbacks, we are naturally protected from callback spamming because browsers only send a limited number of requests at once.
But with websockets, I think this could result in some sort of "callback storm" if the app is written for it. We should limit the max number of concurrent callbacks.




Implements WebSocket-based callbacks for Dash, enabling real-time bidirectional communication between the renderer and backend. This replaces HTTP POST requests with persistent WebSocket connections for reduced latency and server-push capabilities.
Architecture
A SharedWorker maintains a single WebSocket connection shared across all browser tabs, routing messages between renderers and the server.
Usage
Use with fastapi
pip install dash[fastapi]==4.2.0rc1(when available).Example app in
wsapp.py(to be deleted before full release alongside quart_app.py)