How to broadcast message to all active websocket connections?

When I try to store the WebSocket object in an array and call send() on each of them I'll get
Error: Cannot perform I/O on behalf of a different request. I/O objects (such as streams,
request/response bodies, and others) created in the context of one request handler cannot be
accessed from a different request's handler. This is a limitation of Cloudflare Workers which
allows us to improve overall performance. (I/O type: Native)
Error: Cannot perform I/O on behalf of a different request. I/O objects (such as streams,
request/response bodies, and others) created in the context of one request handler cannot be
accessed from a different request's handler. This is a limitation of Cloudflare Workers which
allows us to improve overall performance. (I/O type: Native)
4 Replies
DaniFoldi
DaniFoldi5mo ago
Hmm, websockets shouldn't be triggering this. Can you share a bit of your code where you're broadcasting from?
tga
tga5mo ago
I tried to make a minimal repro, it still fails but with a different error msg
const connections: WebSocket[] = []

export default {
fetch: (req) => {
const url = new URL(req.url)
if (url.pathname === "/ws") {
const upgradeHeader = req.headers.get("Upgrade")
if (!upgradeHeader || upgradeHeader !== "websocket") {
return new Response("Expected Upgrade: websocket", { status: 426 })
}
const webSocketPair = new WebSocketPair()
const [client, server] = Object.values(webSocketPair)
server.accept()
connections.push(server)
connections.forEach((ws) => ws.send("new connection!"))
// logic to remove on "close" event
return new Response(null, {
status: 101,
webSocket: client,
})
}
return new Response(`
<script>
const ws = new WebSocket("ws://localhost:8787/ws")
ws.onmessage = (e) => {
console.log("data", e.data)
}
ws.onopen = () => {
console.log("open")
ws.send("hi")
}
</script>
`, {
headers: {
"Content-Type": "text/html; charset=utf-8",
}
})
},
} satisfies ExportedHandler<{}>
const connections: WebSocket[] = []

export default {
fetch: (req) => {
const url = new URL(req.url)
if (url.pathname === "/ws") {
const upgradeHeader = req.headers.get("Upgrade")
if (!upgradeHeader || upgradeHeader !== "websocket") {
return new Response("Expected Upgrade: websocket", { status: 426 })
}
const webSocketPair = new WebSocketPair()
const [client, server] = Object.values(webSocketPair)
server.accept()
connections.push(server)
connections.forEach((ws) => ws.send("new connection!"))
// logic to remove on "close" event
return new Response(null, {
status: 101,
webSocket: client,
})
}
return new Response(`
<script>
const ws = new WebSocket("ws://localhost:8787/ws")
ws.onmessage = (e) => {
console.log("data", e.data)
}
ws.onopen = () => {
console.log("open")
ws.send("hi")
}
</script>
`, {
headers: {
"Content-Type": "text/html; charset=utf-8",
}
})
},
} satisfies ExportedHandler<{}>
✘ [ERROR] A hanging Promise was canceled. This happens when the worker runtime is waiting for a Promise from JavaScript to resolve, but has detected that the Promise cannot possibly ever resolve because all code and events related to the Promise's I/O context have already finished.


[wrangler:err] SyntaxError: Unexpected end of JSON input
✘ [ERROR] A hanging Promise was canceled. This happens when the worker runtime is waiting for a Promise from JavaScript to resolve, but has detected that the Promise cannot possibly ever resolve because all code and events related to the Promise's I/O context have already finished.


[wrangler:err] SyntaxError: Unexpected end of JSON input
DaniFoldi
DaniFoldi5mo ago
ah, so you're within a worker - yeah that won't work during testing, and won't be of much use when uploaded either Cloudflare deploys your worker to thousands of servers, and any one might be serving a request, so it's unlikely for all clients to be connected to a single metal I recommend using Durable Objects and the websocket hibernation api, where you can bring this guarantee in
tga
tga5mo ago
oh I see, that makes sense, thank you!
Want results from more Discord servers?
Add your server