You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by tv...@apache.org on 2025/10/16 19:11:03 UTC

(beam) branch master updated: Timeout execution tree creation for SDK worker ops. (#36200)

This is an automated email from the ASF dual-hosted git repository.

tvalentyn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new d91fb6d6987 Timeout execution tree creation for SDK worker ops. (#36200)
d91fb6d6987 is described below

commit d91fb6d6987310e8ce6e50e3f3af7afc628f038d
Author: tvalentyn <tv...@users.noreply.github.com>
AuthorDate: Thu Oct 16 12:10:55 2025 -0700

    Timeout execution tree creation for SDK worker ops. (#36200)
    
    * Timeout execution tree creation for SDK worker ops.
    
    * Run tests
    
    * Update the error message
    
    * Minor change to termination logic
    
    * Grammar
---
 .../apache_beam/runners/worker/bundle_processor.py | 27 +++++++++++++++++++++-
 .../apache_beam/runners/worker/sdk_worker_main.py  |  4 ++++
 .../apache_beam/runners/worker/worker_status.py    | 16 ++++++++++---
 3 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/sdks/python/apache_beam/runners/worker/bundle_processor.py b/sdks/python/apache_beam/runners/worker/bundle_processor.py
index ad48358d588..85f1e43d603 100644
--- a/sdks/python/apache_beam/runners/worker/bundle_processor.py
+++ b/sdks/python/apache_beam/runners/worker/bundle_processor.py
@@ -24,6 +24,7 @@ from __future__ import annotations
 import base64
 import bisect
 import collections
+import concurrent.futures
 import copy
 import heapq
 import itertools
@@ -76,6 +77,7 @@ from apache_beam.runners.worker import data_sampler
 from apache_beam.runners.worker import operation_specs
 from apache_beam.runners.worker import operations
 from apache_beam.runners.worker import statesampler
+from apache_beam.runners.worker.worker_status import thread_dump
 from apache_beam.transforms import TimeDomain
 from apache_beam.transforms import core
 from apache_beam.transforms import environments
@@ -1130,7 +1132,30 @@ class BundleProcessor(object):
         'fnapi-step-%s' % self.process_bundle_descriptor.id,
         self.counter_factory)
 
-    self.ops = self.create_execution_tree(self.process_bundle_descriptor)
+    with concurrent.futures.ThreadPoolExecutor(
+        max_workers=1, thread_name_prefix='ExecutionTreeCreator') as executor:
+      future = executor.submit(
+          self.create_execution_tree, self.process_bundle_descriptor)
+      try:
+        self.ops = future.result(timeout=3600)
+      except concurrent.futures.TimeoutError:
+        # In rare cases, unpickling a DoFn might get permanently stuck,
+        # for example when unpickling involves importing a module and
+        # a subprocess is launched during the import operation.
+        _LOGGER.error(
+            'Timed out while reconstructing a pipeline fragment for: %s.\n'
+            'This is likely a transient error. The SDK harness '
+            'will self-terminate, and the runner can retry the operation. '
+            'If the error is frequent, check whether the stuckness happens '
+            'while deserializing (unpickling) a dependency of your pipeline '
+            'in the stacktrace below: \n%s\n',
+            self.process_bundle_descriptor.id,
+            thread_dump('ExecutionTreeCreator'))
+        # Raising an exception here doesn't interrupt the left-over thread.
+        # Out of caution, terminate the SDK harness process.
+        from apache_beam.runners.worker.sdk_worker_main import terminate_sdk_harness
+        terminate_sdk_harness()
+
     for op in reversed(self.ops.values()):
       op.setup(self.data_sampler)
     self.splitting_lock = threading.Lock()
diff --git a/sdks/python/apache_beam/runners/worker/sdk_worker_main.py b/sdks/python/apache_beam/runners/worker/sdk_worker_main.py
index 7ea0e0eb109..cdb807e8dbc 100644
--- a/sdks/python/apache_beam/runners/worker/sdk_worker_main.py
+++ b/sdks/python/apache_beam/runners/worker/sdk_worker_main.py
@@ -233,6 +233,10 @@ def terminate_sdk_harness():
   if _FN_LOG_HANDLER:
     _FN_LOG_HANDLER.close()
   os.kill(os.getpid(), signal.SIGINT)
+  # Delay further control flow in the caller until process is terminated.
+  time.sleep(60)
+  # Try to force-terminate if still running.
+  os.kill(os.getpid(), signal.SIGKILL)
 
 
 def _load_pipeline_options(options_json):
diff --git a/sdks/python/apache_beam/runners/worker/worker_status.py b/sdks/python/apache_beam/runners/worker/worker_status.py
index f4102b19389..1d54a3ee176 100644
--- a/sdks/python/apache_beam/runners/worker/worker_status.py
+++ b/sdks/python/apache_beam/runners/worker/worker_status.py
@@ -66,13 +66,23 @@ def _current_frames():
     return sys._current_frames()  # pylint: disable=protected-access
 
 
-def thread_dump():
-  """Get a thread dump for the current SDK worker harness. """
+def thread_dump(thread_prefix=None):
+  """Get a thread dump for the current SDK harness.
+
+  Args:
+    thread_prefix: (str) An optional prefix to filter threads by.
+  """
   # deduplicate threads with same stack trace
   stack_traces = defaultdict(list)
   frames = _current_frames()
 
-  for t in threading.enumerate():
+  threads_to_dump = threading.enumerate()
+  if thread_prefix:
+    threads_to_dump = [
+        t for t in threads_to_dump if t.name.startswith(thread_prefix)
+    ]
+
+  for t in threads_to_dump:
     try:
       stack_trace = ''.join(traceback.format_stack(frames[t.ident]))
     except KeyError: