withYjs: one peer disconnecting wipes every peer's awareness for ~15s
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- typescript
- Domain
- backend, distributed-systems
Research direction
Start in packages/y-partyserver/src/server/index.ts, reading the awareness update handling around lines 285-292 and onClose around lines 497-509. Reproduce with three relaying peers in workerd, then verify that ownership is reclaimed on added or updated states and that disconnecting one connection removes only its owned awareness states.
Written by the indexing model from the issue text.
Description
What happens
In withYjs, the connection→clientID ownership map is built from the added array of awareness update events. added means "the document hasn't seen this clientID before", which is a property of the document, not of the connection that sent the update.
Yjs clients relay other peers' awareness states without preserving the origin. So when peer B's socket forwards peer A's state, A's clientID is added under B's connection, and B gets recorded as A's owner. removeAwarenessStates deletes from states but keeps meta, so a clientID is only ever added once. The misattribution is permanent for the object's lifetime.
On onClose, that connection's whole ownership list goes to removeAwarenessStates. So closing one connection removes the awareness state of every peer whose clientID it happened to relay first. The remaining peers can't see each other until Yjs re-announces local state, about 15 seconds later.
Where
packages/y-partyserver/src/server/index.ts on current main:
L285-292, the ownership map is credited with added:
if (conn !== null) {
const currentIds = new Set(getAwarenessIds(conn));
for (const clientID of added) currentIds.add(clientID);
for (const clientID of removed) currentIds.delete(clientID);
setAwarenessIds(conn, [...currentIds]);
}
L497-509, onClose removes it:
const controlledIds = getAwarenessIds(connection);
if (controlledIds.length > 0)
awarenessProtocol.removeAwarenessStates(this.document.awareness, controlledIds, null);
Reproduction
Three peers in one room, each sending only its own awareness state. Because clients relay, one connection ends up credited with the others' clientIDs. Close that connection and every peer's awareness is removed.
Reproduced in workerd (@cloudflare/vitest-pool-workers), both hibernate: true and hibernate: false. Present since #341 (2.1.0), still on main and in published 2.2.0.
Fix
Make ownership last-writer-wins and exclusive. Claim on added and updated so a peer's own refresh reclaims its clientID, and revoke the claimed ids from every other connection's list:
const claimed = new Set([...added, ...updated]);
for (const other of this.getConnections()) {
if (other === conn) continue;
const otherIds = getAwarenessIds(other);
const kept = otherIds.filter((id) => !claimed.has(id));
if (kept.length !== otherIds.length) setAwarenessIds(other, kept);
}
for (const clientID of claimed) currentIds.add(clientID);
A relayed clientID stays misattributed until its owner's next frame, so the window shrinks to the Yjs refresh interval rather than closing completely. Fully closing it would need an ownership signal in the protocol itself.
- Dominant language
- TypeScript
- Stars
- 1.3k
- Forks
- 81
- PR merge metrics
- No merged PRs in 30d
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from cloudflare/partykit
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
cloudflare/partykit#419 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
cloudflare/partykit#411 ·
-
[y-partyserver] provider.destroy() leaks reconnect timer, hanging Node processes and delaying GC Open
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
cloudflare/partykit#402 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
cloudflare/partykit#401 · 1 reaction ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
cloudflare/partykit#400 ·
All issues in cloudflare/partykit
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
copse-dev/agent-pane#2953 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Eynzof/Hermes-CN-Desktop#610 ·
-
bug clawsweeper:linked-pr-open clawsweeper:needs-live-repro clawsweeper:no-new-fix-pr impact:message-loss issue-rating: 🐚 platinum hermit P2 regression
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
calcite-components needs triage refactor
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Esri/calcite-design-system#15203 ·