AlexxIT/go2rtc · error
mqtt client is closed, send mqtt message fail
Error message
mqtt client is closed, send mqtt message fail
What it means
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.
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
Example fix
// before
client.Close()
client.SendOffer(sdp, res, 1, false) // fails
// after
client.Close()
client = tuya.NewMqttClient(...)
if err := client.Init(); err != nil { return err }
err := client.SendOffer(sdp, res, 1, false) Defensive patterns
Strategy: type-guard
Validate before calling
func (c *TuyaMqttClient) SafeSend(fn func() error) error {
if c.closed { return errors.New("client closed") }
return fn()
} Type guard
func usable(c *TuyaMqttClient) bool { return c != nil && !c.closed } Try / catch
if !usable(client) { client = createAndInitClient(); }
if err := client.SendOffer(...); err != nil {
if strings.Contains(err.Error(), "client is closed") { recreate and retry }
} Prevention
- 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
When it happens
Trigger: Calling SendOffer, SendCandidate, SendResolution, SendSpeaker, or SendDisconnect after TuyaMqttClient.Close() has been called, or reusing a client instance from another goroutine after close.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- openIoTHubConfigResponse.Msg
- failed to publish wake-up message
- failed to subscribe to lowPower topic
- loginResp.ErrorMsg
- tuya:
AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07).
Data as JSON: /api/errors/047a536e898faafd.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/tuya/mqtt.go:400
c.handleCandidate(candidate)
}
}
func (c *TuyaMqttClient) onDisconnect() {
if c.handleDisconnect != nil {
c.handleDisconnect()
}
}
func (c *TuyaMqttClient) onError(err error) {
if c.handleError != nil {
c.handleError(err)
}
}
func (c *TuyaMqttClient) sendMqttMessage(messageType string, protocol int, transactionID string, data interface{}) error {
if c.closed {
return fmt.Errorf("mqtt client is closed, send mqtt message fail")
}
jsonMessage, err := json.Marshal(data)
if err != nil {
return err
}
msg := &MqttMessage{
Protocol: protocol,
Pv: "2.2",
T: time.Now().Unix(),
Data: MqttFrame{
Header: MqttFrameHeader{
Type: messageType,
From: c.uid,
To: c.deviceId,
SessionID: c.sessionId,
MotoID: c.motoId,View on GitHub (pinned to c245815e75)