1+ """
2+ Internal synchronization coordinator for the sample profiler.
3+
4+ This module is used internally by the sample profiler to coordinate
5+ the startup of target processes. It should not be called directly by users.
6+ """
7+
8+ import os
9+ import sys
10+ import socket
11+ import runpy
12+ import time
13+ from typing import List , NoReturn
14+
15+
16+ class CoordinatorError (Exception ):
17+ """Base exception for coordinator errors."""
18+ pass
19+
20+
21+ class ArgumentError (CoordinatorError ):
22+ """Raised when invalid arguments are provided."""
23+ pass
24+
25+
26+ class SyncError (CoordinatorError ):
27+ """Raised when synchronization with profiler fails."""
28+ pass
29+
30+
31+ class TargetError (CoordinatorError ):
32+ """Raised when target execution fails."""
33+ pass
34+
35+
36+ def _validate_arguments (args : List [str ]) -> tuple [int , str , List [str ]]:
37+ """
38+ Validate and parse command line arguments.
39+
40+ Args:
41+ args: Command line arguments including script name
42+
43+ Returns:
44+ Tuple of (sync_port, working_directory, target_args)
45+
46+ Raises:
47+ ArgumentError: If arguments are invalid
48+ """
49+ if len (args ) < 4 :
50+ raise ArgumentError (
51+ "Insufficient arguments. Expected: <sync_port> <cwd> <target> [args...]"
52+ )
53+
54+ try :
55+ sync_port = int (args [1 ])
56+ if not (1 <= sync_port <= 65535 ):
57+ raise ValueError ("Port out of range" )
58+ except ValueError as e :
59+ raise ArgumentError (f"Invalid sync port '{ args [1 ]} ': { e } " ) from e
60+
61+ cwd = args [2 ]
62+ if not os .path .isdir (cwd ):
63+ raise ArgumentError (f"Working directory does not exist: { cwd } " )
64+
65+ target_args = args [3 :]
66+ if not target_args :
67+ raise ArgumentError ("No target specified" )
68+
69+ return sync_port , cwd , target_args
70+
71+
72+ def _signal_readiness (sync_port : int ) -> None :
73+ """
74+ Signal readiness to the profiler via TCP socket.
75+
76+ Args:
77+ sync_port: Port number where profiler is listening
78+
79+ Raises:
80+ SyncError: If unable to signal readiness
81+ """
82+ max_retries = 3
83+ retry_delay = 0.1
84+
85+ for attempt in range (max_retries ):
86+ try :
87+ sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
88+ sock .settimeout (2.0 ) # 2 second timeout
89+ try :
90+ sock .connect (("127.0.0.1" , sync_port ))
91+ sock .send (b"ready" )
92+ return
93+ finally :
94+ sock .close ()
95+ except (socket .error , OSError ) as e :
96+ if attempt == max_retries - 1 :
97+ # On final attempt, raise the error
98+ raise SyncError (f"Failed to signal readiness after { max_retries } attempts: { e } " ) from e
99+ # Wait before retry
100+ time .sleep (retry_delay * (2 ** attempt )) # Exponential backoff
101+
102+
103+ def _setup_environment (cwd : str ) -> None :
104+ """
105+ Set up the execution environment.
106+
107+ Args:
108+ cwd: Working directory to change to
109+
110+ Raises:
111+ TargetError: If unable to set up environment
112+ """
113+ try :
114+ os .chdir (cwd )
115+ except OSError as e :
116+ raise TargetError (f"Failed to change to directory { cwd } : { e } " ) from e
117+
118+ # Add current directory to sys.path if not present (for module imports)
119+ if cwd not in sys .path :
120+ sys .path .insert (0 , cwd )
121+
122+
123+ def _execute_module (module_name : str , module_args : List [str ]) -> None :
124+ """
125+ Execute a Python module.
126+
127+ Args:
128+ module_name: Name of the module to execute
129+ module_args: Arguments to pass to the module
130+
131+ Raises:
132+ TargetError: If module execution fails
133+ """
134+ # Replace sys.argv to match original module call
135+ sys .argv = ["-m" , module_name ] + module_args
136+
137+ try :
138+ runpy .run_module (module_name , run_name = "__main__" , alter_sys = True )
139+ except ImportError as e :
140+ raise TargetError (f"Module '{ module_name } ' not found: { e } " ) from e
141+ except SystemExit :
142+ # SystemExit is normal for modules
143+ pass
144+ except Exception as e :
145+ raise TargetError (f"Error executing module '{ module_name } ': { e } " ) from e
146+
147+
148+ def _execute_script (script_path : str , script_args : List [str ], cwd : str ) -> None :
149+ """
150+ Execute a Python script.
151+
152+ Args:
153+ script_path: Path to the script to execute
154+ script_args: Arguments to pass to the script
155+ cwd: Current working directory for path resolution
156+
157+ Raises:
158+ TargetError: If script execution fails
159+ """
160+ # Make script path absolute if it isn't already
161+ if not os .path .isabs (script_path ):
162+ script_path = os .path .join (cwd , script_path )
163+
164+ if not os .path .isfile (script_path ):
165+ raise TargetError (f"Script not found: { script_path } " )
166+
167+ # Replace sys.argv to match original script call
168+ sys .argv = [script_path ] + script_args
169+
170+ try :
171+ with open (script_path , 'rb' ) as f :
172+ source_code = f .read ()
173+
174+ # Compile and execute the script
175+ code = compile (source_code , script_path , 'exec' )
176+ exec (code , {'__name__' : '__main__' , '__file__' : script_path })
177+ except FileNotFoundError as e :
178+ raise TargetError (f"Script file not found: { script_path } " ) from e
179+ except PermissionError as e :
180+ raise TargetError (f"Permission denied reading script: { script_path } " ) from e
181+ except SyntaxError as e :
182+ raise TargetError (f"Syntax error in script { script_path } : { e } " ) from e
183+ except SystemExit :
184+ # SystemExit is normal for scripts
185+ pass
186+ except Exception as e :
187+ raise TargetError (f"Error executing script '{ script_path } ': { e } " ) from e
188+
189+
190+ def main () -> NoReturn :
191+ """
192+ Main coordinator function.
193+
194+ This function coordinates the startup of a target Python process
195+ with the sample profiler by signaling when the process is ready
196+ to be profiled.
197+ """
198+ try :
199+ # Parse and validate arguments
200+ sync_port , cwd , target_args = _validate_arguments (sys .argv )
201+
202+ # Set up execution environment
203+ _setup_environment (cwd )
204+
205+ # Signal readiness to profiler
206+ _signal_readiness (sync_port )
207+
208+ # Execute the target
209+ if target_args [0 ] == "-m" :
210+ # Module execution
211+ if len (target_args ) < 2 :
212+ raise ArgumentError ("Module name required after -m" )
213+
214+ module_name = target_args [1 ]
215+ module_args = target_args [2 :]
216+ _execute_module (module_name , module_args )
217+ else :
218+ # Script execution
219+ script_path = target_args [0 ]
220+ script_args = target_args [1 :]
221+ _execute_script (script_path , script_args , cwd )
222+
223+ except CoordinatorError as e :
224+ print (f"Profiler coordinator error: { e } " , file = sys .stderr )
225+ sys .exit (1 )
226+ except KeyboardInterrupt :
227+ print ("Interrupted" , file = sys .stderr )
228+ sys .exit (1 )
229+ except Exception as e :
230+ print (f"Unexpected error in profiler coordinator: { e } " , file = sys .stderr )
231+ sys .exit (1 )
232+
233+ # Normal exit
234+ sys .exit (0 )
235+
236+
237+ if __name__ == "__main__" :
238+ main ()
0 commit comments