From 3c5e525909052368f6e0e06c68fc0a2190879460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 2 Jul 2024 13:50:11 +0200 Subject: [PATCH 1/6] [FIX] queue_job: close connection to databases without job queue Without this, we leak connections to Databases that don't have queue_job installed. --- queue_job/jobrunner/runner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/queue_job/jobrunner/runner.py b/queue_job/jobrunner/runner.py index 5e8d750a01..3b4d7baa46 100644 --- a/queue_job/jobrunner/runner.py +++ b/queue_job/jobrunner/runner.py @@ -469,6 +469,8 @@ def initialize_databases(self): for job_data in cr: self.channel_manager.notify(db_name, *job_data) _logger.info("queue job runner ready for db %s", db_name) + else: + db.close() def requeue_dead_jobs(self): for db in self.db_by_name.values(): From d505f651846ef8935c7ff0c7b21798b271587881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 2 Jul 2024 13:45:08 +0200 Subject: [PATCH 2/6] [FIX] queue_job: handle exceptions in Database constructor Without this we risk connection leaks in case of exceptions in the constructor. --- queue_job/jobrunner/runner.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/queue_job/jobrunner/runner.py b/queue_job/jobrunner/runner.py index 3b4d7baa46..4aef188b10 100644 --- a/queue_job/jobrunner/runner.py +++ b/queue_job/jobrunner/runner.py @@ -227,10 +227,14 @@ def __init__(self, db_name): self.db_name = db_name connection_info = _connection_info_for(db_name) self.conn = psycopg2.connect(**connection_info) - self.conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) - self.has_queue_job = self._has_queue_job() - if self.has_queue_job: - self._initialize() + try: + self.conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) + self.has_queue_job = self._has_queue_job() + if self.has_queue_job: + self._initialize() + except BaseException: + self.close() + raise def close(self): # pylint: disable=except-pass From c592ed8cb54fa44dcf07ab619b88196ed26ff67a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 2 Jul 2024 13:06:14 +0200 Subject: [PATCH 3/6] [IMP] queue_job: HA job runner using session level advisory lock --- queue_job/jobrunner/runner.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/queue_job/jobrunner/runner.py b/queue_job/jobrunner/runner.py index 4aef188b10..8e9d0699d5 100644 --- a/queue_job/jobrunner/runner.py +++ b/queue_job/jobrunner/runner.py @@ -143,12 +143,17 @@ SELECT_TIMEOUT = 60 ERROR_RECOVERY_DELAY = 5 +PG_ADVISORY_LOCK_ID = 2293787760715711918 _logger = logging.getLogger(__name__) select = selectors.DefaultSelector +class MasterElectionLost(Exception): + pass + + # Unfortunately, it is not possible to extend the Odoo # server command line arguments, so we resort to environment variables # to configure the runner (channels mostly). @@ -231,6 +236,7 @@ def __init__(self, db_name): self.conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) self.has_queue_job = self._has_queue_job() if self.has_queue_job: + self._acquire_master_lock() self._initialize() except BaseException: self.close() @@ -247,6 +253,14 @@ def close(self): pass self.conn = None + def _acquire_master_lock(self): + """Acquire the master runner lock or raise MasterElectionLost""" + with closing(self.conn.cursor()) as cr: + cr.execute("SELECT pg_try_advisory_lock(%s)", (PG_ADVISORY_LOCK_ID,)) + if not cr.fetchone()[0]: + msg = f"could not acquire master runner lock on {self.db_name}" + raise MasterElectionLost(msg) + def _has_queue_job(self): with closing(self.conn.cursor()) as cr: cr.execute( @@ -452,7 +466,7 @@ def get_db_names(self): db_names = config["db_name"].split(",") else: db_names = odoo.service.db.list_dbs(True) - return db_names + return sorted(db_names) def close_databases(self, remove_jobs=True): for db_name, db in self.db_by_name.items(): @@ -566,7 +580,7 @@ def run(self): while not self._stop: # outer loop does exception recovery try: - _logger.info("initializing database connections") + _logger.debug("initializing database connections") # TODO: how to detect new databases or databases # on which queue_job is installed after server start? self.initialize_databases() @@ -582,6 +596,14 @@ def run(self): except InterruptedError: # Interrupted system call, i.e. KeyboardInterrupt during select self.stop() + except MasterElectionLost as e: + _logger.debug( + "master election lost: %s, sleeping %ds and retrying", + e, + ERROR_RECOVERY_DELAY, + ) + self.close_databases() + time.sleep(ERROR_RECOVERY_DELAY) except Exception: _logger.exception( "exception: sleeping %ds and retrying", ERROR_RECOVERY_DELAY From 2631808e04ecc4047e3a042d7318f1d651f07318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 2 Jul 2024 20:35:11 +0200 Subject: [PATCH 4/6] [IMP] queue_job: make sorting more explicit --- queue_job/jobrunner/runner.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/queue_job/jobrunner/runner.py b/queue_job/jobrunner/runner.py index 8e9d0699d5..7f935c63d7 100644 --- a/queue_job/jobrunner/runner.py +++ b/queue_job/jobrunner/runner.py @@ -466,7 +466,7 @@ def get_db_names(self): db_names = config["db_name"].split(",") else: db_names = odoo.service.db.list_dbs(True) - return sorted(db_names) + return db_names def close_databases(self, remove_jobs=True): for db_name, db in self.db_by_name.items(): @@ -479,7 +479,8 @@ def close_databases(self, remove_jobs=True): self.db_by_name = {} def initialize_databases(self): - for db_name in self.get_db_names(): + for db_name in sorted(self.get_db_names()): + # sorting is important to avoid deadlocks in acquiring the master lock db = Database(db_name) if db.has_queue_job: self.db_by_name[db_name] = db From b2ff50d7bc070fdc337b5839edb2c66eb7ef091f Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 17 Mar 2025 12:53:38 +0000 Subject: [PATCH 5/6] [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 bf1673ecf0..42c24dad52 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ addon | version | maintainers | summary --- | --- | --- | --- [base_export_async](base_export_async/) | 16.0.1.1.0 | | Asynchronous export with job queue [base_import_async](base_import_async/) | 16.0.1.2.0 | | Import CSV files in the background -[queue_job](queue_job/) | 16.0.2.10.0 | [![guewen](https://github.com/guewen.png?size=30px)](https://github.com/guewen) | Job Queue +[queue_job](queue_job/) | 16.0.2.11.0 | [![guewen](https://github.com/guewen.png?size=30px)](https://github.com/guewen) | Job Queue [queue_job_batch](queue_job_batch/) | 16.0.1.0.1 | | Job Queue Batch [queue_job_cron](queue_job_cron/) | 16.0.2.1.0 | | Scheduled Actions as Queue Jobs [queue_job_cron_jobrunner](queue_job_cron_jobrunner/) | 16.0.1.1.0 | [![ivantodorovich](https://github.com/ivantodorovich.png?size=30px)](https://github.com/ivantodorovich) | Run jobs without a dedicated JobRunner diff --git a/queue_job/README.rst b/queue_job/README.rst index a0b8c4afde..49092aee44 100644 --- a/queue_job/README.rst +++ b/queue_job/README.rst @@ -7,7 +7,7 @@ Job Queue !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:3122e45d99a3e3ce7c38316fbec4714e530da80fd806bcdfff9aa934c4ec0bdb + !! source digest: sha256:71335b8e2973a1871cf0bf504860c2af79d7d15f8c6e39d237f0c396f0b46e9f !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Mature-brightgreen.png diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py index 1ba6c59940..e997d763c7 100644 --- a/queue_job/__manifest__.py +++ b/queue_job/__manifest__.py @@ -2,7 +2,7 @@ { "name": "Job Queue", - "version": "16.0.2.10.0", + "version": "16.0.2.11.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 f585708e59..992e17f4d1 100644 --- a/queue_job/static/description/index.html +++ b/queue_job/static/description/index.html @@ -367,7 +367,7 @@

