{"record":{"id":"a560f2bff87ee30f","repo":"siyuan-note/siyuan","slug":"websocket-is-closing","errorCode":null,"errorMessage":"WebSocket is closing","messagePattern":"WebSocket is closing","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/api_client.go","lineNumber":335,"sourceCode":"\t\t\tdoClose := func() {\n\t\t\t\tcloseOnce.Do(func() {\n\t\t\t\t\tcancel()\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tws_open := rt.ToValue(func(openCall goja.FunctionCall, rt *goja.Runtime) goja.Value {\n\t\t\t\topenPromise, openResolve, openReject := rt.NewPromise()\n\n\t\t\t\topenRunErr := p.worker.Run(func(rt *goja.Runtime) (_ any, err error) {\n\t\t\t\t\tstate := WebSocketState(readyState.Load())\n\t\t\t\t\tswitch state {\n\t\t\t\t\tcase WebSocketReadyStateOpen:\n\t\t\t\t\t\tif resolveErr := openResolve(nil); resolveErr != nil {\n\t\t\t\t\t\t\tlogging.LogErrorf(\"[plugin:%s] siyuan.client.socket.open resolve: %v\", p.Name, resolveErr)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn\n\t\t\t\t\tcase WebSocketReadyStateClosing:\n\t\t\t\t\t\terr = fmt.Errorf(\"WebSocket is closing\")\n\t\t\t\t\t\treturn\n\t\t\t\t\tcase WebSocketReadyStateClosed:\n\t\t\t\t\t\terr = fmt.Errorf(\"WebSocket is closed\")\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\taddOpenPromise(openResolve, openReject)\n\t\t\t\t\topenOnce.Do(func() {\n\t\t\t\t\t\tgo func() {\n\t\t\t\t\t\t\tconn, _, dialErr := gws.NewClient(h, &gws.ClientOption{\n\t\t\t\t\t\t\t\tAddr:          wsURL,\n\t\t\t\t\t\t\t\tRequestHeader: wsHeader,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\tif dialErr != nil {\n\t\t\t\t\t\t\t\tp.worker.Run(func(rt *goja.Runtime) (_ any, _ error) {\n\t\t\t\t\t\t\t\t\tsetReadyState(rt, WebSocketReadyStateClosed)\n\n\t\t\t\t\t\t\t\t\tevent := rt.NewObject()","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/api_client.go#L317-L353","documentation":"The WebSocket close was already initiated (ready state = CLOSING) when open() was called, so the binding rejects with 'WebSocket is closing' instead of registering a new open promise. A socket in CLOSING state will never transition to OPEN, so waiting would be pointless.","triggerScenarios":"Calling socket.open() while a close() is in progress — e.g. an open retry racing with plugin shutdown, or calling open() from a close handler / during runtime teardown.","commonSituations":"Reconnect logic that calls open() without checking readyState after an error; asynchronous callbacks firing during window unload; plugin reload where the old socket is still closing.","solutions":["Check socket.readyState before calling open(); skip or wait when it is CLOSING.","Wait for the close event (or onclose callback) to complete, then create a fresh socket and open it.","Re-run the open on a new socket instance rather than reusing the closing one."],"exampleFix":"// before\nsocket.open();\n// after\nif (socket.readyState === 0 || socket.readyState === 1) {\n  // already connecting/open, do nothing\n} else if (socket.readyState === 3) {\n  socket = client.socket.connect('/ws');\n  socket.open();\n} // CLOSING (2): wait for close event before reopening","handlingStrategy":"type-guard","validationCode":"if (socket.readyState === 2) { /* CLOSING: defer reopen until onclose */ }","typeGuard":"const canOpen = (s) => s.readyState === 3; // only CLOSED sockets may be replaced/reopened; CLOSING must wait","tryCatchPattern":"try {\n  socket.open();\n} catch (e) {\n  if (String(e).includes('WebSocket is closing')) {\n    socket.onclose = () => { socket = client.socket.connect(path); socket.open(); };\n  }\n}","preventionTips":["Check readyState before every open call","Drive reconnection from the onclose event, not from timers alone","Avoid calling open() from teardown/close handlers"],"tags":["websocket","lifecycle","race-condition"],"backgroundTag":"invalid-state-transition","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}