Browse Source

[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>
Markus Heiser 1 day ago
parent
commit
fe52290e65
1 changed files with 17 additions and 19 deletions
  1. 17 19
      searx/plugins/calculator.py

+ 17 - 19
searx/plugins/calculator.py

@@ -40,6 +40,22 @@ class SXNGPlugin(Plugin):
             preference_section="general",
             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:
     def post_search(self, request: "SXNG_Request", search: "SearchWithPlugins") -> EngineResults:
         results = EngineResults()
         results = EngineResults()
 
 
@@ -72,7 +88,7 @@ class SXNGPlugin(Plugin):
         query_py_formatted = query.replace("^", "**")
         query_py_formatted = query.replace("^", "**")
 
 
         # Prevent the runtime from being longer than 50 ms
         # 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] == "":
         if res is None or res[0] == "":
             return results
             return results
 
 
@@ -200,21 +216,3 @@ def handler(q: multiprocessing.Queue, func, args, **kwargs):  # pylint:disable=i
     except:
     except:
         q.put(None)
         q.put(None)
         raise
         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