fix(messages-recv): use consistent transaction key for session operations
CRITICAL FIX: Addresses Codex Bot comment on PR #79 about transaction key mismatch. Problem: Session delete operations used `delete-session-${sessionId}` as transaction key, while encrypt/decrypt operations in sendMessage() use `meId` as key. Different keys = different mutexes = operations can run concurrently = race condition. Timeline before fix: T0: sendMessage() → transaction(meId) → mutex_meId acquired T1: Encrypt uses session X T2: shouldRecreateSession() → transaction(delete-session-X) → mutex_delete acquired T3: Delete session X ← CONCURRENT! T4: sendMessage() tries to use session X → CRASH Changes: - Line 472: Change key from `delete-session-${sessionId}` to `authState.creds.me?.id` - Line 1034: Same change for outgoing retry deletion - Now all session operations (read/write/delete/encrypt) share same mutex - Operations are properly serialized, preventing concurrent access After fix: T0: sendMessage() → transaction(meId) → mutex_meId acquired T1: shouldRecreateSession() → transaction(meId) → waits for mutex T2: sendMessage() completes → mutex released T3: shouldRecreateSession() acquires mutex → deletes session safely Validation: - ✅ Both session deletions now use same key as encrypt operations - ✅ Cross-file contract respected (messages-send.ts:1385 uses meId) - ✅ Race condition eliminated via mutex serialization https://claude.ai/code/session_VMxqX
This commit is contained in:
@@ -465,10 +465,11 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
if (shouldRecreateSession) {
|
if (shouldRecreateSession) {
|
||||||
logger.debug({ fromJid, retryCount, reason: recreateReason, errorCode }, 'recreating session for retry')
|
logger.debug({ fromJid, retryCount, reason: recreateReason, errorCode }, 'recreating session for retry')
|
||||||
// Delete existing session to force recreation
|
// Delete existing session to force recreation
|
||||||
// CRITICAL: Wrap in transaction to prevent race with other session operations
|
// CRITICAL: Use same transaction key as encrypt/decrypt operations to prevent race
|
||||||
|
// Using meId ensures this delete serializes with sendMessage() and other session operations
|
||||||
await authState.keys.transaction(async () => {
|
await authState.keys.transaction(async () => {
|
||||||
await authState.keys.set({ session: { [sessionId]: null } })
|
await authState.keys.set({ session: { [sessionId]: null } })
|
||||||
}, `delete-session-${sessionId}`)
|
}, authState.creds.me?.id || 'session-operation')
|
||||||
forceIncludeKeys = true
|
forceIncludeKeys = true
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1026,10 +1027,11 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
|
|||||||
|
|
||||||
if (shouldRecreateSession) {
|
if (shouldRecreateSession) {
|
||||||
logger.debug({ participant, retryCount, reason: recreateReason, errorCode }, 'recreating session for outgoing retry')
|
logger.debug({ participant, retryCount, reason: recreateReason, errorCode }, 'recreating session for outgoing retry')
|
||||||
// CRITICAL: Wrap in transaction to prevent race with other session operations
|
// CRITICAL: Use same transaction key as encrypt/decrypt operations to prevent race
|
||||||
|
// Using meId ensures this delete serializes with sendMessage() and other session operations
|
||||||
await authState.keys.transaction(async () => {
|
await authState.keys.transaction(async () => {
|
||||||
await authState.keys.set({ session: { [sessionId]: null } })
|
await authState.keys.set({ session: { [sessionId]: null } })
|
||||||
}, `delete-session-${sessionId}`)
|
}, authState.creds.me?.id || 'session-operation')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry')
|
logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry')
|
||||||
|
|||||||
Reference in New Issue
Block a user