{"record":{"id":"deb7a70358425a2a","repo":"siyuan-note/siyuan","slug":"websocket-is-not-open-state-d-deb7a7","errorCode":null,"errorMessage":"WebSocket is not open (state: %d)","messagePattern":"WebSocket is not open \\(state: (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/plugin/plugin.go","lineNumber":1144,"sourceCode":"\t\t\tsendRunErr := p.worker.Run(func(rt *goja.Runtime) (_ any, err error) {\n\t\t\t\tvar messageData []byte\n\t\t\t\tvar opcode gws.Opcode\n\t\t\t\tif len(sendCall.Arguments) >= 1 {\n\t\t\t\t\tdata := sendCall.Argument(0)\n\t\t\t\t\tif arrayBuffer, ok := data.Export().(goja.ArrayBuffer); ok {\n\t\t\t\t\t\topcode = gws.OpcodeBinary\n\t\t\t\t\t\tb := arrayBuffer.Bytes()\n\t\t\t\t\t\tmessageData = make([]byte, len(b))\n\t\t\t\t\t\tcopy(messageData, b) // ArrayBuffer.Bytes() points into JS engine memory; copy before async send\n\t\t\t\t\t} else {\n\t\t\t\t\t\topcode = gws.OpcodeText\n\t\t\t\t\t\tmessageData = []byte(data.String())\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tstate := WebSocketState(readyState.Load())\n\t\t\t\tif state == WebSocketReadyStateClosing || state == WebSocketReadyStateClosed {\n\t\t\t\t\terr = fmt.Errorf(\"WebSocket is not open (state: %d)\", state)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tupdatePortBufferedAmount(rt, len(messageData))\n\t\t\t\tsocket.WriteAsync(opcode, messageData, func(writeErr error) {\n\t\t\t\t\tp.worker.Run(func(rt *goja.Runtime) (_ any, err error) {\n\t\t\t\t\t\tif writeErr == nil {\n\t\t\t\t\t\t\tupdatePortBufferedAmount(rt, -len(messageData))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\terr = writeErr\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn\n\t\t\t\t\t}, func(rt *goja.Runtime, result any, err error) {\n\t\t\t\t\t\tif lo.IsNil(err) {\n\t\t\t\t\t\t\tif resolveErr := sendResolve(result); resolveErr != nil {\n\t\t\t\t\t\t\t\tlogging.LogErrorf(\"[plugin:%s] ws server port.send resolve: %v\", p.Name, resolveErr)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {","sourceCodeStart":1126,"sourceCodeEnd":1162,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/plugin/plugin.go#L1126-L1162","documentation":"Returned by the WebSocket port.send() path when the socket readyState is CLOSING (2) or CLOSED (3) at send time. The kernel refuses to enqueue a WriteAsync in those states and rejects the send Promise with the current state number.","triggerScenarios":"Plugin calls port.send(...) after onclose fired, or a client disconnect/cancel races with the send so the state transitions to closing before the worker runs.","commonSituations":"Sending from a timer/loop without checking readyState, or sending in onclose/onerror handlers after the connection is already shutting down.","solutions":["Check port.readyState === 1 (OPEN) before calling port.send.","Stop send loops on onclose/onerror; drain queued sends before reopening.","Catch the rejection and treat state 2/3 as a non-fatal 'connection gone' signal."],"exampleFix":"// before\nport.send(JSON.stringify(msg));\n\n// after\nif (port.readyState !== 1) return;\nport.send(JSON.stringify(msg)).catch((e) => {\n  if (/state: [23]/.test(String(e))) return; // already closing/closed\n  throw e;\n});","handlingStrategy":"validation","validationCode":"const OPEN = 1; if (port.readyState !== OPEN) { /* skip or buffer */ return; }","typeGuard":"function isPortOpen(port: { readyState: number }): boolean { return port.readyState === 1; }","tryCatchPattern":"try { await port.send(payload); } catch (e) { if (/state: [23]/.test(String(e))) return; throw e; }","preventionTips":["Check readyState === 1 before each send.","Stop send loops in onclose/onerror.","Tolerate state 2/3 rejections as 'connection gone'."],"tags":["plugin","server","websocket","lifecycle"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}