Skip to content

Commit b4c689d

Browse files
committed
Merge PR #749 into 18.0
Signed-off-by sbidoul
2 parents a6520d9 + 7f44c75 commit b4c689d

File tree

18 files changed

+496
-244
lines changed

18 files changed

+496
-244
lines changed

queue_job/README.rst

Lines changed: 103 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
.. image:: https://odoo-community.org/readme-banner-image
2-
:target: https://odoo-community.org/get-involved?utm_source=readme
3-
:alt: Odoo Community Association
4-
51
=========
62
Job Queue
73
=========
@@ -17,7 +13,7 @@ Job Queue
1713
.. |badge1| image:: https://img.shields.io/badge/maturity-Mature-brightgreen.png
1814
:target: https://odoo-community.org/page/development-status
1915
:alt: Mature
20-
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
16+
.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png
2117
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
2218
:alt: License: LGPL-3
2319
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fqueue-lightgray.png?logo=github
@@ -65,20 +61,20 @@ instantaneous if no other job is running.
6561

6662
Features:
6763

68-
- Views for jobs, jobs are stored in PostgreSQL
69-
- Jobrunner: execute the jobs, highly efficient thanks to PostgreSQL's
70-
NOTIFY
71-
- Channels: give a capacity for the root channel and its sub-channels
72-
and segregate jobs in them. Allow for instance to restrict heavy jobs
73-
to be executed one at a time while little ones are executed 4 at a
74-
times.
75-
- Retries: Ability to retry jobs by raising a type of exception
76-
- Retry Pattern: the 3 first tries, retry after 10 seconds, the 5 next
77-
tries, retry after 1 minutes, ...
78-
- Job properties: priorities, estimated time of arrival (ETA), custom
79-
description, number of retries
80-
- Related Actions: link an action on the job view, such as open the
81-
record concerned by the job
64+
- Views for jobs, jobs are stored in PostgreSQL
65+
- Jobrunner: execute the jobs, highly efficient thanks to PostgreSQL's
66+
NOTIFY
67+
- Channels: give a capacity for the root channel and its sub-channels
68+
and segregate jobs in them. Allow for instance to restrict heavy jobs
69+
to be executed one at a time while little ones are executed 4 at a
70+
times.
71+
- Retries: Ability to retry jobs by raising a type of exception
72+
- Retry Pattern: the 3 first tries, retry after 10 seconds, the 5 next
73+
tries, retry after 1 minutes, ...
74+
- Job properties: priorities, estimated time of arrival (ETA), custom
75+
description, number of retries
76+
- Related Actions: link an action on the job view, such as open the
77+
record concerned by the job
8278

8379
**Table of contents**
8480

@@ -93,18 +89,18 @@ Be sure to have the ``requests`` library.
9389
Configuration
9490
=============
9591

96-
- Using environment variables and command line:
92+
- Using environment variables and command line:
9793

98-
- Adjust environment variables (optional):
94+
- Adjust environment variables (optional):
9995

100-
- ``ODOO_QUEUE_JOB_CHANNELS=root:4`` or any other channels
101-
configuration. The default is ``root:1``
102-
- if ``xmlrpc_port`` is not set: ``ODOO_QUEUE_JOB_PORT=8069``
96+
- ``ODOO_QUEUE_JOB_CHANNELS=root:4`` or any other channels
97+
configuration. The default is ``root:1``
98+
- if ``xmlrpc_port`` is not set: ``ODOO_QUEUE_JOB_PORT=8069``
10399

104-
- Start Odoo with ``--load=web,queue_job`` and ``--workers`` greater
105-
than 1. [1]_
100+
- Start Odoo with ``--load=web,queue_job`` and ``--workers`` greater
101+
than 1. [1]_
106102

107-
- Using the Odoo configuration file:
103+
- Using the Odoo configuration file:
108104

109105
.. code:: ini
110106
@@ -117,8 +113,8 @@ Configuration
117113
[queue_job]
118114
channels = root:2
119115
120-
- Confirm the runner is starting correctly by checking the odoo log
121-
file:
116+
- Confirm the runner is starting correctly by checking the odoo log
117+
file:
122118

123119
::
124120

@@ -127,10 +123,14 @@ Configuration
127123
...INFO...queue_job.jobrunner.runner: queue job runner ready for db <dbname>
128124
...INFO...queue_job.jobrunner.runner: database connections ready
129125

