[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 <markus.heiser@darmarit.de>
This commit is contained in:
Markus Heiser 2025-07-08 09:30:41 +02:00 committed by GitHub
parent 6ff4035635
commit fe52290e65
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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