Esempio n. 1
0
 def execute(self):
     try:
         pantsd_client = PantsDaemonClient(OptionsBootstrapper.create().bootstrap_options)
         with pantsd_client.lifecycle_lock:
             pantsd_client.terminate()
     except ProcessManager.NonResponsiveProcess as e:
         raise TaskError("failure while terminating pantsd: {}".format(e))
Esempio n. 2
0
 def __init__(
     self,
     args: List[str],
     env: Mapping[str, str],
     options_bootstrapper: OptionsBootstrapper,
     stdin=None,
     stdout=None,
     stderr=None,
 ) -> None:
     """
     :param args: The arguments (e.g. sys.argv) for this run.
     :param env: The environment (e.g. os.environ) for this run.
     :param options_bootstrapper: The bootstrap options.
     :param file stdin: The stream representing stdin.
     :param file stdout: The stream representing stdout.
     :param file stderr: The stream representing stderr.
     """
     self._start_time = time.time()
     self._args = args
     self._env = env
     self._options_bootstrapper = options_bootstrapper
     self._bootstrap_options = options_bootstrapper.bootstrap_options
     self._client = PantsDaemonClient(self._bootstrap_options)
     self._stdin = stdin or sys.stdin
     self._stdout = stdout or sys.stdout.buffer
     self._stderr = stderr or sys.stderr.buffer
Esempio n. 3
0
def kill_daemon(pid_dir=None):
    args = ["./pants"]
    if pid_dir:
        args.append(f"--pants-subprocessdir={pid_dir}")
    pantsd_client = PantsDaemonClient(
        OptionsBootstrapper.create(env=os.environ, args=args, allow_pantsrc=False).bootstrap_options
    )
    with pantsd_client.lifecycle_lock:
        pantsd_client.terminate()
Esempio n. 4
0
 def __init__(
     self,
     args: List[str],
     env: Mapping[str, str],
     options_bootstrapper: OptionsBootstrapper,
 ) -> None:
     """
     :param args: The arguments (e.g. sys.argv) for this run.
     :param env: The environment (e.g. os.environ) for this run.
     :param options_bootstrapper: The bootstrap options.
     """
     self._args = args
     self._env = env
     self._options_bootstrapper = options_bootstrapper
     self._bootstrap_options = options_bootstrapper.bootstrap_options
     self._client = PantsDaemonClient(self._bootstrap_options)
Esempio n. 5
0
class RemotePantsRunner:
    """A thin client variant of PantsRunner."""
    class Fallback(Exception):
        """Raised when fallback to an alternate execution mode is requested."""

    class Terminated(Exception):
        """Raised when an active run is terminated mid-flight."""

    def __init__(
        self,
        args: List[str],
        env: Mapping[str, str],
        options_bootstrapper: OptionsBootstrapper,
    ) -> None:
        """
        :param args: The arguments (e.g. sys.argv) for this run.
        :param env: The environment (e.g. os.environ) for this run.
        :param options_bootstrapper: The bootstrap options.
        """
        self._start_time = time.time()
        self._args = args
        self._env = env
        self._options_bootstrapper = options_bootstrapper
        self._bootstrap_options = options_bootstrapper.bootstrap_options
        self._client = PantsDaemonClient(self._bootstrap_options)

    def run(self) -> ExitCode:
        """Starts up a pantsd instance if one is not already running, then connects to it via
        nailgun."""

        pantsd_handle = self._client.maybe_launch()
        logger.debug(f"Connecting to pantsd on port {pantsd_handle.port}")

        return self._connect_and_execute(pantsd_handle)

    def _connect_and_execute(
            self, pantsd_handle: PantsDaemonClient.Handle) -> ExitCode:
        global_options = self._bootstrap_options.for_global_scope()
        executor = GlobalOptions.create_py_executor(global_options)

        # Merge the nailgun TTY capability environment variables with the passed environment dict.
        ng_env = NailgunProtocol.ttynames_to_env(sys.stdin, sys.stdout,
                                                 sys.stderr)
        modified_env = {
            **self._env,
            **ng_env,
            "PANTSD_RUNTRACKER_CLIENT_START_TIME":
            str(self._start_time),
            "PANTSD_REQUEST_TIMEOUT_LIMIT":
            str(global_options.pantsd_timeout_when_multiple_invocations),
        }

        command = self._args[0]
        args = self._args[1:]

        retries = 3
        attempt = 1
        while True:
            port = pantsd_handle.port
            logger.debug(
                f"Connecting to pantsd on port {port} attempt {attempt}/{retries}"
            )

            # We preserve TTY settings since the server might write directly to the TTY, and we'd like
            # to clean up any side effects before exiting.
            #
            # We ignore keyboard interrupts because the nailgun client will handle them.
            with STTYSettings.preserved(), interrupts_ignored():
                try:
                    return native_engine.nailgun_client_create(
                        executor, port).execute(command, args, modified_env)

                # NailgunConnectionException represents a failure connecting to pantsd, so we retry
                # up to the retry limit.
                except NailgunConnectionException as e:
                    if attempt > retries:
                        raise self.Fallback(e)

                    # Wait one second before retrying
                    logger.warning(
                        f"Pantsd was unresponsive on port {port}, retrying.")
                    time.sleep(1)

                    # One possible cause of the daemon being non-responsive during an attempt might be if a
                    # another lifecycle operation is happening concurrently (incl teardown). To account for
                    # this, we won't begin attempting restarts until at least 1 attempt has passed.
                    if attempt > 1:
                        pantsd_handle = self._client.restart()

                    attempt += 1
