def execute(self): binary = self.require_single_root_target() if isinstance(binary, PythonBinary): # We can't throw if binary isn't a PythonBinary, because perhaps we were called on a # jvm_binary, in which case we have to no-op and let jvm_run do its thing. # TODO(benjy): Use MutexTask to coordinate this. pex = self.create_pex(binary.pexinfo) args = [*self.get_passthru_args(), *self.get_options().args] env = self.prepare_pex_env() self.context.release_lock() cmdline = " ".join(pex.cmdline(args)) with self.context.new_workunit( name="run", cmd=cmdline, labels=[WorkUnitLabel.TOOL, WorkUnitLabel.RUN] ): po = pex.run(blocking=False, args=args, env=env) try: result = po.wait() if result != 0: msg = f"{cmdline} ... exited non-zero ({result})" raise TaskError(msg, exit_code=result) except KeyboardInterrupt: # The process may still have exited, even if we were interrupted. safe_kill(po.pid, signal.SIGINT) raise
def execute(self): binary = self.require_single_root_target() if isinstance(binary, PythonBinary): # We can't throw if binary isn't a PythonBinary, because perhaps we were called on a # jvm_binary, in which case we have to no-op and let jvm_run do its thing. # TODO(benjy): Use MutexTask to coordinate this. pex = self.create_pex(binary.pexinfo) args = [] for arg in self.get_options().args: args.extend(safe_shlex_split(arg)) args += self.get_passthru_args() env = os.environ.copy() env.update(self.ensure_interpreter_search_path_env()) self.context.release_lock() cmdline = ' '.join(pex.cmdline(args)) with self.context.new_workunit( name='run', cmd=cmdline, labels=[WorkUnitLabel.TOOL, WorkUnitLabel.RUN]): po = pex.run(blocking=False, args=args, env=env) try: result = po.wait() if result != 0: msg = '{cmdline} ... exited non-zero ({code})'.format( cmdline=cmdline, code=result) raise TaskError(msg, exit_code=result) except KeyboardInterrupt: # The process may still have exited, even if we were interrupted. safe_kill(po.pid, signal.SIGINT) raise
def maybe_send_signal(self, signum): """Send the signal `signum`. No error is raised if the pid is None. :param signum: The signal number to send to the remote process. """ if self.remote_pid is not None: safe_kill(self.remote_pid, signum)
def _process_session(self): """Process the outputs of the nailgun session. :raises: :class:`NailgunProtocol.ProcessStreamTimeout` if a timeout set from a signal handler with .set_exit_timeout() completes. :raises: :class:`Exception` if the session completes before the timeout, the `reason` argument to .set_exit_timeout() will be raised.""" try: for chunk_type, payload in self.iter_chunks( MaybeShutdownSocket(self._sock), return_bytes=True, timeout_object=self, ): # TODO(#6579): assert that we have at this point received all the chunk types in # ChunkType.REQUEST_TYPES, then require PID and PGRP (exactly once?), and then allow any of # ChunkType.EXECUTION_TYPES. if chunk_type == ChunkType.STDOUT: self._write_flush(self._stdout, payload) elif chunk_type == ChunkType.STDERR: self._write_flush(self._stderr, payload) elif chunk_type == ChunkType.EXIT: self._write_flush(self._stdout) self._write_flush(self._stderr) return int(payload) elif chunk_type == ChunkType.PID: self.remote_pid = int(payload) self.remote_process_cmdline = psutil.Process(self.remote_pid).cmdline() if self._remote_pid_callback: self._remote_pid_callback(self.remote_pid) elif chunk_type == ChunkType.PGRP: self.remote_pgrp = int(payload) if self._remote_pgrp_callback: self._remote_pgrp_callback(self.remote_pgrp) elif chunk_type == ChunkType.START_READING_INPUT: self._maybe_start_input_writer() else: raise self.ProtocolError('received unexpected chunk {} -> {}'.format(chunk_type, payload)) except NailgunProtocol.ProcessStreamTimeout as e: assert(self.remote_pid is not None) # NB: We overwrite the process title in the pantsd process, which causes it to have an # argv with lots of empty spaces for some reason. We filter those out and pretty-print the # rest here. filtered_remote_cmdline = safe_shlex_join( arg for arg in self.remote_process_cmdline if arg != '') logger.warning( "timed out when attempting to gracefully shut down the remote client executing \"{}\". " "sending SIGKILL to the remote client at pid: {}. message: {}" .format(filtered_remote_cmdline, self.remote_pid, e)) finally: # Bad chunk types received from the server can throw NailgunProtocol.ProtocolError in # NailgunProtocol.iter_chunks(). This ensures the NailgunStreamWriter is always stopped. self._maybe_stop_input_writer() # If an asynchronous error was set at any point (such as in a signal handler), we want to make # sure we clean up the remote process before exiting with error. if self._exit_reason: if self.remote_pgrp: safe_kill(self.remote_pgrp, signal.SIGKILL) if self.remote_pid: safe_kill(self.remote_pid, signal.SIGKILL) raise self._exit_reason
def execute(self): binary = self.require_single_root_target() if isinstance(binary, PythonBinary): # We can't throw if binary isn't a PythonBinary, because perhaps we were called on a # jvm_binary, in which case we have to no-op and let jvm_run do its thing. # TODO(benjy): Use MutexTask to coordinate this. pex = self.create_pex(binary.pexinfo) args = [] for arg in self.get_options().args: args.extend(safe_shlex_split(arg)) args += self.get_passthru_args() self.context.release_lock() cmdline = ' '.join(pex.cmdline(args)) with self.context.new_workunit(name='run', cmd=cmdline, labels=[WorkUnitLabel.TOOL, WorkUnitLabel.RUN]): po = pex.run(blocking=False, args=args, env=os.environ.copy()) try: result = po.wait() if result != 0: msg = '{cmdline} ... exited non-zero ({code})'.format(cmdline=cmdline, code=result) raise TaskError(msg, exit_code=result) except KeyboardInterrupt: # The process may still have exited, even if we were interrupted. safe_kill(po.pid, signal.SIGINT) raise
def maybe_send_signal(self, signum, include_pgrp=True): """Send the signal `signum` send if the PID and/or PGRP chunks have been received. No error is raised if the pid or pgrp are None or point to an already-dead process. """ remote_pid = self._maybe_last_pid() if remote_pid is not None: safe_kill(remote_pid, signum) if include_pgrp: remote_pgrp = self._maybe_last_pgrp() if remote_pgrp: safe_kill(remote_pgrp, signum)
def maybe_send_signal(self, signum, include_pgrp=True): """Send the signal `signum` send if the PID and/or PGRP chunks have been received. No error is raised if the pid or pgrp are None or point to an already-dead process. :param signum: The signal number to send to the remote process. :param include_pgrp: If True, it will try to kill the pgrp as well """ remote_pid = self._maybe_last_pid() if remote_pid is not None: safe_kill(remote_pid, signum) if include_pgrp: remote_pgrp = self._maybe_last_pgrp() if remote_pgrp: safe_kill(remote_pgrp, signum)