{"record":{"id":"eea9d93a83cb6775","repo":"siyuan-note/siyuan","slug":"websocket-is-not-open-state-d","errorCode":null,"errorMessage":"WebSocket is not open (state: %d)","messagePattern":"WebSocket is not open \\(state: (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/api_client.go","lineNumber":426,"sourceCode":"\n\t\t\t\tvar messageData []byte\n\t\t\t\tvar opcode gws.Opcode\n\t\t\t\tif data := sendCall.Argument(0); isJsValueNotNull(data) {\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\tsendRunErr := p.worker.Run(func(rt *goja.Runtime) (_ any, err error) {\n\t\t\t\t\tstate := WebSocketState(readyState.Load())\n\t\t\t\t\tif state == WebSocketReadyStateClosing || state == WebSocketReadyStateClosed {\n\t\t\t\t\t\terr = fmt.Errorf(\"WebSocket is not open (state: %d)\", state)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tc := gwsConn.Load()\n\t\t\t\t\tif c == nil {\n\t\t\t\t\t\terr = fmt.Errorf(\"WebSocket not yet connected\")\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tupdateBufferedAmount(rt, len(messageData))\n\t\t\t\t\tc.WriteAsync(opcode, messageData, func(writeErr error) {\n\t\t\t\t\t\tp.worker.Run(func(rt *goja.Runtime) (_ any, err error) {\n\t\t\t\t\t\t\tif writeErr == nil {\n\t\t\t\t\t\t\t\tupdateBufferedAmount(rt, -len(messageData))\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\terr = writeErr\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn","sourceCodeStart":408,"sourceCodeEnd":444,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/api_client.go#L408-L444","documentation":"send() was invoked while the socket ready state is CLOSING (2) or CLOSED (3). Data can only be written on an OPEN (1) connection, so the binding rejects with the numeric state for diagnosis. This is a WebSocket lifecycle guard, not a transport failure.","triggerScenarios":"Calling socket.send(message) after close() was called, after onclose fired, or concurrently with a close in progress; also on a socket that never finished opening.","commonSituations":"Queued send callbacks executing after teardown; UI event handlers sending on a socket closed by kernel restart; missing readyState checks in send wrappers.","solutions":["Check socket.readyState === 1 before sending; buffer or drop messages otherwise.","Recreate the socket (connect + open) after it reaches CLOSED, then resend.","Route sends through onopen so messages only go out on a live connection."],"exampleFix":"// before\nsocket.send(JSON.stringify(msg));\n// after\nif (socket.readyState === 1) {\n  socket.send(JSON.stringify(msg));\n} else {\n  pendingQueue.push(msg); // flush on next onopen\n}","handlingStrategy":"type-guard","validationCode":"function canSend(s) { return s.readyState === 1; }\nif (!canSend(socket)) queueMessage(msg);","typeGuard":"const isOpen = (s) => s.readyState === 1;","tryCatchPattern":"try {\n  socket.send(data);\n} catch (e) {\n  if (String(e).includes('WebSocket is not open')) {\n    pendingQueue.push(data); // flush on next onopen\n  }\n}","preventionTips":["Gate all sends on readyState === 1","Buffer outgoing messages while the socket is not open","Close sockets explicitly on teardown so handlers stop sending"],"tags":["websocket","lifecycle","send"],"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"}