fix: atomically claim conversation to prevent duplicate assignment (#14495)
## Description Fixes a bug under Assignment V2 where a single conversation could be reassigned dozens of times in a row by the system, producing long stacks of "Assigned to X by Automation System via <policy>" activity messages alternating between agents. After this change each unassigned conversation is assigned exactly once, even on busy inboxes. ## Fixes # (issue) ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? ## How to reproduce 1. Enable `assignment_v2` on an account with at least 2 online agents in an inbox. 2. Generate sustained resolve/snooze activity in the inbox (each one enqueues `AutoAssignment::AssignmentJob` for the whole inbox). 3. Watch any one unassigned conversation while the jobs drain — pre-fix it picks up multiple back-to-back "Assigned to …" activity rows alternating between agents. ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules
This commit is contained in:
@@ -25,6 +25,18 @@ module Redis::Alfred
|
||||
$alfred.with { |conn| conn.del(key) }
|
||||
end
|
||||
|
||||
# atomic compare-and-delete (release a lock only if you still own it); WATCH/MULTI
|
||||
# aborts the delete if the key changes between the check and the delete.
|
||||
def delete_if_equals(key, expected_value)
|
||||
$alfred.with do |conn|
|
||||
conn.watch(key) do
|
||||
next conn.unwatch unless conn.get(key) == expected_value
|
||||
|
||||
conn.multi { |transaction| transaction.del(key) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# increment a key by 1. throws error if key value is incompatible
|
||||
# sets key to 0 before operation if key doesn't exist
|
||||
def incr(key)
|
||||
|
||||
@@ -73,6 +73,8 @@ module Redis::RedisKeys
|
||||
# Track conversation assignments to agents for rate limiting
|
||||
ASSIGNMENT_KEY = 'ASSIGNMENT::%<inbox_id>d::AGENT::%<agent_id>d::CONVERSATION::%<conversation_id>d'.freeze
|
||||
ASSIGNMENT_KEY_PATTERN = 'ASSIGNMENT::%<inbox_id>d::AGENT::%<agent_id>d::*'.freeze
|
||||
# At-most-one AssignmentJob per inbox in-flight (queued or running); further enqueues are skipped
|
||||
AUTO_ASSIGNMENT_IN_FLIGHT_KEY = 'AUTO_ASSIGNMENT_IN_FLIGHT::%<inbox_id>d'.freeze
|
||||
|
||||
## Account Onboarding
|
||||
ACCOUNT_ONBOARDING_ENRICHMENT = 'ONBOARDING_ENRICHMENT::%<account_id>d'.freeze
|
||||
|
||||
Reference in New Issue
Block a user