This increase the success rate of retry if the other transaction last. issue8014 review50701002
119 lines
3.8 KiB
Python
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)
|