{"record":{"id":"4cc6b5ffc7671a4c","repo":"nats-io/nats-server","slug":"another-session-is-in-use-with-client-id-q","errorCode":null,"errorMessage":"another session is in use with client ID %q","messagePattern":"another session is in use with client ID %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/mqtt.go","lineNumber":2517,"sourceCode":"// First check if this session's client ID is already in the \"locked\" map,\n// which if it is the case means that another client is now bound to this\n// session and this should return an error.\n// If not in the \"locked\" map, but the client is not bound with this session,\n// then same error is returned.\n// Finally, if all checks ok, then the session's ID is added to the \"locked\" map.\n//\n// No lock held on entry.\nfunc (as *mqttAccountSessionManager) lockSession(sess *mqttSession, c *client) error {\n\tas.mu.Lock()\n\tdefer as.mu.Unlock()\n\tvar fail bool\n\tif _, fail = as.sessLocked[sess.id]; !fail {\n\t\tsess.mu.Lock()\n\t\tfail = sess.c != c\n\t\tsess.mu.Unlock()\n\t}\n\tif fail {\n\t\treturn fmt.Errorf(\"another session is in use with client ID %q\", sess.id)\n\t}\n\tas.sessLocked[sess.id] = struct{}{}\n\treturn nil\n}\n\n// Remove the session from the \"locked\" map.\n//\n// No lock held on entry.\nfunc (as *mqttAccountSessionManager) unlockSession(sess *mqttSession) {\n\tas.mu.Lock()\n\tdelete(as.sessLocked, sess.id)\n\tas.mu.Unlock()\n}\n\n// Simply adds the session to the various sessions maps.\n// The boolean `lock` indicates if this function should acquire the lock\n// prior to adding to the maps.\n//","sourceCodeStart":2499,"sourceCodeEnd":2535,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/mqtt.go#L2499-L2535","documentation":"Returned by mqttAccountSessionManager.lockSession when a session for a given client ID cannot be acquired because it is already 'locked' by another in-flight client connection, or the session is currently bound to a different client (sess.c != c). The server rejects the second CONNECT takeover attempt so two live clients never share one MQTT session.","triggerScenarios":"Two MQTT clients connect concurrently with the same client ID; a previous connection for that client ID has not fully torn down when the new CONNECT is processed (lock still held in sessLocked, or sess.c still points at the old client).","commonSituations":"Load balancer or test harness reconnecting faster than the old TCP connection is cleaned up; duplicated client ID in a device fleet; a stuck/zombie TCP half-open connection keeping the old session bound.","solutions":["Ensure each client uses a unique client ID","Retry the CONNECT after a short backoff so the old session can be unlocked (unlockSession runs on disconnect)","Investigate why the old connection lingers (half-open TCP, missing keepalive/disconnect) and reduce that window","If a client is stuck, close the old TCP connection to force session release"],"exampleFix":"// before\nconnect(client, clientID=\"device-1\", reconnectDelay=0)\n// after\nconnect(client, clientID=uuid(), reconnectDelay=2s + jitter) // or retry same ID after backoff","handlingStrategy":"retry","validationCode":"// Ensure client ID uniqueness before connecting\nif clientID == \"\" || !/^[A-Za-z0-9_-]{1,64}$/.test(clientID) {\n  clientID = `client-${crypto.randomUUID()}`\n}\nif (activeConnections.has(clientID)) throw new Error('client ID already connected')","typeGuard":null,"tryCatchPattern":"// catch the CONNECT error and retry with backoff\ntry {\n  conn = await connect({ clientID })\n} catch (e) {\n  if (String(e).includes('another session is in use')) {\n    await sleep(backoff++)\n    conn = await connect({ clientID })\n  } else { throw e }\n}","preventionTips":["Generate a unique client ID per process/device instance","Add reconnect backoff with jitter instead of immediate reconnect","Enable keepalive so dead connections are detected and sessions released","Monitor for duplicate client ID logs in the fleet"],"tags":["mqtt","session-conflict","client-id"],"backgroundTag":"mqtt-client-id-taken","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}