siyuan-note/siyuan · warning
WebSocket is closing
Error message
WebSocket is closing
What it means
Returned by socket.open() when the WebSocket readyState is CLOSING (2). open() refuses to start or resolve because the connection is already shutting down; this rejects the open() promise.
Source
Thrown at kernel/plugin/api_client.go:335
doClose := func() {
closeOnce.Do(func() {
cancel()
})
}
ws_open := rt.ToValue(func(openCall goja.FunctionCall, rt *goja.Runtime) goja.Value {
openPromise, openResolve, openReject := rt.NewPromise()
openRunErr := p.worker.Run(func(rt *goja.Runtime) (_ any, err error) {
state := WebSocketState(readyState.Load())
switch state {
case WebSocketReadyStateOpen:
if resolveErr := openResolve(nil); resolveErr != nil {
logging.LogErrorf("[plugin:%s] siyuan.client.socket.open resolve: %v", p.Name, resolveErr)
}
return
case WebSocketReadyStateClosing:
err = fmt.Errorf("WebSocket is closing")
return
case WebSocketReadyStateClosed:
err = fmt.Errorf("WebSocket is closed")
return
}
addOpenPromise(openResolve, openReject)
openOnce.Do(func() {
go func() {
conn, _, dialErr := gws.NewClient(h, &gws.ClientOption{
Addr: wsURL,
RequestHeader: wsHeader,
})
if dialErr != nil {
p.worker.Run(func(rt *goja.Runtime) (_ any, _ error) {
setReadyState(rt, WebSocketReadyStateClosed)
event := rt.NewObject()View on GitHub (pinned to 251596fc0d)
Solutions
- Check ws.readyState before calling open(); skip if it is CLOSING(2) or CLOSED(3).
- Create a fresh socket() (new connection object) for reconnects rather than calling open() on a closing one.
- Drive reconnects from onclose, gating on readyState === CLOSED.
Example fix
// before
ws.close();
await ws.open(); // rejects: WebSocket is closing
// after
ws.close();
// for a new connection, create a new socket instead of reopening
const ws2 = await siyuan.client.socket('/ws/foo');
await ws2.open(); Defensive patterns
Strategy: validation
Validate before calling
if (ws.readyState === 2 || ws.readyState === 3) {
// create a new socket instead of reopening
} else {
await ws.open();
} Type guard
const isReusable = (ws) => ws.readyState === 0 || ws.readyState === 1;
Try / catch
try { await ws.open(); }
catch (e) { if (/WebSocket is closing/.test(String(e))) { /* reconnect via new socket() */ } } Prevention
- Treat a socket as single-use; reconnect by creating a new socket().
- Gate open() on readyState.
- Drive reconnect from onclose.
When it happens
Trigger: Calling open() after close() has transitioned readyState to CLOSING but before it reaches CLOSED. Happens in close-then-reconnect logic that races, or calling open() twice where the first triggered a close.
Common situations: Reconnect handlers that call open() from onclose without checking state, or a close initiated elsewhere (user navigates away) while open() is invoked.
Related errors
- WebSocket is closed
- WebSocket is not open (state: %d)
- WebSocket not yet connected
- WebSocket is not open (state: %d)
- Recorder has been disposed
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/a560f2bff87ee30f.
Report an issue: GitHub.