fix(messages-recv): wrap session deletions in transactions

CRITICAL FIX: Wraps session deletion operations in transactions to prevent
race conditions with concurrent session operations.

Changes:
- Wrap session deletion at line 468 (incoming retry) in transaction
- Wrap session deletion at line 1026 (outgoing retry) in transaction
- Use transaction key format: delete-session-${sessionId}

Problem before fix:
Session deletions happened OUTSIDE transactions while other operations
INSIDE transactions could be reading/writing the same session key.

Timeline of race condition:
T0: Message A arrives → processingMutex.mutex()
T1: Transaction started → reads session X
T2: Message B (retry) → shouldRecreateSession()
T3: Message B deletes session X ← OUTSIDE transaction
T4: Message A tries to use session X in transaction
T5: Session doesn't exist → decryption failure
T6: Message A lost

After fix:
All session operations (read/write/delete) are serialized via transactions,
preventing concurrent access and data corruption.

https://claude.ai/code/session_VMxqX
This commit is contained in:
Claude
2026-02-04 03:11:57 +00:00
parent f2e9701b9c
commit 209a55a8b7
+6
View File
@@ -465,7 +465,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
if (shouldRecreateSession) {
logger.debug({ fromJid, retryCount, reason: recreateReason, errorCode }, 'recreating session for retry')
// Delete existing session to force recreation
// CRITICAL: Wrap in transaction to prevent race with other session operations
await authState.keys.transaction(async () => {
await authState.keys.set({ session: { [sessionId]: null } })
}, `delete-session-${sessionId}`)
forceIncludeKeys = true
}
} catch (error) {
@@ -1023,7 +1026,10 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
if (shouldRecreateSession) {
logger.debug({ participant, retryCount, reason: recreateReason, errorCode }, 'recreating session for outgoing retry')
// CRITICAL: Wrap in transaction to prevent race with other session operations
await authState.keys.transaction(async () => {
await authState.keys.set({ session: { [sessionId]: null } })
}, `delete-session-${sessionId}`)
}
} catch (error) {
logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry')