{"record":{"id":"17116ea8f5cdc6aa","repo":"gorilla/websocket","slug":"websocket-write-closed","errorCode":null,"errorMessage":"websocket: write closed","messagePattern":"websocket: write closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"conn.go","lineNumber":179,"sourceCode":"// IsUnexpectedCloseError returns boolean indicating whether the error is a\n// *CloseError with a code not in the list of expected codes.\nfunc IsUnexpectedCloseError(err error, expectedCodes ...int) bool {\n\tif e, ok := err.(*CloseError); ok {\n\t\tfor _, code := range expectedCodes {\n\t\t\tif e.Code == code {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\treturn false\n}\n\nvar (\n\terrWriteTimeout        = &netError{msg: \"websocket: write timeout\", timeout: true, temporary: true}\n\terrUnexpectedEOF       = &CloseError{Code: CloseAbnormalClosure, Text: io.ErrUnexpectedEOF.Error()}\n\terrBadWriteOpCode      = errors.New(\"websocket: bad write message type\")\n\terrWriteClosed         = errors.New(\"websocket: write closed\")\n\terrInvalidControlFrame = errors.New(\"websocket: invalid control frame\")\n)\n\n// maskRand is an io.Reader for generating mask bytes. The reader is initialized\n// to crypto/rand Reader. Tests swap the reader to a math/rand reader for\n// reproducible results.\nvar maskRand = rand.Reader\n\n// newMaskKey returns a new 32 bit value for masking client frames.\nfunc newMaskKey() [4]byte {\n\tvar k [4]byte\n\t_, _ = io.ReadFull(maskRand, k[:])\n\treturn k\n}\n\nfunc isControl(frameType int) bool {\n\treturn frameType == CloseMessage || frameType == PingMessage || frameType == PongMessage\n}","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/gorilla/websocket/blob/e064f32e3674d9d79a8fd417b5bc06fa5c6cad8f/conn.go#L161-L197","documentation":"errWriteClosed is returned by Conn.Write (and Close paths) when a write is attempted after the write side of the connection has been closed. Once the connection's write state is marked closed, further writes fail with this sentinel.","triggerScenarios":"Calling WriteMessage/NextWriter-based writes after conn.Close() has been called, or after the internal write close has occurred (e.g. following a close handshake completion or prior fatal write error).","commonSituations":"Background goroutines still writing after the main flow closed the connection; write pump continuing after read pump detected an error and closed; double-close of a connection followed by an attempted write.","solutions":["Stop all writers before/when closing: use a done channel or context cancellation coordinated with conn.Close()","Treat this error as benign during shutdown — check with errors.Is and return quietly","Use a single writer goroutine (send messages over a channel) so close ordering is deterministic","Guard writes with a sync.Once or state flag that prevents writes after Close is invoked"],"exampleFix":"// before\nconn.Close()\nconn.WriteMessage(websocket.TextMessage, []byte(\"bye\")) // errors: write closed\n// after\nerr := conn.WriteMessage(websocket.TextMessage, []byte(\"bye\"))\nif err == nil {\n    conn.Close()\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isWriteClosed(err error) bool {\n    return errors.Is(err, websocket.ErrCloseSent) || errors.Is(err, errWriteClosedEquivalent(err))\n}\n// in practice: match by message since errWriteClosed is unexported\nfunc isWriteClosedMsg(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"websocket: write closed\")\n}","tryCatchPattern":"if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {\n    if strings.Contains(err.Error(), \"websocket: write closed\") ||\n        errors.Is(err, websocket.ErrCloseSent) {\n        return nil // connection already shutting down\n    }\n    return err\n}","preventionTips":["Signal all writer goroutines via a closed channel or context before conn.Close()","Route every write through one owner goroutine to make close ordering deterministic","Ignore benign shutdown-write errors instead of retrying them"],"tags":["websocket","lifecycle","concurrency"],"backgroundTag":"connection-already-closed","analyzedSha":"e064f32e3674d9d79a8fd417b5bc06fa5c6cad8f","analyzedAt":"2026-08-31T12:40:58.222Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}