From 7cf92ba86fcfb9a39a6832ba87e4c6693d6eb6ad Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Mon, 2 Jun 2025 13:50:28 +0200 Subject: [PATCH 1/3] [IMP] queue_job: use __slots__ for ChannelJob It decreases memory footprint when there's thousands of jobs to prioritize. --- queue_job/jobrunner/channels.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/queue_job/jobrunner/channels.py b/queue_job/jobrunner/channels.py index 3f81ec0b2a..ffecadbd9a 100644 --- a/queue_job/jobrunner/channels.py +++ b/queue_job/jobrunner/channels.py @@ -173,6 +173,8 @@ class ChannelJob: """ + __slots__ = ("db_name", "channel", "uuid", "seq", "date_created", "priority", "eta", "__weakref__") + def __init__(self, db_name, channel, uuid, seq, date_created, priority, eta): self.db_name = db_name self.channel = channel From c72ae6b3a88210d5c2315f7acbf36cb2de42455b Mon Sep 17 00:00:00 2001 From: Florent Xicluna Date: Tue, 3 Jun 2025 08:58:27 +0200 Subject: [PATCH 2/3] [IMP] queue_job: more efficient ChannelJob sorting --- queue_job/jobrunner/channels.py | 55 +++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/queue_job/jobrunner/channels.py b/queue_job/jobrunner/channels.py index ffecadbd9a..54188db11f 100644 --- a/queue_job/jobrunner/channels.py +++ b/queue_job/jobrunner/channels.py @@ -2,6 +2,7 @@ # Copyright 2015-2016 Camptocamp SA # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) import logging +from collections import namedtuple from functools import total_ordering from heapq import heappop, heappush from weakref import WeakValueDictionary @@ -10,6 +11,7 @@ from ..job import CANCELLED, DONE, ENQUEUED, FAILED, PENDING, STARTED, WAIT_DEPENDENCIES NOT_DONE = (WAIT_DEPENDENCIES, PENDING, ENQUEUED, STARTED, FAILED) +JobSortingKey = namedtuple("SortingKey", "eta priority date_created seq") _logger = logging.getLogger(__name__) @@ -108,7 +110,7 @@ class ChannelJob: job that are necessary to prioritise them. Channel jobs are comparable according to the following rules: - * jobs with an eta come before all other jobs + * jobs with an eta cannot be compared with jobs without * then jobs with a smaller eta come first * then jobs with a smaller priority come first * then jobs with a smaller creation time come first @@ -135,14 +137,18 @@ class ChannelJob: >>> j3 < j1 True - j4 and j5 comes even before j3, because they have an eta + j4 and j5 have an eta, they cannot be compared with j3 >>> j4 = ChannelJob(None, None, 4, ... seq=0, date_created=4, priority=9, eta=9) >>> j5 = ChannelJob(None, None, 5, ... seq=0, date_created=5, priority=9, eta=9) - >>> j4 < j5 < j3 + >>> j4 < j5 True + >>> j4 < j3 + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'int' and 'NoneType' j6 has same date_created and priority as j5 but a smaller eta @@ -153,7 +159,7 @@ class ChannelJob: Here is the complete suite: - >>> j6 < j4 < j5 < j3 < j1 < j2 + >>> j6 < j4 < j5 and j3 < j1 < j2 True j0 has the same properties as j1 but they are not considered @@ -173,16 +179,13 @@ class ChannelJob: """ - __slots__ = ("db_name", "channel", "uuid", "seq", "date_created", "priority", "eta", "__weakref__") + __slots__ = ("db_name", "channel", "uuid", "_sorting_key", "__weakref__") def __init__(self, db_name, channel, uuid, seq, date_created, priority, eta): self.db_name = db_name self.channel = channel self.uuid = uuid - self.seq = seq - self.date_created = date_created - self.priority = priority - self.eta = eta + self._sorting_key = JobSortingKey(eta, priority, date_created, seq) def __repr__(self): return "" % self.uuid @@ -193,18 +196,36 @@ def __eq__(self, other): def __hash__(self): return id(self) + def set_no_eta(self): + self._sorting_key = JobSortingKey(None, *self._sorting_key[1:]) + + @property + def seq(self): + return self._sorting_key.seq + + @property + def date_created(self): + return self._sorting_key.date_created + + @property + def priority(self): + return self._sorting_key.priority + + @property + def eta(self): + return self._sorting_key.eta + def sorting_key(self): - return self.eta, self.priority, self.date_created, self.seq + # DEPRECATED + return self._sorting_key def sorting_key_ignoring_eta(self): - return self.priority, self.date_created, self.seq + return self._sorting_key[1:] def __lt__(self, other): - if self.eta and not other.eta: - return True - elif not self.eta and other.eta: - return False - return self.sorting_key() < other.sorting_key() + # Do not compare job where ETA is set with job where it is not + # If one job 'eta' is set, and the other is None, it raises TypeError + return self._sorting_key < other._sorting_key class ChannelQueue: @@ -314,7 +335,7 @@ def remove(self, job): def pop(self, now): while self._eta_queue and self._eta_queue[0].eta <= now: eta_job = self._eta_queue.pop() - eta_job.eta = None + eta_job.set_no_eta() self._queue.add(eta_job) if self.sequential and self._eta_queue and self._queue: eta_job = self._eta_queue[0] From 33bcad90198577ef8bb7e0324a1b011ac54b1930 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 29 Oct 2025 19:06:13 +0000 Subject: [PATCH 3/3] [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 749a383610..64d6f3cde2 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ addon | version | maintainers | summary [base_export_async](base_export_async/) | 14.0.1.1.0 | | Asynchronous export with job queue [base_import_async](base_import_async/) | 14.0.1.0.2 | | Import CSV files in the background [export_async_schedule](export_async_schedule/) | 14.0.1.0.1 | guewen | Generate and send exports by emails on a schedule -[queue_job](queue_job/) | 14.0.3.15.0 | guewen | Job Queue +[queue_job](queue_job/) | 14.0.3.15.1 | guewen | Job Queue [queue_job_batch](queue_job_batch/) | 14.0.1.0.2 | | Job Queue Batch [queue_job_context](queue_job_context/) | 14.0.1.0.1 | AshishHirapara | Queue Job, prepare context before enqueue keys [queue_job_cron](queue_job_cron/) | 14.0.2.0.0 | | Scheduled Actions as Queue Jobs diff --git a/queue_job/README.rst b/queue_job/README.rst index e5c372da8c..875c2a48cd 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:9f7dad42fbcb042d09ea5124a82257167c8d6bf4ebc51f97999e9031e8c7eb23 + !! source digest: sha256:d7d006c41953034faf1dfee702a1887c0908c92b5744d6d3e48dfe52a7f2461b !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Mature-brightgreen.png diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py index 28340b81e1..e25e4742e9 100644 --- a/queue_job/__manifest__.py +++ b/queue_job/__manifest__.py @@ -2,7 +2,7 @@ { "name": "Job Queue", - "version": "14.0.3.15.0", + "version": "14.0.3.15.1", "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 c0bc6ee76d..c53bb505a6 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:9f7dad42fbcb042d09ea5124a82257167c8d6bf4ebc51f97999e9031e8c7eb23 +!! source digest: sha256:d7d006c41953034faf1dfee702a1887c0908c92b5744d6d3e48dfe52a7f2461b !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

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

This addon adds an integrated Job Queue to Odoo.