Run a command with socket-based synchronization and return the process.
(original_cmd, suppress_output=False)
| 248 | |
| 249 | |
| 250 | def _run_with_sync(original_cmd, suppress_output=False): |
| 251 | """Run a command with socket-based synchronization and return the process.""" |
| 252 | # Create a TCP socket for synchronization with better socket options |
| 253 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sync_sock: |
| 254 | # Set SO_REUSEADDR to avoid "Address already in use" errors |
| 255 | sync_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
| 256 | sync_sock.bind(("127.0.0.1", 0)) # Let OS choose a free port |
| 257 | sync_port = sync_sock.getsockname()[1] |
| 258 | sync_sock.listen(1) |
| 259 | sync_sock.settimeout(_SYNC_TIMEOUT_SEC) |
| 260 | |
| 261 | # Get current working directory to preserve it |
| 262 | cwd = os.getcwd() |
| 263 | |
| 264 | # Build command using the sync coordinator |
| 265 | target_args = original_cmd[1:] # Remove python executable |
| 266 | cmd = ( |
| 267 | sys.executable, |
| 268 | "-m", |
| 269 | "profiling.sampling._sync_coordinator", |
| 270 | str(sync_port), |
| 271 | cwd, |
| 272 | ) + tuple(target_args) |
| 273 | |
| 274 | # Start the process with coordinator |
| 275 | # When suppress_output=True (live mode), capture stderr so we can |
| 276 | # report errors if the process dies before signaling ready. |
| 277 | # When suppress_output=False (normal mode), let stderr inherit so |
| 278 | # script errors print to the terminal. |
| 279 | popen_kwargs = {} |
| 280 | if suppress_output: |
| 281 | popen_kwargs["stdin"] = subprocess.DEVNULL |
| 282 | popen_kwargs["stdout"] = subprocess.DEVNULL |
| 283 | popen_kwargs["stderr"] = subprocess.PIPE |
| 284 | |
| 285 | process = subprocess.Popen(cmd, **popen_kwargs) |
| 286 | |
| 287 | try: |
| 288 | _wait_for_ready_signal(sync_sock, process, _SYNC_TIMEOUT_SEC) |
| 289 | except socket.timeout: |
| 290 | # If we timeout, kill the process and raise an error |
| 291 | if process.poll() is None: |
| 292 | process.terminate() |
| 293 | try: |
| 294 | process.wait(timeout=_PROCESS_KILL_TIMEOUT_SEC) |
| 295 | except subprocess.TimeoutExpired: |
| 296 | process.kill() |
| 297 | process.wait() |
| 298 | raise RuntimeError( |
| 299 | "Process failed to signal readiness within timeout" |
| 300 | ) |
| 301 | |
| 302 | return process |
| 303 | |
| 304 | |
| 305 | _RATE_PATTERN = re.compile(r''' |
no test coverage detected
searching dependent graphs…