{"record":{"id":"d76c72de56f32cd5","repo":"navidrome/navidrome","slug":"failed-to-send-binary-message-w","errorCode":null,"errorMessage":"failed to send binary message: %w","messagePattern":"failed to send binary message: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/host_websocket.go","lineNumber":165,"sourceCode":"\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := wsConn.conn.WriteMessage(websocket.TextMessage, []byte(message)); err != nil {\n\t\treturn fmt.Errorf(\"failed to send text message: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (s *webSocketServiceImpl) SendBinary(ctx context.Context, connectionID string, data []byte) error {\n\twsConn, err := s.getConnection(connectionID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := wsConn.conn.WriteMessage(websocket.BinaryMessage, data); err != nil {\n\t\treturn fmt.Errorf(\"failed to send binary message: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (s *webSocketServiceImpl) CloseConnection(ctx context.Context, connectionID string, code int32, reason string) error {\n\ts.mu.Lock()\n\twsConn, exists := s.connections[connectionID]\n\tif !exists {\n\t\ts.mu.Unlock()\n\t\treturn fmt.Errorf(\"connection ID %q not found\", connectionID)\n\t}\n\tdelete(s.connections, connectionID)\n\ts.mu.Unlock()\n\n\t// Mark as closed to prevent callback\n\twsConn.closeMu.Lock()\n\twsConn.isClosed = true","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/navidrome/navidrome/blob/4ed7494a3293a9e9e647897ebfb9be327efd981b/plugins/host_websocket.go#L147-L183","documentation":"SendBinary writes a binary frame via conn.WriteMessage(websocket.BinaryMessage, data); any write failure is wrapped as \"failed to send binary message: %w\". Like SendText, it fails when the connection is dead, closing, or being written concurrently.","triggerScenarios":"Calling SendBinary on a closed/torn-down connection, during network interruption, or from multiple goroutines without coordination; also very large payloads causing write timeouts.","commonSituations":"Streaming binary data (files, protobuf) after the server dropped the link; peer sent close frame but app kept sending; write deadline expired.","solutions":["Inspect the wrapped error to distinguish closed connection vs timeout and reconnect if needed","Guard writes with a mutex or a dedicated writer goroutine","Check connection health (ping/pong) before sending large payloads","Set appropriate write deadlines and chunk very large payloads"],"exampleFix":"// before\nerr := ws.SendBinary(ctx, id, payload)\n// after\nif err := ws.SendBinary(ctx, id, payload); err != nil {\n    if isConnClosed(err) {\n        id = mustReconnect()\n        err = ws.SendBinary(ctx, id, payload)\n    }\n}","handlingStrategy":"retry","validationCode":"if len(data) == 0 { return errors.New(\"empty binary payload\") }\nif !isAlive(connectionID) { return errors.New(\"connection not alive; reconnect first\") }","typeGuard":"func isWriteErrRecoverable(err error) bool {\n    return errors.Is(err, net.ErrClosed) || strings.Contains(err.Error(), \"broken pipe\") || strings.Contains(err.Error(), \"write timeout\")\n}","tryCatchPattern":"err := ws.SendBinary(ctx, id, data)\nif err != nil {\n    if isWriteErrRecoverable(err) {\n        id = reconnect()\n        err = ws.SendBinary(ctx, id, data)\n    }\n    if err != nil { return err }\n}","preventionTips":["Serialize binary writes like text writes (single writer/mutex)","Chunk large payloads and set sane write deadlines","Detect dead peers with ping/pong before streaming binary data"],"tags":["websocket","network","binary","send"],"backgroundTag":"websocket-write-failed","analyzedSha":"4ed7494a3293a9e9e647897ebfb9be327efd981b","analyzedAt":"2026-09-01T05:03:05.018Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}