Skip to content

Commit efa358b

Browse files
committed
Merge PR #715 into 18.0
Signed-off-by guewen
2 parents eb58253 + 0c63fd6 commit efa358b

6 files changed

Lines changed: 349 additions & 108 deletions

File tree

queue_job/README.rst

Lines changed: 130 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,20 @@ instantaneous if no other job is running.
6161

6262
Features:
6363

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
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
7878

7979
**Table of contents**
8080

@@ -89,18 +89,18 @@ Be sure to have the ``requests`` library.
8989
Configuration
9090
=============
9191

92-
- Using environment variables and command line:
92+
- Using environment variables and command line:
9393

94-
- Adjust environment variables (optional):
94+
- Adjust environment variables (optional):
9595

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``
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``
9999

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

103-
- Using the Odoo configuration file:
103+
- Using the Odoo configuration file:
104104

105105
.. code:: ini
106106
@@ -113,8 +113,8 @@ Configuration
113113
[queue_job]
114114
channels = root:2
115115
116-
- Confirm the runner is starting correctly by checking the odoo log
117-
file:
116+
- Confirm the runner is starting correctly by checking the odoo log
117+
file:
118118

119119
::
120120

@@ -123,10 +123,10 @@ Configuration
123123
...INFO...queue_job.jobrunner.runner: queue job runner ready for db <dbname>
124124
...INFO...queue_job.jobrunner.runner: database connections ready
125125

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``
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``
130130

131131
.. [1]
132132
It works with the threaded Odoo server too, although this way of
@@ -247,23 +247,56 @@ is at the top of the graph. In the example above, if it was called on
247247
``group_a``, then ``group_b`` would never be delayed (but a warning
248248
would be shown).
249249

250+
It is also possible to split a job into several jobs, each one
251+
processing a part of the work. This can be useful to avoid very long
252+
jobs, parallelize some task and get more specific errors. Usage is as
253+
follows:
254+
255+
.. code:: python
256+
257+
def button_split_delayable(self):
258+
(
259+
self # Can be a big recordset, let's say 1000 records
260+
.delayable()
261+
.generate_thumbnail((50, 50))
262+
.set(priority=30)
263+
.set(description=_("generate xxx"))
264+
.split(50) # Split the job in 20 jobs of 50 records each
265+
.delay()
266+
)
267+
268+
The ``split()`` method takes a ``chain`` boolean keyword argument. If
269+
set to True, the jobs will be chained, meaning that the next job will
270+
only start when the previous one is done:
271+
272+
.. code:: python
273+
274+
def button_increment_var(self):
275+
(
276+
self
277+
.delayable()
278+
.increment_counter()
279+
.split(1, chain=True) # Will exceute the jobs one after the other
280+
.delay()
281+
)
282+
250283
Enqueing Job Options
251284
~~~~~~~~~~~~~~~~~~~~
252285

253-
- priority: default is 10, the closest it is to 0, the faster it will
254-
be executed
255-
- eta: Estimated Time of Arrival of the job. It will not be executed
256-
before this date/time
257-
- max_retries: default is 5, maximum number of retries before giving up
258-
and set the job state to 'failed'. A value of 0 means infinite
259-
retries.
260-
- description: human description of the job. If not set, description is
261-
computed from the function doc or method name
262-
- channel: the complete name of the channel to use to process the
263-
function. If specified it overrides the one defined on the function
264-
- identity_key: key uniquely identifying the job, if specified and a
265-
job with the same key has not yet been run, the new job will not be
266-
created
286+
- priority: default is 10, the closest it is to 0, the faster it will be
287+
executed
288+
- eta: Estimated Time of Arrival of the job. It will not be executed
289+
before this date/time
290+
- max_retries: default is 5, maximum number of retries before giving up
291+
and set the job state to 'failed'. A value of 0 means infinite
292+
retries.
293+
- description: human description of the job. If not set, description is
294+
computed from the function doc or method name
295+
- channel: the complete name of the channel to use to process the
296+
function. If specified it overrides the one defined on the function
297+
- identity_key: key uniquely identifying the job, if specified and a job
298+
with the same key has not yet been run, the new job will not be
299+
created
267300

268301
Configure default options for jobs
269302
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -334,11 +367,11 @@ dictionary on the job function:
334367
"kwargs": {"name": "Partner"},
335368
}
336369
337-
- ``enable``: when ``False``, the button has no effect (default:
338-
``True``)
339-
- ``func_name``: name of the method on ``queue.job`` that returns an
340-
action
341-
- ``kwargs``: extra arguments to pass to the related action method
370+
- ``enable``: when ``False``, the button has no effect (default:
371+
``True``)
372+
- ``func_name``: name of the method on ``queue.job`` that returns an
373+
action
374+
- ``kwargs``: extra arguments to pass to the related action method
342375