Esempio n. 6
0
class RemotePantsRunner:
    """A thin client variant of PantsRunner."""
    class Fallback(Exception):
        """Raised when fallback to an alternate execution mode is requested."""

    class Terminated(Exception):
        """Raised when an active run is terminated mid-flight."""

    RECOVERABLE_EXCEPTIONS = (
        NailgunClient.NailgunConnectionError,
        NailgunClient.NailgunExecutionError,
    )

    def __init__(
        self,
        args: List[str],
        env: Mapping[str, str],
        options_bootstrapper: OptionsBootstrapper,
    ) -> None:
        """
        :param args: The arguments (e.g. sys.argv) for this run.
        :param env: The environment (e.g. os.environ) for this run.
        :param options_bootstrapper: The bootstrap options.
        """
        self._start_time = time.time()
        self._args = args
        self._env = env
        self._options_bootstrapper = options_bootstrapper
        self._bootstrap_options = options_bootstrapper.bootstrap_options
        self._client = PantsDaemonClient(self._bootstrap_options)

    @staticmethod
    def _backoff(attempt):
        """Minimal backoff strategy for daemon restarts."""
        time.sleep(attempt + (attempt - 1))

    def run(self) -> ExitCode:
        """Runs pants remotely with retry and recovery for nascent executions."""

        pantsd_handle = self._client.maybe_launch()
        retries = 3

        attempt = 1
        while True:
            logger.debug(
                "connecting to pantsd on port {} (attempt {}/{})".format(
                    pantsd_handle.port, attempt, retries))
            try:
                return self._connect_and_execute(pantsd_handle)
            except self.RECOVERABLE_EXCEPTIONS as e:
                if attempt > retries:
                    raise self.Fallback(e)

                self._backoff(attempt)
                logger.warning(
                    "pantsd was unresponsive on port {}, retrying ({}/{})".
                    format(pantsd_handle.port, attempt, retries))

                # One possible cause of the daemon being non-responsive during an attempt might be if a
                # another lifecycle operation is happening concurrently (incl teardown). To account for
                # this, we won't begin attempting restarts until at least 1 second has passed (1 attempt).
                if attempt > 1:
                    pantsd_handle = self._client.restart()
                attempt += 1
            except NailgunClient.NailgunError as e:
                # Ensure a newline.
                logger.critical("")
                logger.critical("lost active connection to pantsd!")
                traceback = sys.exc_info()[2]
                raise self._extract_remote_exception(
                    pantsd_handle.pid, e).with_traceback(traceback)

    def _connect_and_execute(
            self, pantsd_handle: PantsDaemonClient.Handle) -> ExitCode:
        port = pantsd_handle.port
        pid = pantsd_handle.pid

        global_options = self._bootstrap_options.for_global_scope()

        # Merge the nailgun TTY capability environment variables with the passed environment dict.
        ng_env = NailgunProtocol.ttynames_to_env(sys.stdin, sys.stdout.buffer,
                                                 sys.stderr.buffer)
        modified_env = {
            **self._env,
            **ng_env,
            "PANTSD_RUNTRACKER_CLIENT_START_TIME":
            str(self._start_time),
            "PANTSD_REQUEST_TIMEOUT_LIMIT":
            str(global_options.pantsd_timeout_when_multiple_invocations),
        }

        # Instantiate a NailgunClient.
        client = NailgunClient(
            port=port,
            remote_pid=pid,
            ins=sys.stdin,
            out=sys.stdout.buffer,
            err=sys.stderr.buffer,
            exit_on_broken_pipe=True,
            metadata_base_dir=pantsd_handle.metadata_base_dir,
        )

        timeout = global_options.pantsd_pailgun_quit_timeout
        pantsd_signal_handler = PailgunClientSignalHandler(client,
                                                           pid=pid,
                                                           timeout=timeout)
        with ExceptionSink.trapped_signals(
                pantsd_signal_handler), STTYSettings.preserved():
            # Execute the command on the pailgun.
            return client.execute(self._args[0], self._args[1:], modified_env)

    def _extract_remote_exception(self, pantsd_pid, nailgun_error):
        """Given a NailgunError, returns a Terminated exception with additional info (where
        possible).

        This method will include the entire exception log for either the `pid` in the NailgunError,
        or failing that, the `pid` of the pantsd instance.
        """
        sources = [pantsd_pid]

        exception_text = None
        for source in sources:
            log_path = ExceptionSink.exceptions_log_path(for_pid=source)
            exception_text = maybe_read_file(log_path)
            if exception_text:
                break

        exception_suffix = ("\nRemote exception:\n{}".format(exception_text)
                            if exception_text else "")
        return self.Terminated(
            "abruptly lost active connection to pantsd runner: {!r}{}".format(
                nailgun_error, exception_suffix))