-
-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Close race condition in procrastinate_fetch_job
This closes a race condition in the procrastinate_fetch_job plpgsql function, where jobs sharing the same lock can be run out of order. With this commit jobs with the same lock are **always** executed in order, whatever their ETAs and queues. In effect: - if job A in queue 1 (id 1) and job B in queue 2 (id 2) have the same lock, and no workers process queue 1, then job B won't be executed, because job A must be executed first - if job A is deferred with ETA 1 year, no other jobs with the same lock will be executed for 1 year The lock name may change from "lock" to "serial lock" in the future.
- Loading branch information
Éric Lemoine
committed
Jun 5, 2020
1 parent
5e47d99
commit 66b3070
Showing
2 changed files
with
76 additions
and
36 deletions.
There are no files selected for viewing
46 changes: 46 additions & 0 deletions
46
procrastinate/sql/migrations/delta_0.8.1_002_close_fetch_job_race_condition.sql
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
DROP TABLE IF EXISTS procrastinate_job_locks; | ||
|
||
CREATE UNIQUE INDEX procrastinate_jobs_lock_idx ON procrastinate_jobs (lock) WHERE status = 'doing'; | ||
|
||
CREATE OR REPLACE FUNCTION procrastinate_fetch_job(target_queue_names character varying[]) RETURNS procrastinate_jobs | ||
LANGUAGE plpgsql | ||
AS $$ | ||
DECLARE | ||
found_jobs procrastinate_jobs; | ||
BEGIN | ||
WITH candidate AS ( | ||
SELECT jobs.* | ||
FROM procrastinate_jobs AS jobs | ||
LEFT JOIN procrastinate_jobs AS earlier_jobs | ||
ON (earlier_jobs.lock = jobs.lock | ||
AND earlier_jobs.status IN ('todo', 'doing') | ||
AND earlier_jobs.id < jobs.id) | ||
WHERE | ||
earlier_jobs.id IS NULL | ||
AND jobs.status = 'todo' | ||
AND (target_queue_names IS NULL OR jobs.queue_name = ANY( target_queue_names )) | ||
AND (jobs.scheduled_at IS NULL OR jobs.scheduled_at <= now()) | ||
ORDER BY jobs.id ASC LIMIT 1 | ||
FOR UPDATE OF jobs SKIP LOCKED | ||
) | ||
UPDATE procrastinate_jobs | ||
SET status = 'doing' | ||
FROM candidate | ||
WHERE procrastinate_jobs.id = candidate.id | ||
RETURNING procrastinate_jobs.* INTO found_jobs; | ||
|
||
RETURN found_jobs; | ||
END; | ||
$$; | ||
|
||
CREATE OR REPLACE FUNCTION procrastinate_finish_job(job_id integer, end_status procrastinate_job_status, next_scheduled_at timestamp with time zone) RETURNS void | ||
LANGUAGE plpgsql | ||
AS $$ | ||
BEGIN | ||
UPDATE procrastinate_jobs | ||
SET status = end_status, | ||
attempts = attempts + 1, | ||
scheduled_at = COALESCE(next_scheduled_at, scheduled_at) | ||
WHERE id = job_id; | ||
END; | ||
$$; |
This file contains 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