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

  1. Guard call sites with the client's closed/connection state before any Send* call
  2. Recreate or reinitialize the client after Close instead of reusing it
  3. Track lifecycle so Shutdown waits for in-flight sends before closing
  4. 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

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


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)