-
-
Notifications
You must be signed in to change notification settings - Fork 442
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #52 from acsone/8.0-jobrunner-sbi
[8.0] job runner
- Loading branch information
Showing
26 changed files
with
1,610 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import logging | ||
import traceback | ||
from cStringIO import StringIO | ||
|
||
from psycopg2 import OperationalError | ||
|
||
import openerp | ||
from openerp import http | ||
from openerp.service.model import PG_CONCURRENCY_ERRORS_TO_RETRY | ||
|
||
from ..session import ConnectorSessionHandler | ||
from ..queue.job import (OpenERPJobStorage, | ||
ENQUEUED) | ||
from ..exception import (NoSuchJobError, | ||
NotReadableJobError, | ||
RetryableJobError, | ||
FailedJobError, | ||
NothingToDoJob) | ||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
PG_RETRY = 5 # seconds | ||
|
||
|
||
# TODO: perhaps the notion of ConnectionSession is less important | ||
# now that we are running jobs inside a normal Odoo worker | ||
|
||
|
||
class RunJobController(http.Controller): | ||
|
||
job_storage_class = OpenERPJobStorage | ||
|
||
def _load_job(self, session, job_uuid): | ||
""" Reload a job from the backend """ | ||
try: | ||
job = self.job_storage_class(session).load(job_uuid) | ||
except NoSuchJobError: | ||
# just skip it | ||
job = None | ||
except NotReadableJobError: | ||
_logger.exception('Could not read job: %s', job_uuid) | ||
raise | ||
return job | ||
|
||
@http.route('/connector/runjob', type='http', auth='none') | ||
def runjob(self, db, job_uuid, **kw): | ||
|
||
session_hdl = ConnectorSessionHandler(db, | ||
openerp.SUPERUSER_ID) | ||
|
||
def retry_postpone(job, message, seconds=None): | ||
with session_hdl.session() as session: | ||
job.postpone(result=message, seconds=seconds) | ||
job.set_pending(self) | ||
self.job_storage_class(session).store(job) | ||
|
||
with session_hdl.session() as session: | ||
job = self._load_job(session, job_uuid) | ||
if job is None: | ||
return "" | ||
|
||
try: | ||
# if the job has been manually set to DONE or PENDING, | ||
# or if something tries to run a job that is not enqueued | ||
# before its execution, stop | ||
if job.state != ENQUEUED: | ||
_logger.warning('job %s is in state %s ' | ||
'instead of enqueued in /runjob', | ||
job.state, job_uuid) | ||
return | ||
|
||
with session_hdl.session() as session: | ||
# TODO: set_started should be done atomically with | ||
# update queue_job set=state=started | ||
# where state=enqueid and id= | ||
job.set_started() | ||
self.job_storage_class(session).store(job) | ||
|
||
_logger.debug('%s started', job) | ||
with session_hdl.session() as session: | ||
job.perform(session) | ||
job.set_done() | ||
self.job_storage_class(session).store(job) | ||
_logger.debug('%s done', job) | ||
|
||
except NothingToDoJob as err: | ||
if unicode(err): | ||
msg = unicode(err) | ||
else: | ||
msg = None | ||
job.cancel(msg) | ||
with session_hdl.session() as session: | ||
self.job_storage_class(session).store(job) | ||
|
||
except RetryableJobError as err: | ||
# delay the job later, requeue | ||
retry_postpone(job, unicode(err)) | ||
_logger.debug('%s postponed', job) | ||
|
||
except OperationalError as err: | ||
# Automatically retry the typical transaction serialization errors | ||
if err.pgcode not in PG_CONCURRENCY_ERRORS_TO_RETRY: | ||
raise | ||
retry_postpone(job, unicode(err), seconds=PG_RETRY) | ||
_logger.debug('%s OperationalError, postponed', job) | ||
|
||
except (FailedJobError, Exception): | ||
buff = StringIO() | ||
traceback.print_exc(file=buff) | ||
_logger.error(buff.getvalue()) | ||
|
||
job.set_failed(exc_info=buff.getvalue()) | ||
with session_hdl.session() as session: | ||
self.job_storage_class(session).store(job) | ||
raise | ||
|
||
return "" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
######## | ||
Channels | ||
######## | ||
|
||
This is the API documentation for the job channels and the | ||
scheduling mechanisms of the job runner. | ||
|
||
These classes are not intended for use by module developers. | ||
|
||
.. automodule:: connector.jobrunner.channels | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
########## | ||
Job Runner | ||
########## | ||
|
||
This is the API documentation for the job runner. | ||
|
||
These classes are not intended for use by module developers. | ||
|
||
.. automodule:: connector.jobrunner.runner | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
.. _jobrunner: | ||
|
||
|
||
####################################### | ||
Configuring channels and the job runner | ||
####################################### | ||
|
||
.. automodule:: connector.jobrunner.runner | ||
|
||
What is a channel? | ||
------------------ | ||
|
||
.. autoclass:: connector.jobrunner.channels.Channel | ||
|
||
How to configure Channels? | ||
-------------------------- | ||
|
||
The ``ODOO_CONNECTOR_CHANNELS`` environment variable must be | ||
set before starting Odoo in order to enable the job runner | ||
and configure the capacity of the channels. | ||
|
||
The general syntax is ``channel(.subchannel)*(:capacity(:key(=value)?)*)?,...``. | ||
|
||
Intermediate subchannels which are not configured explicitly are autocreated | ||
with an unlimited capacity (except the root channel which if not configured gets | ||
a default capacity of 1). | ||
|
||
Example ``ODOO_CONNECTOR_CHANNELS``: | ||
|
||
* ``root:4``: allow up to 4 concurrent jobs in the root channel. | ||
* ``root:4,root.sub:2``: allow up to 4 concurrent jobs in the root channel and | ||
up to 2 concurrent jobs in the channel named ``root.sub``. | ||
* ``sub:2``: the same. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
# -*- coding: utf-8 -*- | ||
############################################################################## | ||
# | ||
# This file is part of connector, an Odoo module. | ||
# | ||
# Author: Stéphane Bidoul <[email protected]> | ||
# Copyright (c) 2015 ACSONE SA/NV (<http://acsone.eu>) | ||
# | ||
# connector is free software: you can redistribute it and/or | ||
# modify it under the terms of the GNU Affero General Public License | ||
# as published by the Free Software Foundation, either version 3 of | ||
# the License, or (at your option) any later version. | ||
# | ||
# connector is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU Affero General Public License for more details. | ||
# | ||
# You should have received a copy of the | ||
# GNU Affero General Public License | ||
# along with connector. | ||
# If not, see <http://www.gnu.org/licenses/>. | ||
# | ||
############################################################################## | ||
|
||
import logging | ||
import os | ||
from threading import Thread | ||
import time | ||
|
||
from openerp.service import server | ||
from openerp.tools import config | ||
|
||
from .runner import ConnectorRunner | ||
|
||
_logger = logging.getLogger(__name__) | ||
|
||
START_DELAY = 5 | ||
|
||
|
||
# Here we monkey patch the Odoo server to start the job runner thread | ||
# in the main server process (and not in forked workers). This is | ||
# very easy to deploy as we don't need another startup script. | ||
# The drawback is that it is not possible to extend the Odoo | ||
# server command line arguments, so we resort to environment variables | ||
# to configure the runner (channels mostly). | ||
|
||
|
||
enable = os.environ.get('ODOO_CONNECTOR_CHANNELS') | ||
|
||
|
||
def run(): | ||
# sleep a bit to let the workers start at ease | ||
time.sleep(START_DELAY) | ||
port = os.environ.get('ODOO_CONNECTOR_PORT') or config['xmlrpc_port'] | ||
channels = os.environ.get('ODOO_CONNECTOR_CHANNELS') | ||
runner = ConnectorRunner(port or 8069, channels or 'root:1') | ||
runner.run_forever() | ||
|
||
|
||
orig_prefork_start = server.PreforkServer.start | ||
orig_threaded_start = server.ThreadedServer.start | ||
orig_gevent_start = server.GeventServer.start | ||
|
||
|
||
def prefork_start(server, *args, **kwargs): | ||
res = orig_prefork_start(server, *args, **kwargs) | ||
if enable and not config['stop_after_init']: | ||
_logger.info("starting jobrunner thread (in prefork server)") | ||
thread = Thread(target=run) | ||
thread.daemon = True | ||
thread.start() | ||
return res | ||
|
||
|
||
def threaded_start(server, *args, **kwargs): | ||
res = orig_threaded_start(server, *args, **kwargs) | ||
if enable and not config['stop_after_init']: | ||
_logger.info("starting jobrunner thread (in threaded server)") | ||
thread = Thread(target=run) | ||
thread.daemon = True | ||
thread.start() | ||
return res | ||
|
||
|
||
def gevent_start(server, *args, **kwargs): | ||
res = orig_gevent_start(server, *args, **kwargs) | ||
if enable and not config['stop_after_init']: | ||
_logger.info("starting jobrunner thread (in gevent server)") | ||
# TODO: gevent spawn? | ||
raise RuntimeError("not implemented") | ||
return res | ||
|
||
|
||
server.PreforkServer.start = prefork_start | ||
server.ThreadedServer.start = threaded_start | ||
server.GeventServer.start = gevent_start |
Oops, something went wrong.