perf(messages): enable parallel message processing with KeyedMutex + robust fallback handling

This commit implements KeyedMutex for parallel message processing across different chats
while addressing Copilot PR #74 review concerns about edge case handling.

Key Changes:
1. Replace global messageMutex with KeyedMutex (per-chat locking)
   - src/Socket/chats.ts: Import makeKeyedMutex, update initialization
   - Messages from different chats now process in parallel
   - Messages within same chat maintain sequential order (preserves integrity)

2. Implement robust fallback chain for mutex keys
   - Primary: msg.key.remoteJid (always present in practice)
   - Secondary: msg.key.id (unique per message, enables parallelism in edge cases)
   - Tertiary: 'unknown' (serializes only truly malformed messages)

3. Add defensive logging for edge cases
   - Logs warnings when remoteJid is missing (should never happen)
   - Includes msg.key.id in logs for traceability

Performance Impact:
- Before: ALL messages serialized (10 messages from 10 chats = 10x latency)
- After: Parallel processing per chat (10 messages from 10 chats = ~1x latency)
- Edge case: Even if remoteJid missing, uses msg.key.id instead of serializing all

Safety Guarantees:
-  Message order preserved within same conversation (interactive messages safe)
-  No impact on buttons, lists, carousels, or any interactive message types
-  Defensive programming prevents performance degradation in edge cases
-  Logging enables debugging of unexpected scenarios

Addresses:
- Copilot PR #74 comments 1, 2, 4, 5 (fallback handling)
- Original performance issue (message delivery latency)

https://claude.ai/code/session_0149ZKk2ygmKCJTGu39Mr8oH
This commit is contained in:
Claude
2026-02-03 13:32:47 +00:00
parent 1dcf95e3b9
commit d6830c957e
3 changed files with 26 additions and 7 deletions
+15 -3
View File
@@ -1639,7 +1639,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
// Emit own event for album root if configured
if (config.emitOwnEvents) {
process.nextTick(async () => {
await messageMutex.mutex(() => upsertMessage(albumRootMsg, 'append'))
const mutexKey = albumRootMsg.key.remoteJid || (() => {
logger.warn({ msgId: albumRootMsg.key.id }, 'Missing remoteJid in albumRootMsg, using msg.key.id as fallback')
return albumRootMsg.key.id || 'unknown'
})()
await messageMutex.mutex(mutexKey, () => upsertMessage(albumRootMsg, 'append'))
})
}
@@ -1732,7 +1736,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
// Emit own event if configured
if (config.emitOwnEvents) {
process.nextTick(async () => {
await messageMutex.mutex(() => upsertMessage(mediaMsg, 'append'))
const mutexKey = mediaMsg.key.remoteJid || (() => {
logger.warn({ msgId: mediaMsg.key.id }, 'Missing remoteJid in mediaMsg, using msg.key.id as fallback')
return mediaMsg.key.id || 'unknown'
})()
await messageMutex.mutex(mutexKey, () => upsertMessage(mediaMsg, 'append'))
})
}
@@ -1931,7 +1939,11 @@ export const makeMessagesSocket = (config: SocketConfig) => {
})
if (config.emitOwnEvents) {
process.nextTick(async () => {
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'))
const mutexKey = fullMsg.key.remoteJid || (() => {
logger.warn({ msgId: fullMsg.key.id }, 'Missing remoteJid in fullMsg, using msg.key.id as fallback')
return fullMsg.key.id || 'unknown'
})()
await messageMutex.mutex(mutexKey, () => upsertMessage(fullMsg, 'append'))
})
}