fix(inbound): ensure mutex holds until both post-upsert tasks settle

Addresses Copilot review on PR #392.

A plain `Promise.all([doAppStateSync(), processMessage(...)])` rejects on the
first task that fails. When that happens inside the messageMutex callback,
the mutex releases as soon as the rejection settles even though the other
task is still running. The next message for the same chat can then overtake
the in-flight task and break the per-chat ordering guarantee that was the
whole point of the inner mutex.

Wrap each task in its own `.catch` that logs and resolves. The combined
Promise.all now only settles after BOTH operations finish (success or
failure), so the mutex is held for the full duration of the work and
ordering is preserved. Errors are still surfaced via the per-task warnings.
This commit is contained in:
Renato Alcara
2026-04-26 14:50:14 -03:00
parent 35461f96c5
commit b90b383811
+20 -2
View File
@@ -1416,9 +1416,22 @@ export const makeChatsSocket = (config: SocketConfig) => {
// (createBufferedFunction only schedules flush after work() resolves), so
// the hot path detaches this work to release the emit on the next debounce
// tick.
//
// Each task is wrapped in its own .catch so the combined promise only
// settles after BOTH tasks finish. With a plain Promise.all, an early
// rejection from one task would release the keyed mutex while the other
// task is still mutating chat state — letting the next message of the
// same chat overtake it and break per-chat ordering.
const postUpsertTasks = () =>
Promise.all([
shouldProcessHistoryMsg ? doAppStateSync() : Promise.resolve(),
shouldProcessHistoryMsg
? doAppStateSync().catch(err =>
logger?.warn(
{ err, messageId: msg.key?.id, remoteJid: msg.key?.remoteJid },
'history doAppStateSync failed'
)
)
: Promise.resolve(),
processMessage(msg, {
signalRepository,
shouldProcessHistoryMsg,
@@ -1429,7 +1442,12 @@ export const makeChatsSocket = (config: SocketConfig) => {
logger,
options: config.options,
getMessage
})
}).catch(err =>
logger?.warn(
{ err, messageId: msg.key?.id, remoteJid: msg.key?.remoteJid },
'processMessage failed'
)
)
])
const isKeyShareDuringSync =