From 3b8cdd342a1cd1fc0bf656d9929a622eb770e70e Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Thu, 10 Dec 2020 11:59:29 -0500 Subject: [PATCH] Avoid deep recursion when handling a large queue of events Because each event handler wrapper would call scheduler.run() at the end before returning, with a queue of 100 events to process we'd end up with: Event handler 1 wrapper scheduler.run() Event handler 2 wrapper scheduler.run() ..... .... Event handler 100 wrapper Which would eventually exhaust the Python stack limit. Fix this by making scheduler.run() no-op if the scheduler is already processing the queue in the current thread. --- module_build_service/scheduler/events.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/module_build_service/scheduler/events.py b/module_build_service/scheduler/events.py index b02dc7bf..c7eaf573 100644 --- a/module_build_service/scheduler/events.py +++ b/module_build_service/scheduler/events.py @@ -15,6 +15,7 @@ however Brew sends to topic brew.build.complete, etc. from __future__ import absolute_import from functools import wraps import sched +import threading import time from module_build_service.common import log @@ -40,6 +41,10 @@ class Scheduler(sched.scheduler): are executed. """ + def __init__(self, *args, **kwargs): + sched.scheduler.__init__(self, *args, **kwargs) + self.local = threading.local() + def add(self, handler, arguments=()): """ Schedule execution of `handler` with `arguments`. @@ -50,10 +55,20 @@ class Scheduler(sched.scheduler): """ Runs scheduled handlers. """ - log.debug("Running event scheduler with following events:") - for event in self.queue: - log.debug(" %r", event) - sched.scheduler.run(self) + if getattr(self.local, 'running', False): + return + + try: + self.local.running = True + # Note that events that are added during the execution of the + # handlers are executed as part of the .run() call without + # further logging. + log.debug("Running event scheduler with following events:") + for event in self.queue: + log.debug(" %r", event) + sched.scheduler.run(self) + finally: + self.local.running = False def reset(self): """