mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	
		
			
	
	
		
			244 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			244 lines
		
	
	
	
		
			6.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								"""
							 | 
						||
| 
								 | 
							
								Internal synchronization coordinator for the sample profiler.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This module is used internally by the sample profiler to coordinate
							 | 
						||
| 
								 | 
							
								the startup of target processes. It should not be called directly by users.
							 | 
						||
| 
								 | 
							
								"""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import sys
							 | 
						||
| 
								 | 
							
								import socket
							 | 
						||
| 
								 | 
							
								import runpy
							 | 
						||
| 
								 | 
							
								import time
							 | 
						||
| 
								 | 
							
								from typing import List, NoReturn
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class CoordinatorError(Exception):
							 | 
						||
| 
								 | 
							
								    """Base exception for coordinator errors."""
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class ArgumentError(CoordinatorError):
							 | 
						||
| 
								 | 
							
								    """Raised when invalid arguments are provided."""
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class SyncError(CoordinatorError):
							 | 
						||
| 
								 | 
							
								    """Raised when synchronization with profiler fails."""
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TargetError(CoordinatorError):
							 | 
						||
| 
								 | 
							
								    """Raised when target execution fails."""
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _validate_arguments(args: List[str]) -> tuple[int, str, List[str]]:
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Validate and parse command line arguments.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Args:
							 | 
						||
| 
								 | 
							
								        args: Command line arguments including script name
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Returns:
							 | 
						||
| 
								 | 
							
								        Tuple of (sync_port, working_directory, target_args)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Raises:
							 | 
						||
| 
								 | 
							
								        ArgumentError: If arguments are invalid
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if len(args) < 4:
							 | 
						||
