perf(messages): enable parallel message processing

perf(messages): enable parallel message processing with KeyedMutex + …
This commit is contained in:
Renato Alcara
2026-02-03 11:14:52 -03:00
committed by GitHub
3 changed files with 40 additions and 8 deletions
+3 -3
View File
@@ -41,7 +41,7 @@ import {
newLTHashState, newLTHashState,
processSyncAction processSyncAction
} from '../Utils' } from '../Utils'
import { makeMutex } from '../Utils/make-mutex' import { makeMutex, makeKeyedMutex } from '../Utils/make-mutex'
import processMessage from '../Utils/process-message' import processMessage from '../Utils/process-message'
import { buildTcTokenFromJid } from '../Utils/tc-token-utils' import { buildTcTokenFromJid } from '../Utils/tc-token-utils'
import { import {
@@ -74,8 +74,8 @@ export const makeChatsSocket = (config: SocketConfig) => {
let syncState: SyncState = SyncState.Connecting let syncState: SyncState = SyncState.Connecting
/** this mutex ensures that messages are processed in order */ /** this mutex ensures that messages from the same chat are processed in order, while allowing parallel processing of messages from different chats */
const messageMutex = makeMutex() const messageMutex = makeKeyedMutex()
/** this mutex ensures that receipts are processed in order */ /** this mutex ensures that receipts are processed in order */
const receiptMutex = makeMutex() const receiptMutex = makeMutex()
+19 -2
View File
@@ -1265,8 +1265,24 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
) )
} }
// CRITICAL: Normalize JIDs BEFORE acquiring mutex to ensure messages from the same
// chat (arriving with different JID formats - LID vs PN) use the SAME mutex key.
// This prevents parallel processing of messages from the same conversation which
// would break message ordering guarantees.
// Addresses Copilot/Codex PR #75 critical review: JID normalization vulnerability
await normalizeMessageJids(msg, signalRepository, logger)
try { try {
await messageMutex.mutex(async () => { // Use KeyedMutex with NORMALIZED remoteJid for parallel processing across different chats
// while maintaining sequential order within the same chat
// Fallback chain: remoteJid (normalized) > msg.key.id (unique) > 'unknown' (serializes all)
let mutexKey = msg.key.remoteJid
if (!mutexKey) {
logger.warn({ msgId: msg.key.id, fromMe: msg.key.fromMe }, 'Missing remoteJid after normalization, using msg.key.id as fallback')
mutexKey = msg.key.id || 'unknown'
}
await messageMutex.mutex(mutexKey, async () => {
await decrypt() await decrypt()
// message failed to decrypt // message failed to decrypt
if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') { if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== 'peer') {
@@ -1480,7 +1496,8 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
} }
} }
await normalizeMessageJids(msg, signalRepository, logger) // JID normalization moved BEFORE mutex acquisition (line 1273) to prevent race conditions
// cleanMessage still runs inside mutex to ensure atomic message processing
cleanMessage(msg, authState.creds.me!.id, authState.creds.me!.lid!) cleanMessage(msg, authState.creds.me!.id, authState.creds.me!.lid!)
await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify') await upsertMessage(msg, node.attrs.offline ? 'append' : 'notify')
+18 -3
View File
@@ -1639,7 +1639,12 @@ export const makeMessagesSocket = (config: SocketConfig) => {
// Emit own event for album root if configured // Emit own event for album root if configured
if (config.emitOwnEvents) { if (config.emitOwnEvents) {
process.nextTick(async () => { process.nextTick(async () => {
await messageMutex.mutex(() => upsertMessage(albumRootMsg, 'append')) let mutexKey = albumRootMsg.key.remoteJid
if (!mutexKey) {
logger.warn({ msgId: albumRootMsg.key.id }, 'Missing remoteJid in albumRootMsg, using msg.key.id as fallback')
mutexKey = albumRootMsg.key.id || 'unknown'
}
await messageMutex.mutex(mutexKey, () => upsertMessage(albumRootMsg, 'append'))
}) })
} }
@@ -1732,7 +1737,12 @@ export const makeMessagesSocket = (config: SocketConfig) => {
// Emit own event if configured // Emit own event if configured
if (config.emitOwnEvents) { if (config.emitOwnEvents) {
process.nextTick(async () => { process.nextTick(async () => {
await messageMutex.mutex(() => upsertMessage(mediaMsg, 'append')) let mutexKey = mediaMsg.key.remoteJid
if (!mutexKey) {
logger.warn({ msgId: mediaMsg.key.id }, 'Missing remoteJid in mediaMsg, using msg.key.id as fallback')
mutexKey = mediaMsg.key.id || 'unknown'
}
await messageMutex.mutex(mutexKey, () => upsertMessage(mediaMsg, 'append'))
}) })
} }
@@ -1931,7 +1941,12 @@ export const makeMessagesSocket = (config: SocketConfig) => {
}) })
if (config.emitOwnEvents) { if (config.emitOwnEvents) {
process.nextTick(async () => { process.nextTick(async () => {
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append')) let mutexKey = fullMsg.key.remoteJid
if (!mutexKey) {
logger.warn({ msgId: fullMsg.key.id }, 'Missing remoteJid in fullMsg, using msg.key.id as fallback')
mutexKey = fullMsg.key.id || 'unknown'
}
await messageMutex.mutex(mutexKey, () => upsertMessage(fullMsg, 'append'))
}) })
} }