130-
- Create jobs (eg using ``base_import_async``) and observe they start
131-
immediately and in parallel.
132-
- Tip: to enable debug logging for the queue job, use
133-
``--log-handler=odoo.addons.queue_job:DEBUG``
126+
- Create jobs (eg using ``base_import_async``) and observe they start
127+
immediately and in parallel.
128+
- Tip: to enable debug logging for the queue job, use
129+
``--log-handler=odoo.addons.queue_job:DEBUG``
130+
131+
- Jobs that remain in ``enqueued`` or ``started`` state (because, for
132+
instance, their worker has been killed) will be automatically
133+
re-queued.
134134

135135
.. [1]
136136
It works with the threaded Odoo server too, although this way of
@@ -287,20 +287,20 @@ only start when the previous one is done:
287287
Enqueing Job Options
288288
~~~~~~~~~~~~~~~~~~~~
289289

290-
- priority: default is 10, the closest it is to 0, the faster it will be
291-
executed
292-
- eta: Estimated Time of Arrival of the job. It will not be executed
293-
before this date/time
294-
- max_retries: default is 5, maximum number of retries before giving up
295-
and set the job state to 'failed'. A value of 0 means infinite
296-
retries.
297-
- description: human description of the job. If not set, description is
298-
computed from the function doc or method name
299-
- channel: the complete name of the channel to use to process the
300-
function. If specified it overrides the one defined on the function
301-
- identity_key: key uniquely identifying the job, if specified and a job
302-
with the same key has not yet been run, the new job will not be
303-
created
290+
- priority: default is 10, the closest it is to 0, the faster it will
291+
be executed
292+
- eta: Estimated Time of Arrival of the job. It will not be executed
293+
before this date/time
294+
- max_retries: default is 5, maximum number of retries before giving up
295+
and set the job state to 'failed'. A value of 0 means infinite
296+
retries.
297+
- description: human description of the job. If not set, description is
298+
computed from the function doc or method name
299+
- channel: the complete name of the channel to use to process the
300+
function. If specified it overrides the one defined on the function
301+
- identity_key: key uniquely identifying the job, if specified and a
302+
job with the same key has not yet been run, the new job will not be
303+
created
304304

305305
Configure default options for jobs
306306
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -371,11 +371,11 @@ dictionary on the job function:
371371
"kwargs": {"name": "Partner"},
372372
}
373373
374-
- ``enable``: when ``False``, the button has no effect (default:
375-
``True``)
376-
- ``func_name``: name of the method on ``queue.job`` that returns an
377-
action
378-
- ``kwargs``: extra arguments to pass to the related action method
374+
- ``enable``: when ``False``, the button has no effect (default:
375+
``True``)
376+
- ``func_name``: name of the method on ``queue.job`` that returns an
377+
action
378+
- ``kwargs``: extra arguments to pass to the related action method
379379

380380
Example of related action code:
381381

@@ -419,10 +419,10 @@ integers:
419419
420420
Based on this configuration, we can tell that:
421421

422-
- 5 first retries are postponed 10 seconds later
423-
- retries 5 to 10 postponed 20 seconds later
424-
- retries 10 to 15 postponed 30 seconds later
425-
- all subsequent retries postponed 5 minutes later
422+
- 5 first retries are postponed 10 seconds later
423+
- retries 5 to 10 postponed 20 seconds later
424+
- retries 10 to 15 postponed 30 seconds later
425+
- all subsequent retries postponed 5 minutes later
426426

427427
**Job Context**
428428

@@ -469,11 +469,11 @@ Testing
469469
The recommended way to test jobs, rather than running them directly and
470470
synchronously is to split the tests in two parts:
471471

