{"record":{"id":"97ab3396c8a80baa","repo":"gorilla/websocket","slug":"concurrent-write-to-websocket-connection","errorCode":null,"errorMessage":"concurrent write to websocket connection","messagePattern":"concurrent write to websocket connection","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"conn.go","lineNumber":626,"sourceCode":"\t\tc.writeBuf[framePos] = b0\n\t\tc.writeBuf[framePos+1] = b1 | byte(length)\n\t}\n\n\tif !c.isServer {\n\t\tkey := newMaskKey()\n\t\tcopy(c.writeBuf[maxFrameHeaderSize-4:], key[:])\n\t\tmaskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos])\n\t\tif len(extra) > 0 {\n\t\t\treturn w.endMessage(c.writeFatal(errors.New(\"websocket: internal error, extra used in client mode\")))\n\t\t}\n\t}\n\n\t// Write the buffers to the connection with best-effort detection of\n\t// concurrent writes. See the concurrency section in the package\n\t// documentation for more info.\n\n\tif c.isWriting {\n\t\tpanic(\"concurrent write to websocket connection\")\n\t}\n\tc.isWriting = true\n\n\terr := c.write(w.frameType, c.writeDeadline, c.writeBuf[framePos:w.pos], extra)\n\n\tif !c.isWriting {\n\t\tpanic(\"concurrent write to websocket connection\")\n\t}\n\tc.isWriting = false\n\n\tif err != nil {\n\t\treturn w.endMessage(err)\n\t}\n\n\tif final {\n\t\t_ = w.endMessage(errWriteClosed)\n\t\treturn nil\n\t}","sourceCodeStart":608,"sourceCodeEnd":644,"githubUrl":"https://github.com/gorilla/websocket/blob/e064f32e3674d9d79a8fd417b5bc06fa5c6cad8f/conn.go#L608-L644","documentation":"This panic fires when a write is started on a connection whose isWriting flag is already true, meaning two goroutines attempted to write concurrently. gorilla/websocket only supports one concurrent writer; this best-effort detection protects the connection from interleaved, corrupted frames.","triggerScenarios":"Two goroutines simultaneously calling WriteMessage, WriteControl paths via prepareWrite/flushFrame, or NextWriter-based writes on the same Conn; the second write observes isWriting==true at conn.go:626.","commonSituations":"Broadcasting messages to a connection from multiple goroutines (e.g. a hub fan-out plus a direct write), ping keepalive goroutines writing while the main handler writes, or sending from websocket event callbacks and background timers at the same time.","solutions":["Serialize all writes through a single goroutine with a channel of outbound messages","Guard writes with a sync.Mutex held around every WriteMessage/WriteControl call on that Conn","Move keepalive pings into the same writer goroutine instead of a separate timer goroutine","Recover from the panic per-connection and close/reconnect, since state may be corrupted"],"exampleFix":"// before\ngo func(){ conn.WriteMessage(TextMessage, msg) }()\ngo func(){ conn.WriteMessage(TextMessage, other) }()\n// after\nvar mu sync.Mutex\nfunc safeWrite(conn *websocket.Conn, data []byte) {\n    mu.Lock()\n    defer mu.Unlock()\n    conn.WriteMessage(websocket.TextMessage, data)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"func safeWrite(conn *websocket.Conn, mt int, data []byte) (err error) {\n    defer func() { recover() }() // guard against concurrent-write panic\n    mu.Lock()\n    defer mu.Unlock()\n    return conn.WriteMessage(mt, data)\n}","preventionTips":["One writer goroutine per connection, fed by a channel","Or a sync.Mutex around every write call","Move keepalive pings into the serialized write path","Review fan-out code for multiple write call sites"],"tags":["websocket","concurrency","panic"],"backgroundTag":"concurrent-websocket-write","analyzedSha":"e064f32e3674d9d79a8fd417b5bc06fa5c6cad8f","analyzedAt":"2026-08-31T12:40:58.222Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}