Skip to content

Commit 0c858f9

Browse files
committed
fix: Add __mp_main__ as a duplicate for __main__ for pickle to work
Signed-off-by: yihong0618 <zouzou0208@gmail.com>
1 parent 88ad41f commit 0c858f9

3 files changed

Lines changed: 55 additions & 2 deletions

File tree

Lib/profiling/sampling/_sync_coordinator.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import socket
1111
import runpy
1212
import time
13+
import types
1314
from typing import List, NoReturn
1415

1516

@@ -176,9 +177,15 @@ def _execute_script(script_path: str, script_args: List[str], cwd: str) -> None:
176177
with open(script_path, 'rb') as f:
177178
source_code = f.read()
178179

179-
# Compile and execute the script
180+
# gh-140729: Create a __mp_main__ module to allow pickling
181+
main_module = types.ModuleType("__main__")
182+
main_module.__file__ = script_path
183+
main_module.__builtins__ = __builtins__
184+
sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module
185+
180186
code = compile(source_code, script_path, 'exec')
181-
exec(code, {'__name__': '__main__', '__file__': script_path})
187+
exec(code, main_module.__dict__)
188+
182189
except FileNotFoundError as e:
183190
raise TargetError(f"Script file not found: {script_path}") from e
184191
except PermissionError as e:

Lib/test/test_profiling/test_sampling_profiler.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2991,5 +2991,49 @@ def test_parse_mode_function(self):
29912991
profiling.sampling.sample._parse_mode("invalid")
29922992

29932993

2994+
@requires_subprocess()
2995+
@skip_if_not_supported
2996+
class TestProcessPoolExecutorSupport(unittest.TestCase):
2997+
"""
2998+
Test that ProcessPoolExecutor works correctly with profiling.sampling.
2999+
"""
3000+
3001+
def test_process_pool_executor_pickle(self):
3002+
# gh-140729: test use ProcessPoolExecutor.map() can sampling
3003+
test_script = '''
3004+
import concurrent.futures
3005+
3006+
def worker(x):
3007+
return x * 2
3008+
3009+
if __name__ == "__main__":
3010+
with concurrent.futures.ProcessPoolExecutor() as executor:
3011+
results = list(executor.map(worker, [1, 2, 3]))
3012+
print(f"Results: {results}")
3013+
'''
3014+
with tempfile.NamedTemporaryFile(
3015+
mode='w', suffix='.py', delete=False
3016+
) as script_file:
3017+
script_file.write(test_script)
3018+
script_file.flush()
3019+
script_name = script_file.name
3020+
3021+
result = subprocess.run(
3022+
[
3023+
sys.executable,
3024+
"-m", "profiling.sampling.sample",
3025+
"-d", "1",
3026+
"-i", "100000",
3027+
script_name
3028+
],
3029+
capture_output=True,
3030+
text=True,
3031+
timeout=10
3032+
)
3033+
3034+
self.assertIn("Results: [2, 4, 6]", result.stdout)
3035+
self.assertNotIn("Can't pickle", result.stderr)
3036+
3037+
29943038
if __name__ == "__main__":
29953039
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix: Add __mp_main__ as a duplicate for __main__ for pickle to work in
2+
sampling

0 commit comments

Comments
 (0)