2727
2828from __future__ import annotations
2929
30+ import contextlib
3031import functools
3132import multiprocessing as mp
3233import pickle
3334import sys
35+ import threading
3436
3537import pytest
3638from datafusion import col , lit
3739
3840from . import _pickle_multiprocessing_helpers as helpers
3941
4042
43+ @contextlib .contextmanager
44+ def _snapshot_on_hang (label : str , fire_after_seconds : float = 30.0 ):
45+ """Schedule a process-state snapshot ``fire_after_seconds`` from now.
46+
47+ Cancelled if the ``with`` block exits before then. Used to capture
48+ worker state mid-hang — fork tests return in well under the delay,
49+ so the timer only fires when something is actually stuck.
50+ """
51+ timer = threading .Timer (
52+ fire_after_seconds ,
53+ helpers .snapshot_processes ,
54+ args = (label ,),
55+ )
56+ timer .daemon = True
57+ timer .start ()
58+ try :
59+ yield
60+ finally :
61+ timer .cancel ()
62+
63+
4164@functools .cache
4265def _multiprocessing_available () -> tuple [bool , str ]:
4366 """Return (available, reason). Some sandboxed environments deny semaphore
@@ -86,7 +109,10 @@ def test_builtin_pickle_via_pool(start_method):
86109
87110 ctx = mp .get_context (start_method )
88111 helpers ._diag (f"test_builtin_pickle_via_pool[{ start_method } ]: creating Pool" )
89- with ctx .Pool (processes = 2 , initializer = helpers .diag_init ) as pool :
112+ with (
113+ ctx .Pool (processes = 2 , initializer = helpers .diag_init ) as pool ,
114+ _snapshot_on_hang (f"builtin[{ start_method } ]" ),
115+ ):
90116 helpers ._diag (f"test_builtin_pickle_via_pool[{ start_method } ]: pool ready, map" )
91117 results = pool .map (helpers .unpickle_and_describe , [blob , blob , blob ])
92118 helpers ._diag (f"test_builtin_pickle_via_pool[{ start_method } ]: pool closed" )
@@ -109,7 +135,10 @@ def test_udf_pickle_self_contained(start_method):
109135
110136 ctx = mp .get_context (start_method )
111137 helpers ._diag (f"test_udf_pickle_self_contained[{ start_method } ]: creating Pool" )
112- with ctx .Pool (processes = 2 , initializer = helpers .diag_init ) as pool :
138+ with (
139+ ctx .Pool (processes = 2 , initializer = helpers .diag_init ) as pool ,
140+ _snapshot_on_hang (f"udf[{ start_method } ]" ),
141+ ):
113142 helpers ._diag (
114143 f"test_udf_pickle_self_contained[{ start_method } ]: pool ready, starmap"
115144 )
@@ -134,7 +163,10 @@ def test_closure_capturing_udf_via_pool(start_method):
134163
135164 ctx = mp .get_context (start_method )
136165 helpers ._diag (f"test_closure_capturing_udf_via_pool[{ start_method } ]: creating Pool" )
137- with ctx .Pool (processes = 2 , initializer = helpers .diag_init ) as pool :
166+ with (
167+ ctx .Pool (processes = 2 , initializer = helpers .diag_init ) as pool ,
168+ _snapshot_on_hang (f"closure[{ start_method } ]" ),
169+ ):
138170 helpers ._diag (
139171 f"test_closure_capturing_udf_via_pool[{ start_method } ]: pool ready, apply"
140172 )
0 commit comments