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

  1. Check ws.readyState before calling open(); skip if it is CLOSING(2) or CLOSED(3).
  2. Create a fresh socket() (new connection object) for reconnects rather than calling open() on a closing one.
  3. 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

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


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/a560f2bff87ee30f. Report an issue: GitHub.