{"record":{"id":"85e8e76e2eb942ac","repo":"cloudflare/cloudflared","slug":"write-to-closed-websocket-connection","errorCode":null,"errorMessage":"write to closed websocket connection","messagePattern":"write to closed websocket connection","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"websocket/connection.go","lineNumber":115,"sourceCode":"\treturn c\n}\n\n// Read will read messages from the websocket connection\nfunc (c *Conn) Read(reader []byte) (int, error) {\n\tdata, err := wsutil.ReadClientBinary(c.rw)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn copy(reader, data), nil\n}\n\n// Write will write messages to the websocket connection.\n// It will not write to the connection after Close is called to fix TUN-5184\nfunc (c *Conn) Write(p []byte) (int, error) {\n\tc.writeLock.Lock()\n\tdefer c.writeLock.Unlock()\n\tif c.done {\n\t\treturn 0, errors.New(\"write to closed websocket connection\")\n\t}\n\tif err := wsutil.WriteServerBinary(c.rw, p); err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(p), nil\n}\n\nfunc (c *Conn) pinger(ctx context.Context) {\n\tpongMessge := wsutil.Message{\n\t\tOpCode:  gobwas.OpPong,\n\t\tPayload: []byte{},\n\t}\n\n\tticker := time.NewTicker(c.pingPeriod(ctx))\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/websocket/connection.go#L97-L133","documentation":"websocket/connection.go's Conn.Write guards against writing after Close was called (fix for TUN-5184). If the connection is marked done, it returns this error instead of passing bytes down to wsutil, which would otherwise panic or silently corrupt state. It signals the caller that the websocket lifecycle has ended and the write was discarded (0 bytes written).","triggerScenarios":"Any caller of (*websocket.Conn).Write — e.g. echoTCPOrigin, WriteEvent, echoTCP — invoking Write after another goroutine has called Close() on the same Conn (c.done set to true under writeLock).","commonSituations":"A proxied TCP/SSH session where the client disconnected and the origin loop still tries to flush buffered data; a stream/heartbeat (WriteEvent) racing with connection teardown during shutdown; writing to a websocket whose dial failed or whose reader already exited.","solutions":["Check connection state before writing: stop the write loop when Close/done is signaled (e.g. select on a done channel).","Treat this error as a benign shutdown signal — log at debug and break out of the write loop rather than retrying.","Serialize lifecycle: ensure Close() is only called after all in-flight writers have returned, or guard writes with the same done check.","If writes regularly race with close, restructure so the owning goroutine closes the connection last."],"exampleFix":"// before: blind write\nn, err := wsConn.Write(data)\n// after: tolerate closed-connection teardown\nn, err := wsConn.Write(data)\nif err != nil {\n\tif err.Error() == \"write to closed websocket connection\" {\n\t\treturn nil // connection already torn down\n\t}\n\treturn err\n}","handlingStrategy":"type-guard","validationCode":"// guard: only write while the connection is open\nif wsConn == nil || wsConn.IsClosed() {\n    return errors.New(\"websocket not open; skipping write\")\n}","typeGuard":"func writable(c *websocket.Conn) bool {\n    return c != nil && !c.IsClosed()\n}","tryCatchPattern":"n, err := wsConn.Write(p)\nif err != nil {\n    if strings.Contains(err.Error(), \"write to closed websocket connection\") {\n        return 0, nil // expected during shutdown\n    }\n    return n, err\n}","preventionTips":["Stop writer goroutines via a done channel before calling Close()","Never fan out writes to a Conn from goroutines that outlive the session","Treat closed-connection write errors as normal teardown, not failures","Ensure the reader loop exits trigger Close only after writers are drained"],"tags":["websocket","closed-connection","concurrency","race"],"backgroundTag":"write-to-closed-connection","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}