@@ -385,6 +385,13 @@ def __init__(
385385 and not getattr (Scalene .__args , "gpu" , False )
386386 ):
387387 cmdline += " --cpu-only"
388+ # Add the --program-path so children know which files to profile.
389+ if Scalene .__program_path :
390+ path_str = str (Scalene .__program_path )
391+ if sys .platform == "win32" :
392+ cmdline += f' --program-path="{ path_str } "'
393+ else :
394+ cmdline += f" --program-path='{ path_str } '"
388395 # Add the --pid field so we can propagate it to the child.
389396 cmdline += f" --pid={ os .getpid ()} ---"
390397 # Build the commands to pass along other arguments
@@ -1620,10 +1627,12 @@ def run_profiler(
16201627 Scalene .__stats .clear_all ()
16211628 sys .argv = left
16221629 with contextlib .suppress (Exception ):
1623- # Only set start method to fork if one hasn't been set yet
1624- # This respects user's choice (e.g., spawn on macOS)
1630+ # Only set start method to fork if one hasn't been set yet.
1631+ # This respects user's choice (e.g., spawn on macOS).
1632+ # On Windows, fork is not available; leave the default (spawn).
16251633 if (
16261634 not is_jupyter
1635+ and sys .platform != "win32"
16271636 and multiprocessing .get_start_method (allow_none = True ) is None
16281637 ):
16291638 multiprocessing .set_start_method ("fork" )
@@ -1642,12 +1651,66 @@ def run_profiler(
16421651 # This is important for multiprocessing spawn mode, which checks
16431652 # sys.argv[1] == '--multiprocessing-fork'
16441653 sys .argv = [sys .argv [0 ]] + sys .argv [2 :]
1645- try :
1646- exec (code_to_exec )
1647- except SyntaxError :
1648- traceback .print_exc ()
1649- sys .exit (1 )
1650- sys .exit (0 )
1654+ if Scalene .__is_child :
1655+ # Child process launched by Scalene's redirect_python.
1656+ # Multiprocessing spawn workers (spawn_main) use pipes
1657+ # for all task/result communication. Enabling the CPU
1658+ # profiling timer (ITIMER_VIRTUAL / SIGVTALRM) in these
1659+ # workers causes the signal to fire during pipe I/O,
1660+ # corrupting pickle data and producing UnpicklingError
1661+ # or EOFError. Execute spawn workers without profiling.
1662+ _is_spawn_worker = (
1663+ "from multiprocessing" in code_to_exec
1664+ and "spawn_main" in code_to_exec
1665+ )
1666+ if _is_spawn_worker :
1667+ try :
1668+ exec (compile (code_to_exec , "-c" , "exec" ))
1669+ except SystemExit as se :
1670+ sys .exit (
1671+ se .code if isinstance (se .code , int ) else 1
1672+ )
1673+ except Exception :
1674+ traceback .print_exc ()
1675+ sys .exit (1 )
1676+ sys .exit (0 )
1677+ # Non-spawn child: profile the code.
1678+ # Set program path so _should_trace knows which files to profile.
1679+ if Scalene .__args .program_path :
1680+ Scalene .__program_path = Filename (
1681+ os .path .abspath (Scalene .__args .program_path )
1682+ )
1683+ import __main__
1684+
1685+ the_locals = __main__ .__dict__
1686+ the_globals = __main__ .__dict__
1687+ the_globals ["__file__" ] = "-c"
1688+ the_globals ["__spec__" ] = None
1689+ child_code : Any = ""
1690+ try :
1691+ child_code = compile (code_to_exec , "-c" , "exec" )
1692+ except SyntaxError :
1693+ traceback .print_exc ()
1694+ sys .exit (1 )
1695+ gc .collect ()
1696+ profiler = Scalene (args , Filename ("-c" ))
1697+ try :
1698+ exit_status = profiler .profile_code (
1699+ child_code , the_locals , the_globals , left
1700+ )
1701+ sys .exit (exit_status )
1702+ except Exception as ex :
1703+ template = "Scalene: An exception of type {0} occurred. Arguments:\n {1!r}"
1704+ message = template .format (type (ex ).__name__ , ex .args )
1705+ print (message , file = sys .stderr )
1706+ sys .exit (1 )
1707+ else :
1708+ try :
1709+ exec (code_to_exec )
1710+ except SyntaxError :
1711+ traceback .print_exc ()
1712+ sys .exit (1 )
1713+ sys .exit (0 )
16511714
16521715 if len (sys .argv ) >= 2 and sys .argv [0 ] == "-m" :
16531716 module = True
0 commit comments