[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:
+19
-6
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user