fix(agent-presence): upsert atomically — kill first-time-insert race (#3)
This commit was merged in pull request #3.
This commit is contained in:
@@ -39,15 +39,23 @@ export class AgentPresenceService {
|
|||||||
* Upsert a user's presence. Source is a free-text tag for debugging
|
* Upsert a user's presence. Source is a free-text tag for debugging
|
||||||
* (e.g. "hf-plugin", "manual", "test"). PUT /agents/:id/presence
|
* (e.g. "hf-plugin", "manual", "test"). PUT /agents/:id/presence
|
||||||
* calls this; the plugin pushes only on diff so writes are sparse.
|
* calls this; the plugin pushes only on diff so writes are sparse.
|
||||||
|
*
|
||||||
|
* Implementation note: the older findOne+save split was a read-modify-
|
||||||
|
* write race — two concurrent first-time writes for the same userId
|
||||||
|
* would both read no row, both INSERT, second hits unique-key dup
|
||||||
|
* (`agent_presences.PRIMARY`) and 500s. Fabric.OpenclawPlugin's
|
||||||
|
* presence-sync occasionally fires two PUTs for the same agent within
|
||||||
|
* ~10 ms (tick overlap on its side — separate fix in the plugin),
|
||||||
|
* which surfaced this race in prod.
|
||||||
|
*
|
||||||
|
* `repo.upsert(values, conflictPaths)` compiles to MySQL
|
||||||
|
* `INSERT … ON DUPLICATE KEY UPDATE` and is atomic at the storage
|
||||||
|
* engine level — no read needed, no race window. We synthesize the
|
||||||
|
* returned entity from what we just wrote rather than round-tripping
|
||||||
|
* a SELECT — the controller only reads {userId, status} off it.
|
||||||
*/
|
*/
|
||||||
async setStatus(userId: string, status: PresenceStatus, source: string): Promise<AgentPresence> {
|
async setStatus(userId: string, status: PresenceStatus, source: string): Promise<AgentPresence> {
|
||||||
const existing = await this.repo.findOne({ where: { userId } });
|
await this.repo.upsert({ userId, status, source }, ['userId']);
|
||||||
if (existing) {
|
return this.repo.create({ userId, status, source });
|
||||||
existing.status = status;
|
|
||||||
existing.source = source;
|
|
||||||
return this.repo.save(existing);
|
|
||||||
}
|
|
||||||
const row = this.repo.create({ userId, status, source });
|
|
||||||
return this.repo.save(row);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user