[FIX] core: allow signal handlers in multiprocess workers

Because Python only runs signal handlers on the main thread, native
calls (IO or accept(2)) can delay these handlers running. This is
especially problematic when one such call is completely stuck and
we're trying to dump the stack to diagnose the issue.

By running the actual worker's processing in a sub-thread and leaving
the main thread sleeping, worker processes should always be able to
handle signals.

closes odoo/odoo#30688
This commit is contained in:
Xavier Morel
2019-01-30 11:22:05 +00:00
parent 1dbe4ea0d7
commit 6ce2d6efb5
+19 -6
View File
@@ -685,7 +685,7 @@ class Worker(object):
if e.args[0] not in [errno.EINTR]: if e.args[0] not in [errno.EINTR]:
raise raise
def process_limit(self): def check_limits(self):
# If our parent changed sucide # If our parent changed sucide
if self.ppid != os.getppid(): if self.ppid != os.getppid():
_logger.info("Worker (%s) Parent changed", self.pid) _logger.info("Worker (%s) Parent changed", self.pid)
@@ -704,6 +704,7 @@ class Worker(object):
soft, hard = resource.getrlimit(resource.RLIMIT_AS) soft, hard = resource.getrlimit(resource.RLIMIT_AS)
resource.setrlimit(resource.RLIMIT_AS, (config['limit_memory_hard'], hard)) resource.setrlimit(resource.RLIMIT_AS, (config['limit_memory_hard'], hard))
def set_limits(self):
# SIGXCPU (exceeded CPU time) signal handler will raise an exception. # SIGXCPU (exceeded CPU time) signal handler will raise an exception.
r = resource.getrusage(resource.RUSAGE_SELF) r = resource.getrusage(resource.RUSAGE_SELF)
cpu_time = r.ru_utime + r.ru_stime cpu_time = r.ru_utime + r.ru_stime
@@ -736,17 +737,18 @@ class Worker(object):
signal.signal(signal.SIGCHLD, signal.SIG_DFL) signal.signal(signal.SIGCHLD, signal.SIG_DFL)
signal.set_wakeup_fd(self.wakeup_fd_w) signal.set_wakeup_fd(self.wakeup_fd_w)
self.set_limits()
def stop(self): def stop(self):
pass pass
def run(self): def run(self):
try: try:
self.start() self.start()
while self.alive: t = threading.Thread(name="Worker %s (%s) workthread" % (self.__class__.__name__, self.pid), target=self._runloop)
self.process_limit() t.daemon = True
self.multi.pipe_ping(self.watchdog_pipe) t.start()
self.sleep() t.join()
self.process_work()
_logger.info("Worker (%s) exiting. request_count: %s, registry count: %s.", _logger.info("Worker (%s) exiting. request_count: %s, registry count: %s.",
self.pid, self.request_count, self.pid, self.request_count,
len(odoo.modules.registry.Registry.registries)) len(odoo.modules.registry.Registry.registries))
@@ -756,6 +758,17 @@ class Worker(object):
# should we use 3 to abort everything ? # should we use 3 to abort everything ?
sys.exit(1) sys.exit(1)
def _runloop(self):
try:
while self.alive:
self.multi.pipe_ping(self.watchdog_pipe)
self.sleep()
self.process_work()
self.check_limits()
except:
_logger.exception("Worker %s (%s) Exception occured, exiting...", self.__class__.__name__, self.pid)
sys.exit(1)
class WorkerHTTP(Worker): class WorkerHTTP(Worker):
""" HTTP Request workers """ """ HTTP Request workers """
def process_request(self, client, addr): def process_request(self, client, addr):