343376
Example of related action code:
344377

@@ -382,10 +415,10 @@ integers:
382415
383416
Based on this configuration, we can tell that:
384417

385-
- 5 first retries are postponed 10 seconds later
386-
- retries 5 to 10 postponed 20 seconds later
387-
- retries 10 to 15 postponed 30 seconds later
388-
- all subsequent retries postponed 5 minutes later
418+
- 5 first retries are postponed 10 seconds later
419+
- retries 5 to 10 postponed 20 seconds later
420+
- retries 10 to 15 postponed 30 seconds later
421+
- all subsequent retries postponed 5 minutes later
389422

390423
**Job Context**
391424

@@ -432,11 +465,11 @@ Testing
432465
The recommended way to test jobs, rather than running them directly and
433466
synchronously is to split the tests in two parts:
434467

435-
- one test where the job is mocked (trap jobs with ``trap_jobs()``
436-
and the test only verifies that the job has been delayed with the
437-
expected arguments
438-
- one test that only calls the method of the job synchronously, to
439-
validate the proper behavior of this method only
468+
- one test where the job is mocked (trap jobs with ``trap_jobs()``
469+
and the test only verifies that the job has been delayed with the
470+
expected arguments
471+
- one test that only calls the method of the job synchronously, to
472+
validate the proper behavior of this method only
440473

441474
Proceeding this way means that you can prove that jobs will be enqueued
442475
properly at runtime, and it ensures your code does not have a different
@@ -560,14 +593,14 @@ synchronously
560593
Tips and tricks
561594
~~~~~~~~~~~~~~~
562595
563-
- **Idempotency**
564-
(https://www.restapitutorial.com/lessons/idempotency.html): The
565-
queue_job should be idempotent so they can be retried several times
566-
without impact on the data.
567-
- **The job should test at the very beginning its relevance**: the
568-
moment the job will be executed is unknown by design. So the first
569-
task of a job should be to check if the related work is still
570-
relevant at the moment of the execution.
596+
- **Idempotency**
597+
(https://www.restapitutorial.com/lessons/idempotency.html): The
598+
queue_job should be idempotent so they can be retried several times
599+
without impact on the data.
600+
- **The job should test at the very beginning its relevance**: the
601+
moment the job will be executed is unknown by design. So the first
602+
task of a job should be to check if the related work is still relevant
603+
at the moment of the execution.
571604
572605
Patterns
573606
~~~~~~~~
@@ -584,20 +617,19 @@ Through the time, two main patterns emerged:
584617
Known issues / Roadmap
585618
======================
586619
587-
- After creating a new database or installing ``queue_job`` on an
588-
existing database, Odoo must be restarted for the runner to detect
589-
it.
590-
- When Odoo shuts down normally, it waits for running jobs to finish.
591-
However, when the Odoo server crashes or is otherwise force-stopped,
592-
running jobs are interrupted while the runner has no chance to know
593-
they have been aborted. In such situations, jobs may remain in
594-
``started`` or ``enqueued`` state after the Odoo server is halted.
595-
Since the runner has no way to know if they are actually running or
596-
not, and does not know for sure if it is safe to restart the jobs, it
597-
does not attempt to restart them automatically. Such stale jobs
598-
therefore fill the running queue and prevent other jobs to start. You
599-
must therefore requeue them manually, either from the Jobs view, or
600-
by running the following SQL statement *before starting Odoo*:
620+
- After creating a new database or installing ``queue_job`` on an
621+
existing database, Odoo must be restarted for the runner to detect it.
622+
- When Odoo shuts down normally, it waits for running jobs to finish.
623+
However, when the Odoo server crashes or is otherwise force-stopped,
624+
running jobs are interrupted while the runner has no chance to know
625+
they have been aborted. In such situations, jobs may remain in
626+
``started`` or ``enqueued`` state after the Odoo server is halted.
627+
Since the runner has no way to know if they are actually running or
628+
not, and does not know for sure if it is safe to restart the jobs, it
629+
does not attempt to restart them automatically. Such stale jobs
630+
therefore fill the running queue and prevent other jobs to start. You
631+
must therefore requeue them manually, either from the Jobs view, or by
632+
running the following SQL statement *before starting Odoo*:
601633
602634
.. code:: sql
603635
@@ -609,11 +641,11 @@ Changelog
609641
Next
610642
----
611643
612-
- [ADD] Run jobrunner as a worker process instead of a thread in the
613-
main process (when running with --workers > 0)
614-
- [REF] ``@job`` and ``@related_action`` deprecated, any method can be
615-
delayed, and configured using ``queue.job.function`` records
616-
- [MIGRATION] from 13.0 branched at rev. e24ff4b
644+
- [ADD] Run jobrunner as a worker process instead of a thread in the
645+
main process (when running with --workers > 0)
646+
- [REF] ``@job`` and ``@related_action`` deprecated, any method can be
647+
delayed, and configured using ``queue.job.function`` records
648+
- [MIGRATION] from 13.0 branched at rev. e24ff4b
617649
618650
Bug Tracker
619651
===========
@@ -637,21 +669,21 @@ Authors
637669
Contributors
638670
------------
639671
640-
- Guewen Baconnier <guewen.baconnier@camptocamp.com>
641-
- Stéphane Bidoul <stephane.bidoul@acsone.eu>
642-
- Matthieu Dietrich <matthieu.dietrich@camptocamp.com>
643-
- Jos De Graeve <Jos.DeGraeve@apertoso.be>
644-
- David Lefever <dl@taktik.be>
645-
- Laurent Mignon <laurent.mignon@acsone.eu>
646-
- Laetitia Gangloff <laetitia.gangloff@acsone.eu>
647-
- Cédric Pigeon <cedric.pigeon@acsone.eu>
648-
- Tatiana Deribina <tatiana.deribina@avoin.systems>
649-
- Souheil Bejaoui <souheil.bejaoui@acsone.eu>
650-
- Eric Antones <eantones@nuobit.com>
651-
- Simone Orsi <simone.orsi@camptocamp.com>
652-
- Nguyen Minh Chien <chien@trobz.com>
653-
- Tran Quoc Duong <duongtq@trobz.com>
654-
- Vo Hong Thien <thienvh@trobz.com>
672+
- Guewen Baconnier <guewen.baconnier@camptocamp.com>
673+
- Stéphane Bidoul <stephane.bidoul@acsone.eu>
674+
- Matthieu Dietrich <matthieu.dietrich@camptocamp.com>
675+
- Jos De Graeve <Jos.DeGraeve@apertoso.be>
676+
- David Lefever <dl@taktik.be>
677+
- Laurent Mignon <laurent.mignon@acsone.eu>
678+
- Laetitia Gangloff <laetitia.gangloff@acsone.eu>
679+
- Cédric Pigeon <cedric.pigeon@acsone.eu>
680+
- Tatiana Deribina <tatiana.deribina@avoin.systems>
681+
- Souheil Bejaoui <souheil.bejaoui@acsone.eu>
682+
- Eric Antones <eantones@nuobit.com>
683+
- Simone Orsi <simone.orsi@camptocamp.com>
684+
- Nguyen Minh Chien <chien@trobz.com>
685+
- Tran Quoc Duong <duongtq@trobz.com>
686+
- Vo Hong Thien <thienvh@trobz.com>
655687
656688
Other credits
657689
-------------

queue_job/delay.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,51 @@ def delay(self):
525525
"""Delay the whole graph"""
526526
self._graph.delay()
527527

528+
def split(self, size, chain=False):
529+
"""Split the Delayables.
530+
531+
Use `DelayableGroup` or `DelayableChain`
532+
if `chain` is True containing batches of size `size`
533+
"""
534+
if not self._job_method:
535+
raise ValueError("No method set on the Delayable")
536+
537+
total_records = len(self.recordset)
538+
539+
delayables = []
540+
for index in range(0, total_records, size):
541+
recordset = self.recordset[index : index + size]
542+
delayable = Delayable(
543+
recordset,
544+
priority=self.priority,
545+
eta=self.eta,
546+
max_retries=self.max_retries,
547+
description=self.description,
548+
channel=self.channel,
549+
identity_key=self.identity_key,
550+
)
551+
# Update the __self__
552+
delayable._job_method = getattr(recordset, self._job_method.__name__)
553+
delayable._job_args = self._job_args
554+
delayable._job_kwargs = self._job_kwargs
555+
556+
delayables.append(delayable)
557+
558+
description = self.description or (
559+
self._job_method.__doc__.splitlines()[0].strip()
560+
if self._job_method.__doc__
561+
else f"{self.recordset._name}.{self._job_method.__name__}"
562+
)
563+
for index, delayable in enumerate(delayables):
564+
delayable.set(
565+
description=f"{description} (split {index + 1}/{len(delayables)})"
566+
)
567+
568+
# Prevent warning on deletion
569+
self._generated_job = True
570+
571+
return (DelayableChain if chain else DelayableGroup)(*delayables)
572+
528573
def _build_job(self):
529574
if self._generated_job:
530575
return self._generated_job

0 commit comments

Comments
 (0)