8
8
from dataclasses import dataclass
9
9
from io import StringIO
10
10
11
+ PROCESS_KILLED = - 9
12
+ PROCESS_NO_RESULT = - 999
13
+
14
+
15
+ # Similar to https://docs.python.org/3/library/subprocess.html#subprocess.CompletedProcess
16
+ # (args, check_returncode() are intentionally not supported here.)
17
+ @dataclass
18
+ class CompletedProcess :
19
+ returncode : int
20
+ stdout : str
21
+ stderr : str
22
+
11
23
12
24
class Worker :
13
25
def __init__ (self , result_queue , func , args , kwargs ):
14
26
self .func = func
15
- self .args = args or ()
16
- self .kwargs = kwargs or {}
27
+ self .args = () if args is None else args
28
+ self .kwargs = {} if kwargs is None else kwargs
17
29
self .result_queue = result_queue
18
30
19
31
def __call__ (self ):
@@ -44,17 +56,14 @@ def __call__(self):
44
56
pass
45
57
46
58
47
- # Similar to https://docs.python.org/3/library/subprocess.html#subprocess.CompletedProcess
48
- # (args, check_returncode() are intentionally not supported here.)
49
- @dataclass
50
- class CompletedProcess :
51
- returncode : int
52
- stdout : str
53
- stderr : str
59
+ def run_in_spawned_child_process (func , * , args = None , kwargs = None , timeout = None ):
60
+ """Run `func` in a spawned child process, capturing stdout/stderr.
54
61
62
+ The provided `func` must be defined at the top level of a module, and must
63
+ be importable in the spawned child process. Lambdas, closures, or interactively
64
+ defined functions (e.g., in Jupyter notebooks) will not work.
65
+ """
55
66
56
- def run_in_spawned_child_process (func , * , args = None , kwargs = None , timeout = None ):
57
- """Run Python code in a spawned child process, capturing stdout/stderr/output."""
58
67
ctx = multiprocessing .get_context ("spawn" )
59
68
result_queue = ctx .Queue ()
60
69
process = ctx .Process (target = Worker (result_queue , func , args , kwargs ))
@@ -66,7 +75,7 @@ def run_in_spawned_child_process(func, *, args=None, kwargs=None, timeout=None):
66
75
process .terminate ()
67
76
process .join ()
68
77
return CompletedProcess (
69
- returncode = - 9 ,
78
+ returncode = PROCESS_KILLED ,
70
79
stdout = "" ,
71
80
stderr = f"Process timed out after { timeout } seconds and was terminated." ,
72
81
)
@@ -75,7 +84,7 @@ def run_in_spawned_child_process(func, *, args=None, kwargs=None, timeout=None):
75
84
returncode , stdout , stderr = result_queue .get (timeout = 1.0 )
76
85
except (queue .Empty , EOFError ):
77
86
return CompletedProcess (
78
- returncode = - 999 ,
87
+ returncode = PROCESS_NO_RESULT ,
79
88
stdout = "" ,
80
89
stderr = "Process exited or crashed before returning results." ,
81
90
)
0 commit comments