From d6af182f82e8e082950b33e14fd0b909b7e9b7e0 Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Wed, 12 Nov 2025 16:56:52 +0530 Subject: [PATCH] fix: Use contact_id instead of sender_id for Instagram message locks (#12841) Previously, the lock key for Instagram used sender_id, which for echo messages (outgoing) would be the account's own ID. This caused all outgoing messages to compete for the same lock, creating a bottleneck during bulk messaging. The fix introduces contact_instagram_id method that correctly identifies the contact's ID regardless of message direction: - For echo messages (outgoing): uses recipient.id (the contact) - For incoming messages: uses sender.id (the contact) This ensures each conversation has a unique lock, allowing parallel processing of webhooks while maintaining race condition protection within individual conversations. Fixes lock acquisition errors in Sidekiq when processing bulk Instagram messages. Fixes https://linear.app/chatwoot/issue/CW-5931/p0-mutexapplicationjoblockacquisitionerror-failed-to-acquire-lock-for ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) --- app/jobs/webhooks/instagram_events_job.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/app/jobs/webhooks/instagram_events_job.rb b/app/jobs/webhooks/instagram_events_job.rb index 3db8b6ba9..6383daff8 100644 --- a/app/jobs/webhooks/instagram_events_job.rb +++ b/app/jobs/webhooks/instagram_events_job.rb @@ -8,7 +8,7 @@ class Webhooks::InstagramEventsJob < MutexApplicationJob def perform(entries) @entries = entries - key = format(::Redis::Alfred::IG_MESSAGE_MUTEX, sender_id: sender_id, ig_account_id: ig_account_id) + key = format(::Redis::Alfred::IG_MESSAGE_MUTEX, sender_id: contact_instagram_id, ig_account_id: ig_account_id) with_lock(key) do process_entries(entries) end @@ -77,6 +77,23 @@ class Webhooks::InstagramEventsJob < MutexApplicationJob @entries&.first&.dig(:id) end + def contact_instagram_id + entry = @entries&.first + return nil unless entry + + # Handle both messaging and standby arrays + messaging = (entry[:messaging].presence || entry[:standby] || []).first + return nil unless messaging + + # For echo messages (outgoing from our account), use recipient's ID (the contact) + # For incoming messages (from contact), use sender's ID (the contact) + if messaging.dig(:message, :is_echo) + messaging.dig(:recipient, :id) + else + messaging.dig(:sender, :id) + end + end + def sender_id @entries&.dig(0, :messaging, 0, :sender, :id) end