odoo/ext/3rd-party-addons/queue_job/controllers/main.py

125 lines
3.9 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) 2015-2016 ACSONE SA/NV (<http://acsone.eu>)
# Copyright 2013-2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
import logging
import traceback
from io import StringIO
from psycopg2 import OperationalError
import odoo
from odoo import _, http, tools
from odoo.service.model import PG_CONCURRENCY_ERRORS_TO_RETRY
from ..job import Job, ENQUEUED
from ..exception import (NoSuchJobError,
NotReadableJobError,
RetryableJobError,
FailedJobError,
NothingToDoJob)
_logger = logging.getLogger(__name__)
PG_RETRY = 5 # seconds
class RunJobController(http.Controller):
def _load_job(self, env, job_uuid):
""" Reload a job from the backend """
try:
job = Job.load(env, job_uuid)
except NoSuchJobError:
# just skip it
job = None
except NotReadableJobError:
_logger.exception('Could not read job: %s', job_uuid)
raise
return job
def _try_perform_job(self, env, job):
"""Try to perform the job."""
# 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.uuid, job.state)
return
# TODO: set_started should be done atomically with
# update queue_job set=state=started
# where state=enqueid and id=
job.set_started()
job.store()
http.request.env.cr.commit()
_logger.debug('%s started', job)
job.perform()
job.set_done()
job.store()
http.request.env.cr.commit()
_logger.debug('%s done', job)
@http.route('/queue_job/runjob', type='http', auth='none')
def runjob(self, db, job_uuid, **kw):
http.request.session.db = db
env = http.request.env(user=odoo.SUPERUSER_ID)
def retry_postpone(job, message, seconds=None):
job.postpone(result=message, seconds=seconds)
job.set_pending(reset_retry=False)
job.store()
env.cr.commit()
job = self._load_job(env, job_uuid)
if job is None:
return ""
env.cr.commit()
try:
try:
self._try_perform_job(env, 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, tools.ustr(err.pgerror, errors='replace'),
seconds=PG_RETRY)
_logger.debug('%s OperationalError, postponed', job)
except NothingToDoJob as err:
if str(err):
msg = str(err)
else:
msg = _('Job interrupted and set to Done: nothing to do.')
job.set_done(msg)
job.store()
env.cr.commit()
except RetryableJobError as err:
# delay the job later, requeue
retry_postpone(job, str(err), seconds=err.seconds)
_logger.debug('%s postponed', job)
except (FailedJobError, Exception):
buff = StringIO()
traceback.print_exc(file=buff)
_logger.error(buff.getvalue())
job.env.clear()
with odoo.api.Environment.manage():
with odoo.registry(job.env.cr.dbname).cursor() as new_cr:
job.env = job.env(cr=new_cr)
job.set_failed(exc_info=buff.getvalue())
job.store()
new_cr.commit()
raise
return ""