{"record":{"id":"047a536e898faafd","repo":"AlexxIT/go2rtc","slug":"mqtt-client-is-closed-send-mqtt-message-fail","errorCode":null,"errorMessage":"mqtt client is closed, send mqtt message fail","messagePattern":"mqtt client is closed, send mqtt message fail","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/tuya/mqtt.go","lineNumber":400,"sourceCode":"\t\tc.handleCandidate(candidate)\n\t}\n}\n\nfunc (c *TuyaMqttClient) onDisconnect() {\n\tif c.handleDisconnect != nil {\n\t\tc.handleDisconnect()\n\t}\n}\n\nfunc (c *TuyaMqttClient) onError(err error) {\n\tif c.handleError != nil {\n\t\tc.handleError(err)\n\t}\n}\n\nfunc (c *TuyaMqttClient) sendMqttMessage(messageType string, protocol int, transactionID string, data interface{}) error {\n\tif c.closed {\n\t\treturn fmt.Errorf(\"mqtt client is closed, send mqtt message fail\")\n\t}\n\n\tjsonMessage, err := json.Marshal(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmsg := &MqttMessage{\n\t\tProtocol: protocol,\n\t\tPv:       \"2.2\",\n\t\tT:        time.Now().Unix(),\n\t\tData: MqttFrame{\n\t\t\tHeader: MqttFrameHeader{\n\t\t\t\tType:          messageType,\n\t\t\t\tFrom:          c.uid,\n\t\t\t\tTo:            c.deviceId,\n\t\t\t\tSessionID:     c.sessionId,\n\t\t\t\tMotoID:        c.motoId,","sourceCodeStart":382,"sourceCodeEnd":418,"githubUrl":"https://github.com/AlexxIT/go2rtc/blob/c245815e75e2a5fd60b4290f12bfc04e55a984d3/pkg/tuya/mqtt.go#L382-L418","documentation":"sendMqttMessage guards all outgoing MQTT protocol messages (offer, candidate, resolution, speaker, disconnect) with a closed flag; if the client was closed this static error is returned before any marshalling or publish. It prevents use-after-Close publishes on a torn-down Paho client. Any Send* method on a closed client surfaces this error.","triggerScenarios":"Calling SendOffer, SendCandidate, SendResolution, SendSpeaker, or SendDisconnect after TuyaMqttClient.Close() has been called, or reusing a client instance from another goroutine after close.","commonSituations":"Graceful shutdown sequences where streaming handlers still run after Close, double-close patterns, or reconnect logic that closes the old client but callers hold stale references.","solutions":["Guard call sites with the client's closed/connection state before any Send* call","Recreate or reinitialize the client after Close instead of reusing it","Track lifecycle so Shutdown waits for in-flight sends before closing","Retry with a fresh client if the send must succeed"],"exampleFix":"// before\nclient.Close()\nclient.SendOffer(sdp, res, 1, false) // fails\n// after\nclient.Close()\nclient = tuya.NewMqttClient(...)\nif err := client.Init(); err != nil { return err }\nerr := client.SendOffer(sdp, res, 1, false)","handlingStrategy":"type-guard","validationCode":"func (c *TuyaMqttClient) SafeSend(fn func() error) error {\n    if c.closed { return errors.New(\"client closed\") }\n    return fn()\n}","typeGuard":"func usable(c *TuyaMqttClient) bool { return c != nil && !c.closed }","tryCatchPattern":"if !usable(client) { client = createAndInitClient(); }\nif err := client.SendOffer(...); err != nil {\n    if strings.Contains(err.Error(), \"client is closed\") { recreate and retry }\n}","preventionTips":["Centralize client lifecycle (single owner closes it)","Wait for in-flight sends before Close via WaitGroup","Replace client references after close rather than reusing them"],"tags":["go","mqtt","lifecycle","closed-client","tuya"],"backgroundTag":"invalid-state-transition","analyzedSha":"c245815e75e2a5fd60b4290f12bfc04e55a984d3","analyzedAt":"2026-09-07T11:47:02.965Z","contentChangedAt":"2026-09-07T11:47:02.965Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}