Files
tryton/trytond/worker.py
T
Cédric Krier 0b6c923834 Add increasing delay on OperationalError
This increase the success rate of retry if the other transaction last.

issue8014
review50701002
2019-02-04 22:23:12 +01:00

119 lines
3.8 KiB
Python

# 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 logging
import select
import signal
import time
from multiprocessing import Pool as MPool, cpu_count
from sql import Flavor
from trytond import backend
from trytond.config import config
from trytond.pool import Pool
from trytond.transaction import Transaction
__all__ = ['work']
logger = logging.getLogger(__name__)
Database = backend.get('Database')
DatabaseOperationalError = backend.get('DatabaseOperationalError')
class Queue(object):
def __init__(self, pool, mpool):
self.database = Database(pool.database_name).connect()
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):
Flavor.set(Database.flavor)
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')
logger.info('task "%d" started', task_id)
try:
retry = config.getint('database', 'retry')
for count in range(retry, -1, -1):
if count != retry:
time.sleep(0.02 * (retry - count))
with Transaction().start(pool.database_name, 0) as transaction:
try:
Queue(task_id).run()
break
except DatabaseOperationalError:
if count:
transaction.rollback()
continue
raise
logger.info('task "%d" done', task_id)
except Exception:
logger.critical('task "%d" failed', task_id, exc_info=True)