From fe52290e653e969c959b3160c1a09f02e751efd2 Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Tue, 8 Jul 2025 09:30:41 +0200 Subject: [PATCH] [fix] calculator plugin: subrocess is not closed on timeout (#4983) The issue was introduced in commit: edfbf1e Problematic code:: def timeout_func(timeout, func, *args, **kwargs): ... if not p.is_alive(): ret_val = que.get() else: logger.debug("terminate function after timeout is exceeded") # type: ignore p.terminate() p.join() p.close() The `logger` function in the `else` path is not defined. Was accidentally removed in commit edfbf1e without providing an appropriate replacement.:: File "/usr/local/searxng/searx/plugins/calculator.py", line 216, in timeout_func logger.debug("terminate function after timeout is exceeded") # type: ignore ^^^^^^ NameError: name 'logger' is not defined The exception triggered by this prevents the `p.terminate()` from being executed. As a result, the processes accumulate in memory (memory leak). Related: https://github.com/searxng/searx-instances/discussions/708#discussioncomment-13688168 Signed-off-by: Markus Heiser --- searx/plugins/calculator.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/searx/plugins/calculator.py b/searx/plugins/calculator.py index af44eea1e..5adaaaf22 100644 --- a/searx/plugins/calculator.py +++ b/searx/plugins/calculator.py @@ -40,6 +40,22 @@ class SXNGPlugin(Plugin): preference_section="general", ) + def timeout_func(self, timeout, func, *args, **kwargs): + que = mp_fork.Queue() + p = mp_fork.Process(target=handler, args=(que, func, args), kwargs=kwargs) + p.start() + p.join(timeout=timeout) + ret_val = None + # pylint: disable=used-before-assignment,undefined-variable + if not p.is_alive(): + ret_val = que.get() + else: + self.log.debug("terminate function (%s: %s // %s) after timeout is exceeded", func.__name__, args, kwargs) + p.terminate() + p.join() + p.close() + return ret_val + def post_search(self, request: "SXNG_Request", search: "SearchWithPlugins") -> EngineResults: results = EngineResults() @@ -72,7 +88,7 @@ class SXNGPlugin(Plugin): query_py_formatted = query.replace("^", "**") # Prevent the runtime from being longer than 50 ms - res = timeout_func(0.05, _eval_expr, query_py_formatted) + res = self.timeout_func(0.05, _eval_expr, query_py_formatted) if res is None or res[0] == "": return results @@ -200,21 +216,3 @@ def handler(q: multiprocessing.Queue, func, args, **kwargs): # pylint:disable=i except: q.put(None) raise - - -def timeout_func(timeout, func, *args, **kwargs): - - que = mp_fork.Queue() - p = mp_fork.Process(target=handler, args=(que, func, args), kwargs=kwargs) - p.start() - p.join(timeout=timeout) - ret_val = None - # pylint: disable=used-before-assignment,undefined-variable - if not p.is_alive(): - ret_val = que.get() - else: - logger.debug("terminate function after timeout is exceeded") # type: ignore - p.terminate() - p.join() - p.close() - return ret_val