HarperFast / HarperFast/harper
MQTT: implement duplicate-clientId session takeover (will-message + durable-session races, esp. multi-worker)
- Dominant language
- JavaScript
- Stars
- 89
- Forks
- 10
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 200
Description
## Background
Surfaced during review of #1603 (which makes MQTT share its port across workers via `SO_REUSEPORT` on Linux). The concern is **pre-existing** — it is not introduced by #1603 — but multi-worker MQTT makes it materially more reachable, so it's worth tracking.
## Problem
MQTT does not implement duplicate-`clientId` **session takeover**. Per MQTT 3.1.1 §3.1.4, when a client connects with a `ClientId` that already has a live connection, the server MUST disconnect the existing connection first. `getSession()` in `server/DurableSubscriptionsSession.ts` creates/loads the session from the shared `hdb_session*` tables but never disconnects a prior live connection for the same `clientId` (there's no cross-connection/cross-worker registry check).
Consequences, both racing on the shared LMDB tables when a client reconnects (without a clean DISCONNECT) and the old connection is still half-open:
1. **Will-message race** — when the old connection's keepalive finally expires, its `disconnect(false)` publishes the will found in `hdb_session_will` and deletes it. But the *new* connection wrote its own will to the same shared record, so the old worker can publish the currently-connected client's will and delete it.
2. **Durable-session overwrite** — two live `DurableSubscriptionsSession` instances (potentially on different workers) mapped to the same `hdb_session` record can overwrite each other's `startTime` / `subscriptions`.
Single-worker exhibits (1) already; port-sharing across workers widens both because a reconnect can land on a different worker than the prior connection, with no shared in-memory visibility.
## Direction (not yet designed)
Needs a cross-worker view of active `clientId`s (or a takeover signal) so a new connection can evict the prior one before taking over the durable session — likely coordinated through the shared session table + `transactionBroadcast`, rather than the per-worker in-memory `mqttSettings.sessions` set. Retained-message delivery and cross-worker propagation are already handled correctly by the DB layer.
_Filed by Claude (Opus 4.8) as a follow-up to #1603._
Contributor guide
Research direction
Start by tracing getSession() in server/DurableSubscriptionsSession.ts, then inspect the old connection's disconnect(false) path and the shared hdb_session, hdb_session_will, and transactionBroadcast behavior. The work is complete when duplicate clientId connections coordinate across workers so the prior connection is evicted before session takeover, without will-message or durable-session overwrites.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- database, distributed-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100