Job Queue

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:3122e45d99a3e3ce7c38316fbec4714e530da80fd806bcdfff9aa934c4ec0bdb +!! source digest: sha256:71335b8e2973a1871cf0bf504860c2af79d7d15f8c6e39d237f0c396f0b46e9f !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

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

This addon adds an integrated Job Queue to Odoo.

From fe69f670eb2f572f9767a974e624ee97ab06f2c6 Mon Sep 17 00:00:00 2001 From: davidbeckercbl Date: Mon, 17 Mar 2025 16:35:46 +0000 Subject: [PATCH 6/6] Translated using Weblate (German) Currently translated at 97.4% (154 of 158 strings) Translation: queue-16.0/queue-16.0-queue_job Translate-URL: https://translation.odoo-community.org/projects/queue-16-0/queue-16-0-queue_job/de/ --- queue_job/i18n/de.po | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/queue_job/i18n/de.po b/queue_job/i18n/de.po index 00b450b4b7..ecbcb067da 100644 --- a/queue_job/i18n/de.po +++ b/queue_job/i18n/de.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 12.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2025-03-03 18:06+0000\n" +"PO-Revision-Date: 2025-03-17 19:06+0000\n" "Last-Translator: davidbeckercbl \n" "Language-Team: none\n" "Language: de\n" @@ -692,7 +692,7 @@ msgstr "Job einreihen" #. module: queue_job #: model:ir.model,name:queue_job.model_queue_job_lock msgid "Queue Job Lock" -msgstr "" +msgstr "Job einreihen Sperre" #. module: queue_job #. odoo-python @@ -708,9 +708,8 @@ msgstr "Datensatz" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__records -#, fuzzy msgid "Record(s)" -msgstr "Datensatz" +msgstr "Datensatz/Datensätze" #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form @@ -744,7 +743,7 @@ msgstr "Zugehörige Datensätze" #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_tree msgid "Remaining days to execute" -msgstr "" +msgstr "Verbleibende Tage bis zur Ausführung" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_channel__removal_interval @@ -781,17 +780,17 @@ msgstr "Ergebnis" #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_form msgid "Results" -msgstr "" +msgstr "Ergebnisse" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__edit_retry_pattern msgid "Retry Pattern" -msgstr "" +msgstr "Wiederholungsmuster" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job_function__retry_pattern msgid "Retry Pattern (serialized)" -msgstr "" +msgstr "Wiederholungsmuster (serialisiert)" #. module: queue_job #: model:ir.model,name:queue_job.model_queue_jobs_to_done @@ -893,7 +892,7 @@ msgstr "" #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_set_jobs_cancelled msgid "The selected jobs will be cancelled." -msgstr "" +msgstr "Die markierten Jobs werden abgebrochen." #. module: queue_job #: model_terms:ir.ui.view,arch_db:queue_job.view_requeue_job @@ -958,7 +957,7 @@ msgstr "Benutzer" #: model:ir.model.fields.selection,name:queue_job.selection__queue_job__state__wait_dependencies #: model_terms:ir.ui.view,arch_db:queue_job.view_queue_job_search msgid "Wait Dependencies" -msgstr "" +msgstr "Warten Abhängigkeiten" #. module: queue_job #: model:ir.model,name:queue_job.model_queue_requeue_job @@ -968,7 +967,7 @@ msgstr "Assistent zur erneuten Einreihung einer Job-Auswahl" #. module: queue_job #: model:ir.model.fields,field_description:queue_job.field_queue_job__worker_pid msgid "Worker Pid" -msgstr "" +msgstr "Arbeiter Prozess-Id" #, python-format #~ msgid "If both parameters are 0, ALL jobs will be requeued!"