{"record":{"id":"250f2640bd65d01b","repo":"siyuan-note/siyuan","slug":"websocket-not-yet-connected","errorCode":null,"errorMessage":"WebSocket not yet connected","messagePattern":"WebSocket not yet connected","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/plugin/api_client.go","lineNumber":432,"sourceCode":"\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\n\t\t\t\t\t\t}, func(rt *goja.Runtime, result any, err error) {\n\t\t\t\t\t\t\tif lo.IsNil(err) {\n\t\t\t\t\t\t\t\tif resolveErr := sendResolve(result); resolveErr != nil {\n\t\t\t\t\t\t\t\t\tlogging.LogErrorf(\"[plugin:%s] siyuan.client.socket.send resolve: %v\", p.Name, resolveErr)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {","sourceCodeStart":414,"sourceCodeEnd":450,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/plugin/api_client.go#L414-L450","documentation":"The ready state is OPEN but the underlying gws connection handle (gwsConn) has not been populated yet — a race between the JS-visible OPEN transition and the Go-side connection pointer being stored. send() finds c == nil and rejects with 'WebSocket not yet connected'.","triggerScenarios":"Calling send() immediately after the open promise resolves, before the internal connection reference is published; concurrent send during the connection handshake completion window.","commonSituations":"Code that sends a subscription/register message in the first microtask after open resolves; fast local connections where JS state flips before the atomic pointer store is observed.","solutions":["Retry the send after a short delay or on the next tick; the window is transient.","Serialize sends: perform the first send inside a setTimeout/onopen-followup rather than synchronously after open resolves.","If it persists, the connection dropped between state update and send — recreate the socket."],"exampleFix":"// before\nawait socket.open();\nsocket.send('hello');\n// after\nawait socket.open();\nsetTimeout(() => {\n  if (socket.readyState === 1) socket.send('hello');\n}, 0);","handlingStrategy":"retry","validationCode":"if (socket.readyState === 1) { /* still can race; add a tick delay before first send */ }","typeGuard":null,"tryCatchPattern":"try {\n  socket.send(first);\n} catch (e) {\n  if (String(e).includes('not yet connected')) {\n    setTimeout(() => socket.readyState === 1 && socket.send(first), 50);\n  }\n}","preventionTips":["Delay the first send until at least one event-loop tick after open resolves","Serialize the first post-open message through a small queue","Recreate the socket if the error repeats — the connection likely dropped"],"tags":["websocket","race-condition","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"}