472-
- one test where the job is mocked (trap jobs with ``trap_jobs()``
473-
and the test only verifies that the job has been delayed with the
474-
expected arguments
475-
- one test that only calls the method of the job synchronously, to
476-
validate the proper behavior of this method only
472+
- one test where the job is mocked (trap jobs with ``trap_jobs()``
473+
and the test only verifies that the job has been delayed with the
474+
expected arguments
475+
- one test that only calls the method of the job synchronously, to
476+
validate the proper behavior of this method only
477477

478478
Proceeding this way means that you can prove that jobs will be enqueued
479479
properly at runtime, and it ensures your code does not have a different
@@ -597,14 +597,14 @@ synchronously
597597
Tips and tricks
598598
~~~~~~~~~~~~~~~
599599
600-
- **Idempotency**
601-
(https://www.restapitutorial.com/lessons/idempotency.html): The
602-
queue_job should be idempotent so they can be retried several times
603-
without impact on the data.
604-
- **The job should test at the very beginning its relevance**: the
605-
moment the job will be executed is unknown by design. So the first
606-
task of a job should be to check if the related work is still relevant
607-
at the moment of the execution.
600+
- **Idempotency**
601+
(https://www.restapitutorial.com/lessons/idempotency.html): The
602+
queue_job should be idempotent so they can be retried several times
603+
without impact on the data.
604+
- **The job should test at the very beginning its relevance**: the
605+
moment the job will be executed is unknown by design. So the first
606+
task of a job should be to check if the related work is still
607+
relevant at the moment of the execution.
608608
609609
Patterns
610610
~~~~~~~~
@@ -621,19 +621,20 @@ Through the time, two main patterns emerged:
621621
Known issues / Roadmap
622622
======================
623623
624-
- After creating a new database or installing ``queue_job`` on an
625-
existing database, Odoo must be restarted for the runner to detect it.
626-
- When Odoo shuts down normally, it waits for running jobs to finish.
627-
However, when the Odoo server crashes or is otherwise force-stopped,
628-
running jobs are interrupted while the runner has no chance to know
629-
they have been aborted. In such situations, jobs may remain in
630-
``started`` or ``enqueued`` state after the Odoo server is halted.
631-
Since the runner has no way to know if they are actually running or
632-
not, and does not know for sure if it is safe to restart the jobs, it
633-
does not attempt to restart them automatically. Such stale jobs
634-
therefore fill the running queue and prevent other jobs to start. You
635-
must therefore requeue them manually, either from the Jobs view, or by
636-
running the following SQL statement *before starting Odoo*:
624+
- After creating a new database or installing ``queue_job`` on an
625+
existing database, Odoo must be restarted for the runner to detect
626+
it.
627+
- When Odoo shuts down normally, it waits for running jobs to finish.
628+
However, when the Odoo server crashes or is otherwise force-stopped,
629+
running jobs are interrupted while the runner has no chance to know
630+
they have been aborted. In such situations, jobs may remain in
631+
``started`` or ``enqueued`` state after the Odoo server is halted.
632+
Since the runner has no way to know if they are actually running or
633+
not, and does not know for sure if it is safe to restart the jobs, it
634+
does not attempt to restart them automatically. Such stale jobs
635+
therefore fill the running queue and prevent other jobs to start. You
636+
must therefore requeue them manually, either from the Jobs view, or
637+
by running the following SQL statement *before starting Odoo*:
637638
638639
.. code:: sql
639640
@@ -645,11 +646,11 @@ Changelog
645646
Next
646647
----
647648
648-
- [ADD] Run jobrunner as a worker process instead of a thread in the
649-
main process (when running with --workers > 0)
650-
- [REF] ``@job`` and ``@related_action`` deprecated, any method can be
651-
delayed, and configured using ``queue.job.function`` records
652-
- [MIGRATION] from 13.0 branched at rev. e24ff4b
649+
- [ADD] Run jobrunner as a worker process instead of a thread in the
650+
main process (when running with --workers > 0)
651+
- [REF] ``@job`` and ``@related_action`` deprecated, any method can be
652+
delayed, and configured using ``queue.job.function`` records
653+
- [MIGRATION] from 13.0 branched at rev. e24ff4b
653654
654655
Bug Tracker
655656
===========
@@ -673,21 +674,21 @@ Authors
673674
Contributors
674675
------------
675676
676-
- Guewen Baconnier <guewen.baconnier@camptocamp.com>
677-
- Stéphane Bidoul <stephane.bidoul@acsone.eu>
678-
- Matthieu Dietrich <matthieu.dietrich@camptocamp.com>
679-
- Jos De Graeve <Jos.DeGraeve@apertoso.be>
680-
- David Lefever <dl@taktik.be>
681-
- Laurent Mignon <laurent.mignon@acsone.eu>
682-
- Laetitia Gangloff <laetitia.gangloff@acsone.eu>
683-
- Cédric Pigeon <cedric.pigeon@acsone.eu>
684-
- Tatiana Deribina <tatiana.deribina@avoin.systems>
685-
- Souheil Bejaoui <souheil.bejaoui@acsone.eu>
686-
- Eric Antones <eantones@nuobit.com>
687-
- Simone Orsi <simone.orsi@camptocamp.com>
688-
- Nguyen Minh Chien <chien@trobz.com>
689-
- Tran Quoc Duong <duongtq@trobz.com>
690-
- Vo Hong Thien <thienvh@trobz.com>
677+
- Guewen Baconnier <guewen.baconnier@camptocamp.com>
678+
- Stéphane Bidoul <stephane.bidoul@acsone.eu>
679+
- Matthieu Dietrich <matthieu.dietrich@camptocamp.com>
680+
- Jos De Graeve <Jos.DeGraeve@apertoso.be>
681+
- David Lefever <dl@taktik.be>
682+
- Laurent Mignon <laurent.mignon@acsone.eu>
683+
- Laetitia Gangloff <laetitia.gangloff@acsone.eu>
684+
- Cédric Pigeon <cedric.pigeon@acsone.eu>
685+
- Tatiana Deribina <tatiana.deribina@avoin.systems>
686+
- Souheil Bejaoui <souheil.bejaoui@acsone.eu>
687+
- Eric Antones <eantones@nuobit.com>
688+
- Simone Orsi <simone.orsi@camptocamp.com>
689+
- Nguyen Minh Chien <chien@trobz.com>
690+
- Tran Quoc Duong <duongtq@trobz.com>
691+
- Vo Hong Thien <thienvh@trobz.com>
691692
692693
Other credits
693694
-------------

queue_job/controllers/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def _try_perform_job(self, env, job):
3232
job.set_started()
3333
job.store()
3434
env.cr.commit()
35+
job.lock()
36+
3537
_logger.debug("%s started", job)
3638

3739
job.perform()

queue_job/data/queue_data.xml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
<?xml version="1.0" encoding="utf-8" ?>
22
<odoo>
33
<data noupdate="1">
4-
<record id="ir_cron_queue_job_garbage_collector" model="ir.cron">
5-
<field name="name">Jobs Garbage Collector</field>
6-
<field name="interval_number">5</field>
7-
<field name="interval_type">minutes</field>
8-
<field ref="model_queue_job" name="model_id" />
9-
<field name="state">code</field>
10-
<field name="code">model.requeue_stuck_jobs()</field>
11-
</record>
124
<!-- Queue-job-related subtypes for messaging / Chatter -->
135
<record id="mt_job_failed" model="mail.message.subtype">
146
<field name="name">Job failed</field>

queue_job/job.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,61 @@ def load_many(cls, env, job_uuids):
221221
recordset = cls.db_records_from_uuids(env, job_uuids)
222222
return {cls._load_from_db_record(record) for record in recordset}
223223

224+
def add_lock_record(self):
225+
"""
226+
Create row in db to be locked while the job is being performed.
227+
"""
228+
self.env.cr.execute(
229+
"""
230+
INSERT INTO
231+
queue_job_lock (id, queue_job_id)
232+
SELECT
233+
id, id
234+
FROM
235+
queue_job
236+
WHERE
237+
uuid = %s
238+
ON CONFLICT(id)
239+
DO NOTHING;
240+
""",
241+
[self.uuid],
242+
)
243+
244+
def lock(self):
245+
"""
246+
Lock row of job that is being performed
247+
248+
If a job cannot be locked,
249+
it means that the job wasn't started,
250+
a RetryableJobError is thrown.
251+
"""
252+
self.env.cr.execute(
253+
"""
254+
SELECT
255+
*
256+
FROM
257+
queue_job_lock
258+
WHERE
259+
queue_job_id in (
260+
SELECT
261+
id
262+
FROM
263+
queue_job
264+
WHERE
265+
uuid = %s
266+
AND state='started'
267+
)
268+
FOR UPDATE;
269+
""",
270+
[self.uuid],
271+
)
272+
273+
# 1 job should be locked
274+
if 1 != len(self.env.cr.fetchall()):
275+
raise RetryableJobError(
276+
f"Trying to lock job that wasn't started, uuid: {self.uuid}"
277+
)
278+
224279
@classmethod
225280
def _load_from_db_record(cls, job_db_record):
226281
stored = job_db_record
@@ -735,6 +790,7 @@ def set_started(self):
735790
self.state = STARTED
736791
self.date_started = datetime.now()
737792
self.worker_pid = os.getpid()
793+
self.add_lock_record()
738794

739795
def set_done(self, result=None):
740796
self.state = DONE

0 commit comments

Comments
 (0)