| 
								 | 
							
								        raise ArgumentError(
							 | 
						||
| 
								 | 
							
								            "Insufficient arguments. Expected: <sync_port> <cwd> <target> [args...]"
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        sync_port = int(args[1])
							 | 
						||
| 
								 | 
							
								        if not (1 <= sync_port <= 65535):
							 | 
						||
| 
								 | 
							
								            raise ValueError("Port out of range")
							 | 
						||
| 
								 | 
							
								    except ValueError as e:
							 | 
						||
| 
								 | 
							
								        raise ArgumentError(f"Invalid sync port '{args[1]}': {e}") from e
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    cwd = args[2]
							 | 
						||
| 
								 | 
							
								    if not os.path.isdir(cwd):
							 | 
						||
| 
								 | 
							
								        raise ArgumentError(f"Working directory does not exist: {cwd}")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    target_args = args[3:]
							 | 
						||
| 
								 | 
							
								    if not target_args:
							 | 
						||
| 
								 | 
							
								        raise ArgumentError("No target specified")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return sync_port, cwd, target_args
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Constants for socket communication
							 | 
						||
| 
								 | 
							
								_MAX_RETRIES = 3
							 | 
						||
| 
								 | 
							
								_INITIAL_RETRY_DELAY = 0.1
							 | 
						||
| 
								 | 
							
								_SOCKET_TIMEOUT = 2.0
							 | 
						||
| 
								 | 
							
								_READY_MESSAGE = b"ready"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _signal_readiness(sync_port: int) -> None:
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Signal readiness to the profiler via TCP socket.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Args:
							 | 
						||
| 
								 | 
							
								        sync_port: Port number where profiler is listening
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Raises:
							 | 
						||
| 
								 | 
							
								        SyncError: If unable to signal readiness
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    last_error = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for attempt in range(_MAX_RETRIES):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            # Use context manager for automatic cleanup
							 | 
						||
| 
								 | 
							
								            with socket.create_connection(("127.0.0.1", sync_port), timeout=_SOCKET_TIMEOUT) as sock:
							 | 
						||
| 
								 | 
							
								                sock.send(_READY_MESSAGE)
							 | 
						||
| 
								 | 
							
								                return
							 | 
						||
| 
								 | 
							
								        except (socket.error, OSError) as e:
							 | 
						||
| 
								 | 
							
								            last_error = e
							 | 
						||
| 
								 | 
							
								            if attempt < _MAX_RETRIES - 1:
							 | 
						||
| 
								 | 
							
								                # Exponential backoff before retry
							 | 
						||
| 
								 | 
							
								                time.sleep(_INITIAL_RETRY_DELAY * (2 ** attempt))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # If we get here, all retries failed
							 | 
						||
| 
								 | 
							
								    raise SyncError(f"Failed to signal readiness after {_MAX_RETRIES} attempts: {last_error}") from last_error
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _setup_environment(cwd: str) -> None:
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Set up the execution environment.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Args:
							 | 
						||
| 
								 | 
							
								        cwd: Working directory to change to
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Raises:
							 | 
						||
| 
								 | 
							
								        TargetError: If unable to set up environment
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        os.chdir(cwd)
							 | 
						||
| 
								 | 
							
								    except OSError as e:
							 | 
						||
| 
								 | 
							
								        raise TargetError(f"Failed to change to directory {cwd}: {e}") from e
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Add current directory to sys.path if not present (for module imports)
							 | 
						||
| 
								 | 
							
								    if cwd not in sys.path:
							 | 
						||
| 
								 | 
							
								        sys.path.insert(0, cwd)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _execute_module(module_name: str, module_args: List[str]) -> None:
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Execute a Python module.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Args:
							 | 
						||
| 
								 | 
							
								        module_name: Name of the module to execute
							 | 
						||
| 
								 | 
							
								        module_args: Arguments to pass to the module
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Raises:
							 | 
						||
| 
								 | 
							
								        TargetError: If module execution fails
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # Replace sys.argv to match how Python normally runs modules
							 | 
						||
| 
								 | 
							
								    # When running 'python -m module args', sys.argv is ["__main__.py", "args"]
							 | 
						||
| 
								 | 
							
								    sys.argv = [f"__main__.py"] + module_args
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        runpy.run_module(module_name, run_name="__main__", alter_sys=True)
							 | 
						||
| 
								 | 
							
								    except ImportError as e:
							 | 
						||
| 
								 | 
							
								        raise TargetError(f"Module '{module_name}' not found: {e}") from e
							 | 
						||
| 
								 | 
							
								    except SystemExit:
							 | 
						||
| 
								 | 
							
								        # SystemExit is normal for modules
							 | 
						||
| 
								 | 
							
								        pass
							 | 
						||
| 
								 | 
							
								    except Exception as e:
							 | 
						||
| 
								 | 
							
								        raise TargetError(f"Error executing module '{module_name}': {e}") from e
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _execute_script(script_path: str, script_args: List[str], cwd: str) -> None:
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Execute a Python script.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Args:
							 | 
						||
| 
								 | 
							
								        script_path: Path to the script to execute
							 | 
						||
| 
								 | 
							
								        script_args: Arguments to pass to the script
							 | 
						||
| 
								 | 
							
								        cwd: Current working directory for path resolution
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Raises:
							 | 
						||
| 
								 | 
							
								        TargetError: If script execution fails
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # Make script path absolute if it isn't already
							 | 
						||
| 
								 | 
							
								    if not os.path.isabs(script_path):
							 | 
						||
| 
								 | 
							
								        script_path = os.path.join(cwd, script_path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if not os.path.isfile(script_path):
							 | 
						||
| 
								 | 
							
								        raise TargetError(f"Script not found: {script_path}")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Replace sys.argv to match original script call
							 | 
						||
| 
								 | 
							
								    sys.argv = [script_path] + script_args
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        with open(script_path, 'rb') as f:
							 | 
						||
| 
								 | 
							
								            source_code = f.read()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Compile and execute the script
							 | 
						||
| 
								 | 
							
								        code = compile(source_code, script_path, 'exec')
							 | 
						||
| 
								 | 
							
								        exec(code, {'__name__': '__main__', '__file__': script_path})
							 | 
						||
| 
								 | 
							
								    except FileNotFoundError as e:
							 | 
						||
| 
								 | 
							
								        raise TargetError(f"Script file not found: {script_path}") from e
							 | 
						||
| 
								 | 
							
								    except PermissionError as e:
							 | 
						||
| 
								 | 
							
								        raise TargetError(f"Permission denied reading script: {script_path}") from e
							 | 
						||
| 
								 | 
							
								    except SyntaxError as e:
							 | 
						||
| 
								 | 
							
								        raise TargetError(f"Syntax error in script {script_path}: {e}") from e
							 | 
						||
| 
								 | 
							
								    except SystemExit:
							 | 
						||
| 
								 | 
							
								        # SystemExit is normal for scripts
							 | 
						||
| 
								 | 
							
								        pass
							 | 
						||
| 
								 | 
							
								    except Exception as e:
							 | 
						||
| 
								 | 
							
								        raise TargetError(f"Error executing script '{script_path}': {e}") from e
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def main() -> NoReturn:
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Main coordinator function.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This function coordinates the startup of a target Python process
							 | 
						||
| 
								 | 
							
								    with the sample profiler by signaling when the process is ready
							 | 
						||
| 
								 | 
							
								    to be profiled.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        # Parse and validate arguments
							 | 
						||
| 
								 | 
							
								        sync_port, cwd, target_args = _validate_arguments(sys.argv)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Set up execution environment
							 | 
						||
| 
								 | 
							
								        _setup_environment(cwd)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Signal readiness to profiler
							 | 
						||
| 
								 | 
							
								        _signal_readiness(sync_port)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Execute the target
							 | 
						||
| 
								 | 
							
								        if target_args[0] == "-m":
							 | 
						||
| 
								 | 
							
								            # Module execution
							 | 
						||
| 
								 | 
							
								            if len(target_args) < 2:
							 | 
						||
| 
								 | 
							
								                raise ArgumentError("Module name required after -m")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            module_name = target_args[1]
							 | 
						||
| 
								 | 
							
								            module_args = target_args[2:]
							 | 
						||
| 
								 | 
							
								            _execute_module(module_name, module_args)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            # Script execution
							 | 
						||
| 
								 | 
							
								            script_path = target_args[0]
							 | 
						||
| 
								 | 
							
								            script_args = target_args[1:]
							 | 
						||
| 
								 | 
							
								            _execute_script(script_path, script_args, cwd)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    except CoordinatorError as e:
							 | 
						||
| 
								 | 
							
								        print(f"Profiler coordinator error: {e}", file=sys.stderr)
							 | 
						||
| 
								 | 
							
								        sys.exit(1)
							 | 
						||
| 
								 | 
							
								    except KeyboardInterrupt:
							 | 
						||
| 
								 | 
							
								        print("Interrupted", file=sys.stderr)
							 | 
						||
| 
								 | 
							
								        sys.exit(1)
							 | 
						||
| 
								 | 
							
								    except Exception as e:
							 | 
						||
| 
								 | 
							
								        print(f"Unexpected error in profiler coordinator: {e}", file=sys.stderr)
							 | 
						||
| 
								 | 
							
								        sys.exit(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Normal exit
							 | 
						||
| 
								 | 
							
								    sys.exit(0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if __name__ == "__main__":
							 | 
						||
| 
								 | 
							
								    main()
							 |