Skip to content

Fix unique-job lock leak in inline testing mode - #3

Closed
lovitt wants to merge 1 commit into
masterfrom
fix/inline-unique-job-zombie-lock
Closed

Fix unique-job lock leak in inline testing mode#3
lovitt wants to merge 1 commit into
masterfrom
fix/inline-unique-job-zombie-lock

Conversation

@lovitt

@lovitt lovitt commented Jun 26, 2026

Copy link
Copy Markdown
Member

Problem

In inline mode (Cloudtasker::Testing.inline!), a unique job runs only the first time it's enqueued — later enqueues of the same job are silently rejected.

lock_for_scheduling! (added in 0.15.0) takes a provisional lock, yields to enqueue the job, then takes the final lock. That final lock is meant to be released later, when the job runs — the execute middleware unlocks it.

But in inline mode the job runs during the yield, so it unlocks before the final lock is even taken. The final lock is then set after the job has already finished, leaving it with no future run to release it. It lingers for the full lock_ttl and rejects later enqueues of the same job (on_conflict: :reject). 0.14.0 did a plain lock!; yield, so this is a regression.

Cloudtasker::Testing.inline! do
  TestUniqueJobWorker.perform_async(1, 2)
  TestUniqueJobWorker.perform_async(1, 2)
end
# before: [[1, 2]]            (second rejected)
# after:  [[1, 2], [1, 2]]

Fix

Skip the final-lock step in inline mode, where the job has already run and released its lock. The async/production path is unchanged: inline_mode? is guarded by defined?(Cloudtasker::Testing), which is false in production (same idiom as CloudTask.backend).

Tests

Integration spec (both inline enqueues run) and unit spec (lock not promoted to the final TTL).

@lovitt
lovitt force-pushed the fix/inline-unique-job-zombie-lock branch from 8c7a4b5 to af64897 Compare June 26, 2026 14:49
@lovitt lovitt changed the title Fix unique-job lock leak under inline execution mode Fix unique-job lock leak in inline testing mode Jun 26, 2026
@lovitt
lovitt force-pushed the fix/inline-unique-job-zombie-lock branch 2 times, most recently from d9f8995 to ab9ae4f Compare June 26, 2026 15:00
The provisional/final lock split added in 0.15.0 assumes the block
passed to `lock_for_scheduling!` only enqueues the job. Under inline
testing mode (`Cloudtasker::Testing.inline!`) the block also executes
the job synchronously, which releases the job's lock via the execute
middleware. Step 3 then re-acquires a "final" lock that nothing will
ever release, so a subsequent enqueue of the same unique job is
wrongly rejected for the full lock TTL.

Skip the final-lock step under inline execution: the job has already
run and released its lock by the time scheduling returns. The async
(production) path is unchanged.

Adds an integration spec (two successive inline enqueues of the same
unique job both run) and a unit spec (the lock is not promoted to the
final TTL in inline mode).
@lovitt
lovitt force-pushed the fix/inline-unique-job-zombie-lock branch from ab9ae4f to 62213f8 Compare June 26, 2026 15:11
@lovitt

lovitt commented Jun 26, 2026

Copy link
Copy Markdown
Member Author

Superseded by the upstream PR keypup-io#173, which carries the same fix based on keypup-io/master. Closing in favor of that one.

@lovitt lovitt closed this Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant