def spawn(process_name, cmdline, env, redirect_output): log.info( "Spawning debuggee process:\n\n" "Command line: {0!r}\n\n" "Environment variables: {1!r}\n\n", cmdline, env, ) close_fds = set() try: if redirect_output: # subprocess.PIPE behavior can vary substantially depending on Python version # and platform; using our own pipes keeps it simple, predictable, and fast. stdout_r, stdout_w = os.pipe() stderr_r, stderr_w = os.pipe() close_fds |= {stdout_r, stdout_w, stderr_r, stderr_w} kwargs = dict(stdout=stdout_w, stderr=stderr_w) else: kwargs = {} try: global process process = subprocess.Popen(cmdline, env=env, bufsize=0, **kwargs) except Exception as exc: raise messaging.MessageHandlingError( fmt("Couldn't spawn debuggee: {0}\n\nCommand line:{1!r}", exc, cmdline) ) log.info("Spawned {0}.", describe()) atexit.register(kill) launcher.channel.send_event( "process", { "startMethod": "launch", "isLocalProcess": True, "systemProcessId": process.pid, "name": process_name, "pointerSize": struct.calcsize(compat.force_str("P")) * 8, }, ) if redirect_output: for category, fd, tee in [ ("stdout", stdout_r, sys.stdout), ("stderr", stderr_r, sys.stderr), ]: output.CaptureOutput(describe(), category, fd, tee) close_fds.remove(fd) wait_thread = threading.Thread(target=wait_for_exit, name="wait_for_exit()") wait_thread.daemon = True wait_thread.start() finally: for fd in close_fds: try: os.close(fd) except Exception: log.swallow_exception()
def spawn(process_name, cmdline, env, redirect_output): log.info( "Spawning debuggee process:\n\n" "Command line: {0!r}\n\n" "Environment variables: {1!r}\n\n", cmdline, env, ) close_fds = set() try: if redirect_output: # subprocess.PIPE behavior can vary substantially depending on Python version # and platform; using our own pipes keeps it simple, predictable, and fast. stdout_r, stdout_w = os.pipe() stderr_r, stderr_w = os.pipe() close_fds |= {stdout_r, stdout_w, stderr_r, stderr_w} kwargs = dict(stdout=stdout_w, stderr=stderr_w) else: kwargs = {} if sys.platform != "win32": def preexec_fn(): try: # Start the debuggee in a new process group, so that the launcher can # kill the entire process tree later. os.setpgrp() # Make the new process group the foreground group in its session, so # that it can interact with the terminal. The debuggee will receive # SIGTTOU when tcsetpgrp() is called, and must ignore it. old_handler = signal.signal(signal.SIGTTOU, signal.SIG_IGN) try: tty = os.open("/dev/tty", os.O_RDWR) try: os.tcsetpgrp(tty, os.getpgrp()) finally: os.close(tty) finally: signal.signal(signal.SIGTTOU, old_handler) except Exception: # Not an error - /dev/tty doesn't work when there's no terminal. log.swallow_exception( "Failed to set up process group", level="info" ) kwargs.update(preexec_fn=preexec_fn) try: global process process = subprocess.Popen(cmdline, env=env, bufsize=0, **kwargs) except Exception as exc: raise messaging.MessageHandlingError( fmt("Couldn't spawn debuggee: {0}\n\nCommand line:{1!r}", exc, cmdline) ) log.info("Spawned {0}.", describe()) if sys.platform == "win32": # Assign the debuggee to a new job object, so that the launcher can kill # the entire process tree later. try: global job_handle job_handle = winapi.kernel32.CreateJobObjectA(None, None) job_info = winapi.JOBOBJECT_EXTENDED_LIMIT_INFORMATION() job_info_size = winapi.DWORD(ctypes.sizeof(job_info)) winapi.kernel32.QueryInformationJobObject( job_handle, winapi.JobObjectExtendedLimitInformation, ctypes.pointer(job_info), job_info_size, ctypes.pointer(job_info_size), ) # Setting this flag ensures that the job will be terminated by the OS once the # launcher exits, even if it doesn't terminate the job explicitly. job_info.BasicLimitInformation.LimitFlags |= ( winapi.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE ) winapi.kernel32.SetInformationJobObject( job_handle, winapi.JobObjectExtendedLimitInformation, ctypes.pointer(job_info), job_info_size, ) process_handle = winapi.kernel32.OpenProcess( winapi.PROCESS_TERMINATE | winapi.PROCESS_SET_QUOTA, False, process.pid, ) winapi.kernel32.AssignProcessToJobObject(job_handle, process_handle) except Exception: log.swallow_exception("Failed to set up job object", level="warning") atexit.register(kill) launcher.channel.send_event( "process", { "startMethod": "launch", "isLocalProcess": True, "systemProcessId": process.pid, "name": process_name, "pointerSize": struct.calcsize(compat.force_str("P")) * 8, }, ) if redirect_output: for category, fd, tee in [ ("stdout", stdout_r, sys.stdout), ("stderr", stderr_r, sys.stderr), ]: output.CaptureOutput(describe(), category, fd, tee) close_fds.remove(fd) wait_thread = threading.Thread(target=wait_for_exit, name="wait_for_exit()") wait_thread.daemon = True wait_thread.start() finally: for fd in close_fds: try: os.close(fd) except Exception: log.swallow_exception(level="warning")