From 82f099e83c6a2d07d63a6e5da62bde371a33ab84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Wed, 21 Jan 2026 13:19:50 +0100 Subject: [PATCH 1/2] [IMP] queue_job: prevent commit during queue job execution This would release the job lock, causing spurious restarts by the dead jobs requeuer. --- queue_job/controllers/main.py | 58 +++++++++++++++++++++++++++++------ queue_job/models/queue_job.py | 4 ++- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py index ecdff1b8eb..c98f6305df 100644 --- a/queue_job/controllers/main.py +++ b/queue_job/controllers/main.py @@ -6,6 +6,7 @@ import random import time import traceback +from contextlib import contextmanager from io import StringIO from psycopg2 import OperationalError, errorcodes @@ -26,6 +27,29 @@ DEPENDS_MAX_TRIES_ON_CONCURRENCY_FAILURE = 5 +@contextmanager +def _prevent_commit(cr): + """Context manager to prevent commits on a cursor. + + Commiting while the job is not finished would release the job lock, causing + it to be started again by the dead jobs requeuer. + """ + + def forbidden_commit(*args, **kwargs): + raise RuntimeError( + "Commit is forbidden in queue jobs. " + "If the current job is a cron running as queue job, " + "modify it to run as a normal cron." + ) + + original_commit = cr.commit + cr.commit = forbidden_commit + try: + yield + finally: + cr.commit = original_commit + + class RunJobController(http.Controller): @classmethod def _acquire_job(cls, env: api.Environment, job_uuid: str) -> Job | None: @@ -69,13 +93,16 @@ def _acquire_job(cls, env: api.Environment, job_uuid: str) -> Job | None: def _try_perform_job(cls, env, job): """Try to perform the job, mark it done and commit if successful.""" _logger.debug("%s started", job) - job.perform() - # Triggers any stored computed fields before calling 'set_done' - # so that will be part of the 'exec_time' - env.flush_all() - job.set_done() - job.store() - env.flush_all() + # TODO refactor, the relation between env and job.env is not clear + assert env.cr is job.env.cr + with _prevent_commit(env.cr): + job.perform() + # Triggers any stored computed fields before calling 'set_done' + # so that will be part of the 'exec_time' + env.flush_all() + job.set_done() + job.store() + env.flush_all() env.cr.commit() _logger.debug("%s done", job) @@ -201,6 +228,7 @@ def create_test_job( size=1, failure_rate=0, job_duration=0, + commit_within_job=False, ): if not http.request.env.user.has_group("base.group_erp_manager"): raise Forbidden(_("Access Denied")) @@ -246,6 +274,7 @@ def create_test_job( description=description, failure_rate=failure_rate, job_duration=job_duration, + commit_within_job=commit_within_job, ) if size > 1: @@ -257,6 +286,7 @@ def create_test_job( description=description, failure_rate=failure_rate, job_duration=job_duration, + commit_within_job=commit_within_job, ) return "" @@ -269,6 +299,7 @@ def _create_single_test_job( size=1, failure_rate=0, job_duration=0, + commit_within_job=False, ): delayed = ( http.request.env["queue.job"] @@ -278,7 +309,11 @@ def _create_single_test_job( channel=channel, description=description, ) - ._test_job(failure_rate=failure_rate, job_duration=job_duration) + ._test_job( + failure_rate=failure_rate, + job_duration=job_duration, + commit_within_job=commit_within_job, + ) ) return f"job uuid: {delayed.db_record().uuid}" @@ -293,6 +328,7 @@ def _create_graph_test_jobs( description="Test job", failure_rate=0, job_duration=0, + commit_within_job=False, ): model = http.request.env["queue.job"] current_count = 0 @@ -315,7 +351,11 @@ def _create_graph_test_jobs( max_retries=max_retries, channel=channel, description="%s #%d" % (description, current_count), - )._test_job(failure_rate=failure_rate, job_duration=job_duration) + )._test_job( + failure_rate=failure_rate, + job_duration=job_duration, + commit_within_job=commit_within_job, + ) ) grouping = random.choice(possible_grouping_methods) diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index b8975b878e..3d550bf33b 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -453,9 +453,11 @@ def related_action_open_record(self): ) return action - def _test_job(self, failure_rate=0, job_duration=0): + def _test_job(self, failure_rate=0, job_duration=0, commit_within_job=False): _logger.info("Running test job.") if random.random() <= failure_rate: raise JobError("Job failed") if job_duration: time.sleep(job_duration) + if commit_within_job: + self.env.cr.commit() # pylint: disable=invalid-commit From 283136fd643fed4403fe876ade33acd9ce5a5a5c Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 17 Feb 2026 14:19:34 +0000 Subject: [PATCH 2/2] [BOT] post-merge updates --- README.md | 2 +- queue_job/README.rst | 2 +- queue_job/__manifest__.py | 2 +- queue_job/static/description/index.html | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c4c309ed68..ec5dc193cf 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Available addons addon | version | maintainers | summary --- | --- | --- | --- [base_import_async](base_import_async/) | 18.0.1.0.0 | | Import CSV files in the background -[queue_job](queue_job/) | 18.0.2.0.10 | guewen sbidoul | Job Queue +[queue_job](queue_job/) | 18.0.2.1.0 | guewen sbidoul | Job Queue [queue_job_batch](queue_job_batch/) | 18.0.1.0.0 | | Job Queue Batch [queue_job_cron](queue_job_cron/) | 18.0.1.1.1 | | Scheduled Actions as Queue Jobs [queue_job_cron_jobrunner](queue_job_cron_jobrunner/) | 18.0.1.0.1 | ivantodorovich | Run jobs without a dedicated JobRunner diff --git a/queue_job/README.rst b/queue_job/README.rst index 0db8df9ca4..9dceaf165c 100644 --- a/queue_job/README.rst +++ b/queue_job/README.rst @@ -11,7 +11,7 @@ Job Queue !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:19ba0a7f175cdfcf3194c4e7fa19da2919973572e627bab9bdfe73015900b64a + !! source digest: sha256:1087919058419188998ff826b0852e7a275b847d68679ecf8aca3c3fb73ddbac !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Mature-brightgreen.png diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py index af68bb2660..e8360a1800 100644 --- a/queue_job/__manifest__.py +++ b/queue_job/__manifest__.py @@ -2,7 +2,7 @@ { "name": "Job Queue", - "version": "18.0.2.0.10", + "version": "18.0.2.1.0", "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)", "website": "https://github.com/OCA/queue", "license": "LGPL-3", diff --git a/queue_job/static/description/index.html b/queue_job/static/description/index.html index ef712748bd..d9ca6fa508 100644 --- a/queue_job/static/description/index.html +++ b/queue_job/static/description/index.html @@ -372,7 +372,7 @@

Job Queue

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:19ba0a7f175cdfcf3194c4e7fa19da2919973572e627bab9bdfe73015900b64a +!! source digest: sha256:1087919058419188998ff826b0852e7a275b847d68679ecf8aca3c3fb73ddbac !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Mature License: LGPL-3 OCA/queue Translate me on Weblate Try me on Runboat

This addon adds an integrated Job Queue to Odoo.