Files
tryton/trytond/worker.py
T

151 lines
5.2 KiB
Python
Raw Normal View History

2018-07-27 09:32:28 +02:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime as dt
2018-07-27 09:32:28 +02:00
import logging
import random
2018-07-27 09:32:28 +02:00
import select
import signal
import time
2021-12-11 17:58:45 +01:00
from multiprocessing import Pool as MPool
from multiprocessing import cpu_count
2018-07-27 09:32:28 +02:00
from sql import Flavor
from trytond import backend
from trytond.config import config
from trytond.exceptions import UserError, UserWarning
2018-07-27 09:32:28 +02:00
from trytond.pool import Pool
2021-03-27 19:42:53 +01:00
from trytond.status import processing
2018-07-27 09:32:28 +02:00
from trytond.transaction import Transaction
__all__ = ['work']
logger = logging.getLogger(__name__)
class Queue(object):
def __init__(self, pool, mpool):
2019-12-06 17:33:47 +01:00
self.database = backend.Database(pool.database_name).connect()
2018-07-27 09:32:28 +02:00
self.connection = self.database.get_connection(autocommit=True)
self.pool = pool
self.mpool = mpool
def pull(self, name=None):
Queue = self.pool.get('ir.queue')
return Queue.pull(self.database, self.connection, name=name)
def run(self, task_id):
return self.mpool.apply_async(
run_task, (self.pool.database_name, task_id))
class TaskList(list):
def filter(self):
for t in list(self):
if t.ready():
self.remove(t)
return self
def work(options):
2019-12-06 17:33:47 +01:00
Flavor.set(backend.Database.flavor)
2018-07-27 09:32:28 +02:00
if not config.getboolean('queue', 'worker', default=False):
return
try:
processes = options.processes or cpu_count()
except NotImplementedError:
processes = 1
logger.info("start %d workers", processes)
mpool = MPool(
processes, initializer, (options,), options.maxtasksperchild)
queues = [Queue(pool, mpool) for pool in initializer(options, False)]
tasks = TaskList()
timeout = options.timeout
try:
while True:
while len(tasks.filter()) >= processes:
time.sleep(0.1)
for queue in queues:
task_id, next_ = queue.pull(options.name)
timeout = min(
next_ or options.timeout, timeout, options.timeout)
if task_id:
tasks.append(queue.run(task_id))
break
else:
connections = [q.connection for q in queues]
connections, _, _ = select.select(connections, [], [], timeout)
for connection in connections:
connection.poll()
while connection.notifies:
connection.notifies.pop(0)
except KeyboardInterrupt:
mpool.close()
def initializer(options, worker=True):
if worker:
signal.signal(signal.SIGINT, signal.SIG_IGN)
pools = []
database_list = Pool.database_list()
for database_name in options.database_names:
pool = Pool(database_name)
if database_name not in database_list:
with Transaction().start(database_name, 0, readonly=True):
pool.init()
pools.append(pool)
return pools
def run_task(pool, task_id):
if not isinstance(pool, Pool):
pool = Pool(pool)
Queue = pool.get('ir.queue')
Error = pool.get('ir.error')
2021-03-27 19:42:53 +01:00
name = '<Task %s@%s>' % (task_id, pool.database_name)
logger.info('%s started', name)
retry = config.getint('database', 'retry')
2018-07-27 09:32:28 +02:00
try:
2019-02-04 22:23:12 +01:00
for count in range(retry, -1, -1):
if count != retry:
time.sleep(0.02 * (retry - count))
2018-07-27 09:32:28 +02:00
with Transaction().start(pool.database_name, 0) as transaction:
try:
2019-10-08 19:06:18 +02:00
try:
task, = Queue.search([('id', '=', task_id)])
except ValueError:
# the task was rollbacked, nothing to do
break
2021-03-27 19:42:53 +01:00
with processing(name):
task.run()
2018-07-27 09:32:28 +02:00
break
2019-12-06 17:33:47 +01:00
except backend.DatabaseOperationalError:
2018-07-27 09:32:28 +02:00
if count:
transaction.rollback()
continue
raise
except (UserError, UserWarning) as e:
Error.log(task, e)
raise
2021-03-27 19:42:53 +01:00
logger.info('%s done', name)
except backend.DatabaseOperationalError:
2021-03-27 19:42:53 +01:00
logger.info('%s failed, retrying', name, exc_info=True)
if not config.getboolean('queue', 'worker', default=False):
time.sleep(0.02 * retry)
try:
with Transaction().start(pool.database_name, 0) as transaction:
if not transaction.database.has_channel():
logger.critical('%s failed', name, exc_info=True)
return
task = Queue(task_id)
scheduled_at = dt.datetime.now()
scheduled_at += dt.timedelta(
2020-06-27 00:54:03 +02:00
seconds=random.randint(0, 2 * retry))
Queue.push(task.name, task.data, scheduled_at=scheduled_at)
except Exception:
logger.critical(
2021-03-27 19:42:53 +01:00
'rescheduling %s failed', name, exc_info=True)
except (UserError, UserWarning):
logger.info('%s failed', name)
2018-07-27 09:32:28 +02:00
except Exception:
2021-03-27 19:42:53 +01:00
logger.critical('%s failed', name, exc_info=True)