Fix RecordNotUnique crash on concurrent sync attempt creation - #220
Merged
Conversation
CalendarSource#schedule_sync already rescues ActiveRecord::RecordNotUnique from the idx_unique_active_sync_attempt_per_source partial index, but two other code paths that create a queued SyncAttempt directly did not: SyncAttemptManageable#find_or_create_sync_attempt (used by SyncCalendarJob when invoked without an attempt_id, e.g. from EventMapping, and by PushStateJob) and AutoSyncScheduler#schedule_syncs. A concurrent sync for the same source could hit the unique constraint and crash the job. Fixes #119
This was referenced Sep 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CalendarSource#schedule_syncalready rescuesActiveRecord::RecordNotUniqueraised by theidx_unique_active_sync_attempt_per_sourcepartial unique index (only one activequeued/runningSyncAttemptper source), but two other code paths that create a queuedSyncAttemptdirectly did not have that protection.SyncAttemptManageable#find_or_create_sync_attempt(used bySyncCalendarJobwhen called without anattempt_id— e.g. fromEventMapping#schedule_affected_syncs— and byPushStateJob) now rescues the race and reuses the existing active attempt instead of crashing.AutoSyncScheduler#schedule_syncsnow rescues the same race (which can occur betweenfind_sources_due_for_syncand the per-sourcecreate!) and simply skips that source, since it's already covered by the other in-flight attempt.Fixes #119, which was a recurring Honeybadger crash:
ActiveRecord::RecordNotUnique: SQLite3::ConstraintException: UNIQUE constraint failed: sync_attempts.calendar_source_id.Test plan
SyncCalendarJobtest simulating a concurrent worker winning the race, asserting the job reuses the existing attempt instead of raising.AutoSyncSchedulertest simulating a concurrent attempt created betweenfind_sources_due_for_syncandschedule_syncs, asserting that source is skipped and the other source still gets scheduled.bin/rails test(full suite, 1005 runs) passes.bundle exec rubocopon changed files — no new offenses.