def _get_log_file_paths(self, workdir, pants_run):
    pid_specific_log_file = ExceptionSink.exceptions_log_path(for_pid=pants_run.pid, in_dir=workdir)
    self.assertTrue(os.path.isfile(pid_specific_log_file))

    shared_log_file = ExceptionSink.exceptions_log_path(in_dir=workdir)
    self.assertTrue(os.path.isfile(shared_log_file))

    self.assertNotEqual(pid_specific_log_file, shared_log_file)

    return (pid_specific_log_file, shared_log_file)
示例#2
0
def main():
  start_time = time.time()

  exiter = Exiter()
  ExceptionSink.reset_exiter(exiter)

  with maybe_profiled(os.environ.get('PANTSC_PROFILE')):
    try:
      PantsRunner(exiter, start_time=start_time).run()
    except KeyboardInterrupt as e:
      exiter.exit_and_fail('Interrupted by user:\n{}'.format(e))
  def execute(self):
    exit_msg = self._lifecycle_stubs.add_exiter_message
    if exit_msg:
      new_exiter = MessagingExiter(exit_msg)
      ExceptionSink.reset_exiter(new_exiter)

    output_file = self._lifecycle_stubs.new_interactive_stream_output_file
    if output_file:
      file_stream = open(output_file, 'wb')
      ExceptionSink.reset_interactive_output_stream(file_stream, output_file)

    raise Exception('erroneous!')
示例#4
0
  def set_start_time(self, start_time):
    # Launch RunTracker as early as possible (before .run() is called).
    self._run_tracker = RunTracker.global_instance()
    self._reporting = Reporting.global_instance()

    self._run_start_time = start_time
    self._reporting.initialize(self._run_tracker, self._options, start_time=self._run_start_time)

    # Capture a repro of the 'before' state for this build, if needed.
    self._repro = Reproducer.global_instance().create_repro()
    if self._repro:
      self._repro.capture(self._run_tracker.run_info.get_as_dict())

    # The __call__ method of the Exiter allows for the prototype pattern.
    self._exiter = LocalExiter(self._run_tracker, self._repro, exiter=self._exiter)
    ExceptionSink.reset_exiter(self._exiter)
示例#5
0
  def exit(self, result=PANTS_SUCCEEDED_EXIT_CODE, msg=None, *args, **kwargs):
    # These strings are prepended to the existing exit message when calling the superclass .exit().
    additional_messages = []
    try:
      if not self._run_tracker.has_ended():
        if result == PANTS_SUCCEEDED_EXIT_CODE:
          outcome = WorkUnit.SUCCESS
        elif result == PANTS_FAILED_EXIT_CODE:
          outcome = WorkUnit.FAILURE
        else:
          run_tracker_msg = ("unrecognized exit code {} provided to {}.exit() -- "
                             "interpreting as a failure in the run tracker"
                             .format(result, type(self).__name__))
          # Log the unrecognized exit code to the fatal exception log.
          ExceptionSink.log_exception(run_tracker_msg)
          # Ensure the unrecognized exit code message is also logged to the terminal.
          additional_messages.push(run_tracker_msg)
          outcome = WorkUnit.FAILURE

        self._run_tracker.set_root_outcome(outcome)
        run_tracker_result = self._run_tracker.end()
        assert result == run_tracker_result, "pants exit code not correctly recorded by run tracker"
    except ValueError as e:
      # If we have been interrupted by a signal, calling .end() sometimes writes to a closed file,
      # so we just log that fact here and keep going.
      exception_string = str(e)
      ExceptionSink.log_exception(exception_string)
      additional_messages.push(exception_string)
    finally:
      if self._repro:
        # TODO: Have Repro capture the 'after' state (as a diff) as well? (in reference to the below
        # 'before' state comment)
        # NB: this writes to the logger, which is expected to still be alive if we are exiting from
        # a signal.
        self._repro.log_location_of_repro_file()

    if additional_messages:
      msg = '{}\n\n{}'.format('\n'.join(additional_messages),
                              msg or '')

    super(LocalExiter, self).exit(result=result, msg=msg, *args, **kwargs)
示例#6
0
  def execute(self, execution_request):
    """Invoke the engine for the given ExecutionRequest, returning Return and Throw states.

    :return: A tuple of (root, Return) tuples and (root, Throw) tuples.
    """
    start_time = time.time()
    roots = list(zip(execution_request.roots,
                     self._scheduler._run_and_return_roots(self._session, execution_request.native)))

    ExceptionSink.toggle_ignoring_sigint_v2_engine(False)

    self._maybe_visualize()

    logger.debug(
      'computed %s nodes in %f seconds. there are %s total nodes.',
      len(roots),
      time.time() - start_time,
      self._scheduler.graph_len()
    )

    returns = tuple((root, state) for root, state in roots if type(state) is Return)
    throws = tuple((root, state) for root, state in roots if type(state) is Throw)
    return returns, throws
示例#7
0
    def run(self, start_time: float) -> ExitCode:
        self._set_start_time(start_time)

        with maybe_profiled(self.profile_path):
            global_options = self.options.for_global_scope()
            streaming_handlers = global_options.streaming_workunits_handlers
            report_interval = global_options.streaming_workunits_report_interval
            callbacks = Subsystem.get_streaming_workunit_callbacks(
                streaming_handlers)
            streaming_reporter = StreamingWorkunitHandler(
                self.graph_session.scheduler_session,
                callbacks=callbacks,
                report_interval_seconds=report_interval,
            )

            if self.options.help_request:
                all_help_info = HelpInfoExtracter.get_all_help_info(
                    self.options,
                    self.union_membership,
                    self.graph_session.goal_consumed_subsystem_scopes,
                )
                help_printer = HelpPrinter(
                    bin_name=global_options.pants_bin_name,
                    help_request=self.options.help_request,
                    all_help_info=all_help_info,
                    use_color=global_options.colors,
                )
                return help_printer.print_help()

            with streaming_reporter.session():
                engine_result = PANTS_FAILED_EXIT_CODE
                try:
                    engine_result = self._run_v2()
                except Exception as e:
                    ExceptionSink.log_exception(e)
                run_tracker_result = self._finish_run(engine_result)
            return self._merge_exit_codes(engine_result, run_tracker_result)
示例#8
0
    def run_sync(self):
        """Synchronously run pantsd."""
        os.environ.pop("PYTHONPATH")

        global_bootstrap_options = self._bootstrap_options.for_global_scope()
        # Set the process name in ps output to 'pantsd' vs './pants compile src/etc:: -ldebug'.
        set_process_title(f"pantsd [{self._build_root}]")

        # Switch log output to the daemon's log stream, and empty `env` and `argv` to encourage all
        # further usage of those variables to happen via engine APIs and options.
        self._close_stdio(
            pants_log_path(PurePath(global_bootstrap_options.pants_workdir)))
        with initialize_stdio(global_bootstrap_options), argv_as(
                tuple()), hermetic_environment_as():
            # Install signal and panic handling.
            ExceptionSink.install(
                log_location=init_workdir(global_bootstrap_options),
                pantsd_instance=True)
            native_engine.maybe_set_panic_handler()

            self._initialize_metadata()

            # Check periodically whether the core is valid, and exit if it is not.
            while self._core.is_valid():
                time.sleep(self.JOIN_TIMEOUT_SECONDS)

            # We're exiting: purge our metadata to prevent new connections, then join the server
            # to avoid interrupting ongoing runs.
            self.purge_metadata(force=True)
            self._logger.info(
                "Waiting for ongoing runs to complete before exiting...")
            native_engine.nailgun_server_await_shutdown(self._server)
            # Then shutdown the PantsDaemonCore, which will shut down any live Scheduler.
            self._logger.info(
                "Waiting for Sessions to complete before exiting...")
            self._core.shutdown()
            self._logger.info("Exiting pantsd")
示例#9
0
    def run(self, start_time: float) -> None:
        self.scrub_pythonpath()

        # TODO could options-bootstrapper be parsed in the runners?
        options_bootstrapper = OptionsBootstrapper.create(env=self.env,
                                                          args=self.args)
        bootstrap_options = options_bootstrapper.bootstrap_options
        global_bootstrap_options = bootstrap_options.for_global_scope()

        # Initialize the workdir early enough to ensure that logging has a destination.
        workdir_src = init_workdir(global_bootstrap_options)
        ExceptionSink.reset_log_location(workdir_src)

        # We enable Rust logging here,
        # and everything before it will be routed through regular Python logging.
        self._enable_rust_logging(global_bootstrap_options)

        ExceptionSink.reset_should_print_backtrace_to_terminal(
            global_bootstrap_options.print_exception_stacktrace)

        if self._should_run_with_pantsd(global_bootstrap_options):
            try:
                RemotePantsRunner(self._exiter, self.args, self.env,
                                  options_bootstrapper).run()
                return
            except RemotePantsRunner.Fallback as e:
                logger.warning(
                    "Client exception: {!r}, falling back to non-daemon mode".
                    format(e))

        # N.B. Inlining this import speeds up the python thin client run by about 100ms.
        from pants.bin.local_pants_runner import LocalPantsRunner

        runner = LocalPantsRunner.create(
            env=self.env, options_bootstrapper=options_bootstrapper)
        runner.set_start_time(start_time)
        runner.run()
示例#10
0
    def post_fork_child(self):
        """Post-fork child process callback executed via ProcessManager.daemonize()."""
        # Set the Exiter exception hook post-fork so as not to affect the pantsd processes exception
        # hook with socket-specific behavior. Note that this intentionally points the faulthandler
        # trace stream to sys.stderr, which at this point is still a _LoggerStream object writing to
        # the `pantsd.log`. This ensures that in the event of e.g. a hung but detached pantsd-runner
        # process that the stacktrace output lands deterministically in a known place vs to a stray
        # terminal window.
        # TODO: test the above!
        ExceptionSink.reset_exiter(self._exiter)

        ExceptionSink.reset_interactive_output_stream(
            sys.stderr.buffer if PY3 else sys.stderr)

        # Ensure anything referencing sys.argv inherits the Pailgun'd args.
        sys.argv = self._args

        # Set context in the process title.
        set_process_title('pantsd-runner [{}]'.format(' '.join(self._args)))

        # Broadcast our process group ID (in PID form - i.e. negated) to the remote client so
        # they can send signals (e.g. SIGINT) to all processes in the runners process group.
        NailgunProtocol.send_pid(self._socket, os.getpid())
        NailgunProtocol.send_pgrp(self._socket, os.getpgrp() * -1)

        # Stop the services that were paused pre-fork.
        for service in self._services.services:
            service.terminate()

        # Invoke a Pants run with stdio redirected and a proxied environment.
        with self.nailgunned_stdio(self._socket, self._env) as finalizer,\
             hermetic_environment_as(**self._env):
            try:
                # Setup the Exiter's finalizer.
                self._exiter.set_finalizer(finalizer)

                # Clean global state.
                clean_global_runtime_state(reset_subsystem=True)

                # Re-raise any deferred exceptions, if present.
                self._raise_deferred_exc()

                # Otherwise, conduct a normal run.
                runner = LocalPantsRunner.create(self._exiter, self._args,
                                                 self._env, self._target_roots,
                                                 self._graph_helper,
                                                 self._options_bootstrapper)
                runner.set_start_time(
                    self._maybe_get_client_start_time_from_env(self._env))
                runner.run()
            except KeyboardInterrupt:
                self._exiter.exit_and_fail('Interrupted by user.\n')
            except Exception:
                ExceptionSink._log_unhandled_exception_and_exit()
            else:
                self._exiter.exit(0)
示例#11
0
  def run(self):
    # Ensure anything referencing sys.argv inherits the Pailgun'd args.
    sys.argv = self._args

    # Broadcast our process group ID (in PID form - i.e. negated) to the remote client so
    # they can send signals (e.g. SIGINT) to all processes in the runners process group.
    with self._maybe_shutdown_socket.lock:
      NailgunProtocol.send_pid(self._maybe_shutdown_socket.socket, os.getpid())
      NailgunProtocol.send_pgrp(self._maybe_shutdown_socket.socket, os.getpgrp() * -1)

    # Invoke a Pants run with stdio redirected and a proxied environment.
    with self.nailgunned_stdio(self._maybe_shutdown_socket, self._env) as finalizer, \
      hermetic_environment_as(**self._env), \
      encapsulated_global_logger():
      try:
        # Raise any exceptions we may have found when precomputing products.
        # NB: We raise it here as opposed to earlier because we have setup logging and stdio.
        if self._exception is not None:
          raise self._exception

        # Clean global state.
        clean_global_runtime_state(reset_subsystem=True)

        # Setup the Exiter's finalizer.
        self._exiter.set_finalizer(finalizer)

        # Otherwise, conduct a normal run.
        runner = LocalPantsRunner.create(
          PantsRunFailCheckerExiter(),
          self._args,
          self._env,
          self._target_roots,
          self._graph_helper,
          self._options_bootstrapper,
        )
        runner.set_start_time(self._maybe_get_client_start_time_from_env(self._env))

        runner.run()
      except KeyboardInterrupt:
        self._exiter.exit_and_fail('Interrupted by user.\n')
      except _PantsRunFinishedWithFailureException as e:
        ExceptionSink.log_exception(
          'Pants run failed with exception: {}; exiting'.format(e))
        self._exiter.exit(e.exit_code)
      except _PantsProductPrecomputeFailed as e:
        ExceptionSink.log_exception(repr(e))
        self._exiter.exit(e.exit_code)
      except Exception as e:
        # TODO: We override sys.excepthook above when we call ExceptionSink.set_exiter(). That
        # excepthook catches `SignalHandledNonLocalExit`s from signal handlers, which isn't
        # happening here, so something is probably overriding the excepthook. By catching Exception
        # and calling this method, we emulate the normal, expected sys.excepthook override.
        ExceptionSink._log_unhandled_exception_and_exit(exc=e)
      else:
        self._exiter.exit(PANTS_SUCCEEDED_EXIT_CODE)
示例#12
0
  def launch_repl(self, pex, **pex_run_kwargs):
    running_under_pantsd = self.context.options.for_global_scope().enable_pantsd

    if not running_under_pantsd:
      # While the repl subprocess is synchronously spawned, we rely on process group
      # signalling for a SIGINT to reach the repl subprocess directly - and want to
      # do nothing in response on the parent side.
      with ExceptionSink.trapped_signals(PythonReplSignalHandler()):
        self._run_repl(pex, **pex_run_kwargs)
    else:
      # In pantsd, this task will be running in a non-main thread,
      # so we can't override signal handling here.
      # That said, this means that under pantsd,
      # Ctrl-C will simply crash the repl (and the daemon).
      # TODO(#7623) Potential more robust (but more invasive) fix.
      self._run_repl(pex, **pex_run_kwargs)
    def launch_repl(self, pex, **pex_run_kwargs):
        running_under_pantsd = self.context.options.for_global_scope(
        ).enable_pantsd

        if not running_under_pantsd:
            # While the repl subprocess is synchronously spawned, we rely on process group
            # signalling for a SIGINT to reach the repl subprocess directly - and want to
            # do nothing in response on the parent side.
            with ExceptionSink.trapped_signals(PythonReplSignalHandler()):
                self._run_repl(pex, **pex_run_kwargs)
        else:
            # In pantsd, this task will be running in a non-main thread,
            # so we can't override signal handling here.
            # That said, this means that under pantsd,
            # Ctrl-C will simply crash the repl (and the daemon).
            # TODO(#7623) Potential more robust (but more invasive) fix.
            self._run_repl(pex, **pex_run_kwargs)
示例#14
0
    def run_sync(self):
        """Synchronously run pantsd."""
        # Switch log output to the daemon's log stream from here forward.
        self._close_stdio()
        with self._pantsd_logging() as (log_stream, log_filename):

            # Register an exiter using os._exit to ensure we only close stdio streams once.
            ExceptionSink.reset_exiter(Exiter(exiter=os._exit))

            # We don't have any stdio streams to log to anymore, so we log to a file.
            # We don't override the faulthandler destination because the stream we get will proxy things
            # via the rust logging code, and faulthandler needs to be writing directly to a real file
            # descriptor. When pantsd logging was originally initialised, we already set up faulthandler
            # to log to the correct file descriptor, so don't override it.
            #
            # We can get tracebacks of the pantsd process by tailing the pantsd log and sending it
            # SIGUSR2.
            ExceptionSink.reset_interactive_output_stream(
                log_stream,
                override_faulthandler_destination=False,
            )

            # Reset the log location and the backtrace preference from the global bootstrap options.
            global_bootstrap_options = self._bootstrap_options.for_global_scope(
            )
            ExceptionSink.reset_should_print_backtrace_to_terminal(
                global_bootstrap_options.print_exception_stacktrace)
            ExceptionSink.reset_log_location(
                global_bootstrap_options.pants_workdir)

            self._native.set_panic_handler()

            # Set the process name in ps output to 'pantsd' vs './pants compile src/etc:: -ldebug'.
            set_process_title('pantsd [{}]'.format(self._build_root))

            # Write service socket information to .pids.
            self._write_named_sockets(self._services.port_map)

            # Enter the main service runner loop.
            self._setup_services(self._services)
            self._run_services(self._services)
示例#15
0
    def run_sync(self):
        """Synchronously run pantsd."""
        os.environ.pop("PYTHONPATH")

        # Switch log output to the daemon's log stream from here forward.
        self._close_stdio()
        with self._pantsd_logging() as log_stream:

            # We don't have any stdio streams to log to anymore, so we log to a file.
            # We don't override the faulthandler destination because the stream we get will proxy things
            # via the rust logging code, and faulthandler needs to be writing directly to a real file
            # descriptor. When pantsd logging was originally initialised, we already set up faulthandler
            # to log to the correct file descriptor, so don't override it.
            #
            # We can get tracebacks of the pantsd process by tailing the pantsd log and sending it
            # SIGUSR2.
            ExceptionSink.reset_interactive_output_stream(
                log_stream,
                override_faulthandler_destination=False,
            )

            # Reset the log location and the backtrace preference from the global bootstrap options.
            global_bootstrap_options = self._bootstrap_options.for_global_scope(
            )
            ExceptionSink.reset_should_print_backtrace_to_terminal(
                global_bootstrap_options.print_exception_stacktrace)
            ExceptionSink.reset_log_location(
                global_bootstrap_options.pants_workdir)

            self._native.set_panic_handler()

            # Set the process name in ps output to 'pantsd' vs './pants compile src/etc:: -ldebug'.
            set_process_title(f"pantsd [{self._build_root}]")

            # Write our pid and the server's port to .pids. Order matters a bit here, because
            # technically all that is necessary to connect is the port, and Services are lazily
            # initialized by the core when a connection is established. Our pid needs to be on
            # disk before that happens.
            self._initialize_pid()
            self._write_nailgun_port()

            # Check periodically whether the core is valid, and exit if it is not.
            while self._core.is_valid():
                time.sleep(self.JOIN_TIMEOUT_SECONDS)

            # We're exiting: join the server to avoid interrupting ongoing runs.
            self._logger.info(
                "waiting for ongoing runs to complete before exiting...")
            self._native.nailgun_server_await_shutdown(self._server)
            self._logger.info("exiting.")
示例#16
0
  def run_sync(self):
    """Synchronously run pantsd."""
    # Switch log output to the daemon's log stream from here forward.
    self._close_stdio()
    with self._pantsd_logging() as (log_stream, log_filename):

      # Register an exiter using os._exit to ensure we only close stdio streams once.
      ExceptionSink.reset_exiter(Exiter(exiter=os._exit))

      # We don't have any stdio streams to log to anymore, so we log to a file.
      # We don't override the faulthandler destination because the stream we get will proxy things
      # via the rust logging code, and faulthandler needs to be writing directly to a real file
      # descriptor. When pantsd logging was originally initialised, we already set up faulthandler
      # to log to the correct file descriptor, so don't override it.
      #
      # We can get tracebacks of the pantsd process by tailing the pantsd log and sending it
      # SIGUSR2.
      ExceptionSink.reset_interactive_output_stream(
        log_stream,
        override_faulthandler_destination=False,
      )

      # Reset the log location and the backtrace preference from the global bootstrap options.
      global_bootstrap_options = self._bootstrap_options.for_global_scope()
      ExceptionSink.reset_should_print_backtrace_to_terminal(
        global_bootstrap_options.print_exception_stacktrace)
      ExceptionSink.reset_log_location(global_bootstrap_options.pants_workdir)

      self._native.set_panic_handler()

      # Set the process name in ps output to 'pantsd' vs './pants compile src/etc:: -ldebug'.
      set_process_title('pantsd [{}]'.format(self._build_root))

      # Write service socket information to .pids.
      self._write_named_sockets(self._services.port_map)

      # Enter the main service runner loop.
      self._setup_services(self._services)
      self._run_services(self._services)
示例#17
0
  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]
    if nailgun_error.pid is not None:
      sources = [abs(nailgun_error.pid)] + sources

    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))
示例#18
0
  def test_backup_logging_on_fatal_error(self):
    sink = self._gen_sink_subclass()
    with self.captured_logging(level=logging.ERROR) as captured:
      with temporary_dir() as tmpdir:
        sink.reset_log_location(tmpdir)
        with unittest.mock.patch.object(sink, '_try_write_with_flush', autospec=sink) as mock_write:
          mock_write.side_effect = ExceptionSink.ExceptionSinkError('fake write failure')
          sink.log_exception('XXX')
    errors = list(captured.errors())
    self.assertEqual(2, len(errors))

    def format_log_rx(log_file_type):
      return '.*'.join(re.escape(s) for s in [
        "pants.base.exception_sink: Error logging the message 'XXX' to the {log_file_type} file "
        "handle for".format(log_file_type=log_file_type),
        "at pid {pid}".format(pid=os.getpid()),
        "\nfake write failure",
      ])

    self.assertRegex(str(errors[0]), format_log_rx('pid-specific'))
    self.assertRegex(str(errors[1]), format_log_rx('shared'))
示例#19
0
    def run(self):
        # Register our exiter at the beginning of the run() method so that any code in this process from
        # this point onwards will use that exiter in the case of a fatal error.
        ExceptionSink.reset_exiter(self._exiter)

        options_bootstrapper = OptionsBootstrapper.create(env=self._env,
                                                          args=self._args)
        bootstrap_options = options_bootstrapper.bootstrap_options
        global_bootstrap_options = bootstrap_options.for_global_scope()

        # We enable Rust logging here,
        # and everything before it will be routed through regular Python logging.
        self._enable_rust_logging(global_bootstrap_options)

        ExceptionSink.reset_should_print_backtrace_to_terminal(
            global_bootstrap_options.print_exception_stacktrace)
        ExceptionSink.reset_log_location(
            global_bootstrap_options.pants_workdir)

        for message_regexp in global_bootstrap_options.ignore_pants_warnings:
            warnings.filterwarnings(action='ignore', message=message_regexp)

        # TODO https://github.com/pantsbuild/pants/issues/7205
        if self._should_run_with_pantsd(global_bootstrap_options):
            try:
                return RemotePantsRunner(self._exiter, self._args, self._env,
                                         options_bootstrapper).run()
            except RemotePantsRunner.Fallback as e:
                logger.warn(
                    'caught client exception: {!r}, falling back to non-daemon mode'
                    .format(e))

        # N.B. Inlining this import speeds up the python thin client run by about 100ms.
        from pants.bin.local_pants_runner import LocalPantsRunner

        if self.will_terminate_pantsd():
            logger.debug("Pantsd terminating goal detected: {}".format(
                self._args))

        runner = LocalPantsRunner.create(
            self._exiter,
            self._args,
            self._env,
            options_bootstrapper=options_bootstrapper)
        runner.set_start_time(self._start_time)
        return runner.run()
示例#20
0
    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)
示例#21
0
def test_backup_logging_on_fatal_error(caplog):
    sink = _gen_sink_subclass()
    with temporary_dir() as tmpdir:
        sink.reset_log_location(tmpdir)
        with unittest.mock.patch.object(sink, "_try_write_with_flush", autospec=sink) as mock_write:
            mock_write.side_effect = ExceptionSink.ExceptionSinkError("fake write failure")
            sink._log_exception("XXX")

    errors = [record for record in caplog.records if record.levelname == "ERROR"]
    assert len(errors) == 2

    def assert_log(log_file_type: str, log):
        assert bool(
            re.search(
                fr"Error logging the message 'XXX' to the {log_file_type} file handle for .* at "
                fr"pid {os.getpid()}",
                log.msg,
            )
        )
        assert log.filename == "exception_sink.py"

    assert_log("pid-specific", errors[0])
    assert_log("shared", errors[1])
示例#22
0
    def run(self):
        self.scrub_pythonpath()

        options_bootstrapper = OptionsBootstrapper.create(env=self._env,
                                                          args=self._args)
        bootstrap_options = options_bootstrapper.bootstrap_options
        global_bootstrap_options = bootstrap_options.for_global_scope()

        # Initialize the workdir early enough to ensure that logging has a destination.
        workdir_src = init_workdir(global_bootstrap_options)
        ExceptionSink.reset_log_location(workdir_src)

        # We enable Rust logging here,
        # and everything before it will be routed through regular Python logging.
        self._enable_rust_logging(global_bootstrap_options)

        ExceptionSink.reset_should_print_backtrace_to_terminal(
            global_bootstrap_options.print_exception_stacktrace)
        ExceptionSink.reset_log_location(
            global_bootstrap_options.pants_workdir)

        # TODO https://github.com/pantsbuild/pants/issues/7205
        if self._should_run_with_pantsd(global_bootstrap_options):
            try:
                return RemotePantsRunner(self._exiter, self._args, self._env,
                                         options_bootstrapper).run()
            except RemotePantsRunner.Fallback as e:
                logger.warning(
                    "caught client exception: {!r}, falling back to non-daemon mode"
                    .format(e))

        # N.B. Inlining this import speeds up the python thin client run by about 100ms.
        from pants.bin.local_pants_runner import LocalPantsRunner

        if self.will_terminate_pantsd():
            logger.debug("Pantsd terminating goal detected: {}".format(
                self._args))

        runner = LocalPantsRunner.create(
            self._args, self._env, options_bootstrapper=options_bootstrapper)
        runner.set_start_time(self._start_time)
        return runner.run()
示例#23
0
  def run(self):
    # Register our exiter at the beginning of the run() method so that any code in this process from
    # this point onwards will use that exiter in the case of a fatal error.
    ExceptionSink.reset_exiter(self._exiter)

    options_bootstrapper = OptionsBootstrapper.create(env=self._env, args=self._args)
    bootstrap_options = options_bootstrapper.bootstrap_options
    global_bootstrap_options = bootstrap_options.for_global_scope()

    # We enable Rust logging here,
    # and everything before it will be routed through regular Python logging.
    self._enable_rust_logging(global_bootstrap_options)

    ExceptionSink.reset_should_print_backtrace_to_terminal(global_bootstrap_options.print_exception_stacktrace)
    ExceptionSink.reset_log_location(global_bootstrap_options.pants_workdir)

    for message_regexp in global_bootstrap_options.ignore_pants_warnings:
      warnings.filterwarnings(action='ignore', message=message_regexp)

    if global_bootstrap_options.enable_pantsd:
      try:
        return RemotePantsRunner(self._exiter, self._args, self._env, options_bootstrapper).run()
      except RemotePantsRunner.Fallback as e:
        logger.warn('caught client exception: {!r}, falling back to non-daemon mode'.format(e))

    # N.B. Inlining this import speeds up the python thin client run by about 100ms.
    from pants.bin.local_pants_runner import LocalPantsRunner

    runner = LocalPantsRunner.create(
        self._exiter,
        self._args,
        self._env,
        options_bootstrapper=options_bootstrapper
    )
    runner.set_start_time(self._start_time)
    return runner.run()
示例#24
0
def create_native_pantsd_file_log_handler(level, native, native_filename):
    fd = native.setup_pantsd_logger(native_filename, get_numeric_level(level))
    ExceptionSink.reset_interactive_output_stream(os.fdopen(os.dup(fd), "a"))
    return NativeHandler(level, native, native_filename=native_filename)
示例#25
0
  def post_fork_child(self):
    """Post-fork child process callback executed via ProcessManager.daemonize()."""
    # Set the Exiter exception hook post-fork so as not to affect the pantsd processes exception
    # hook with socket-specific behavior. Note that this intentionally points the faulthandler
    # trace stream to sys.stderr, which at this point is still a _LoggerStream object writing to
    # the `pantsd.log`. This ensures that in the event of e.g. a hung but detached pantsd-runner
    # process that the stacktrace output lands deterministically in a known place vs to a stray
    # terminal window.
    # TODO: test the above!
    ExceptionSink.reset_exiter(self._exiter)

    ExceptionSink.reset_interactive_output_stream(sys.stderr.buffer if PY3 else sys.stderr)
    ExceptionSink.reset_signal_handler(DaemonSignalHandler())

    # Ensure anything referencing sys.argv inherits the Pailgun'd args.
    sys.argv = self._args

    # Set context in the process title.
    set_process_title('pantsd-runner [{}]'.format(' '.join(self._args)))

    # Broadcast our process group ID (in PID form - i.e. negated) to the remote client so
    # they can send signals (e.g. SIGINT) to all processes in the runners process group.
    NailgunProtocol.send_pid(self._socket, os.getpid())
    NailgunProtocol.send_pgrp(self._socket, os.getpgrp() * -1)

    # Stop the services that were paused pre-fork.
    for service in self._services.services:
      service.terminate()

    # Invoke a Pants run with stdio redirected and a proxied environment.
    with self.nailgunned_stdio(self._socket, self._env) as finalizer,\
         hermetic_environment_as(**self._env):
      try:
        # Setup the Exiter's finalizer.
        self._exiter.set_finalizer(finalizer)

        # Clean global state.
        clean_global_runtime_state(reset_subsystem=True)

        # Otherwise, conduct a normal run.
        runner = LocalPantsRunner.create(
          self._exiter,
          self._args,
          self._env,
          self._target_roots,
          self._graph_helper,
          self._options_bootstrapper
        )
        runner.set_start_time(self._maybe_get_client_start_time_from_env(self._env))

        # Re-raise any deferred exceptions, if present.
        self._raise_deferred_exc()

        runner.run()
      except KeyboardInterrupt:
        self._exiter.exit_and_fail('Interrupted by user.\n')
      except _GracefulTerminationException as e:
        ExceptionSink.log_exception(
          'Encountered graceful termination exception {}; exiting'.format(e))
        self._exiter.exit(e.exit_code)
      except Exception:
        # TODO: We override sys.excepthook above when we call ExceptionSink.set_exiter(). That
        # excepthook catches `SignalHandledNonLocalExit`s from signal handlers, which isn't
        # happening here, so something is probably overriding the excepthook. By catching Exception
        # and calling this method, we emulate the normal, expected sys.excepthook override.
        ExceptionSink._log_unhandled_exception_and_exit()
      else:
        self._exiter.exit(PANTS_SUCCEEDED_EXIT_CODE)
示例#26
0
 def _log_exception(self, msg):
   ExceptionSink.log_exception(msg)
示例#27
0
def create_native_pantsd_file_log_handler(level, native, native_filename):
  fd = native.setup_pantsd_logger(native_filename, get_numeric_level(level))
  ExceptionSink.reset_interactive_output_stream(os.fdopen(os.dup(fd), 'a'))
  return NativeHandler(level, native, native_filename=native_filename)
示例#28
0
 def wrap_global_exiter(cls, run_tracker, repro):
   with ExceptionSink.exiter_as(lambda previous_exiter: cls(run_tracker, repro, previous_exiter)):
     yield
示例#29
0
 def run(self, request: InteractiveProcess) -> InteractiveProcessResult:
     ExceptionSink.toggle_ignoring_sigint_v2_engine(True)
     return self._scheduler.run_local_interactive_process(request)
示例#30
0
 def override_global_exiter(cls, maybe_shutdown_socket: MaybeShutdownSocket,
                            finalizer: Callable[[], None]) -> None:
     with ExceptionSink.exiter_as(lambda previous_exiter: cls(
             maybe_shutdown_socket, finalizer, previous_exiter)):
         yield
示例#31
0
async def run_lifecycle_stubs(opts: LifecycleStubsSubsystem) -> LifecycleStubsGoal:
    output_file = opts.options.new_interactive_stream_output_file
    if output_file:
        file_stream = open(output_file, "wb")
        ExceptionSink.reset_interactive_output_stream(file_stream, output_file)
    raise Exception("erroneous!")
示例#32
0
    def run(self, request: InteractiveProcess) -> InteractiveProcessResult:
        if request.forward_signals_to_process:
            with ExceptionSink.ignoring_sigint():
                return self._scheduler.run_local_interactive_process(request)

        return self._scheduler.run_local_interactive_process(request)
示例#33
0
def create_native_pantsd_file_log_handler(
        log_level: LogLevel, native: Native,
        native_filename: str) -> NativeHandler:
    fd = native.setup_pantsd_logger(native_filename, log_level.level)
    ExceptionSink.reset_interactive_output_stream(os.fdopen(os.dup(fd), "a"))
    return NativeHandler(log_level, native, native_filename=native_filename)
示例#34
0
    def run(self):
        # Ensure anything referencing sys.argv inherits the Pailgun'd args.
        sys.argv = self.args

        # Invoke a Pants run with stdio redirected and a proxied environment.
        with self.nailgunned_stdio(
                self.maybe_shutdown_socket,
                self.env) as finalizer, DaemonExiter.override_global_exiter(
                    self.maybe_shutdown_socket,
                    finalizer), hermetic_environment_as(**self.env):

            exit_code = PANTS_SUCCEEDED_EXIT_CODE
            try:
                # Clean global state.
                clean_global_runtime_state(reset_subsystem=True)

                options_bootstrapper = OptionsBootstrapper.create(
                    args=self.args, env=self.env)
                options, build_config = LocalPantsRunner.parse_options(
                    options_bootstrapper)

                global_options = options.for_global_scope()
                session = self.scheduler_service.prepare_graph(options)

                specs = SpecsCalculator.create(
                    options=options,
                    session=session.scheduler_session,
                    exclude_patterns=tuple(
                        global_options.exclude_target_regexp),
                    tags=tuple(global_options.tag) if global_options.tag else
                    (),
                )

                if options.help_request:
                    help_printer = HelpPrinter(
                        options=options,
                        union_membership=UnionMembership(
                            build_config.union_rules()),
                    )
                    exit_code = help_printer.print_help()
                else:
                    exit_code = self.scheduler_service.graph_run_v2(
                        session, specs, options, options_bootstrapper)

                # self.scheduler_service.graph_run_v2 will already run v2 or ambiguous goals. We should
                # only enter this code path if v1 is set.
                if global_options.v1:
                    with ExceptionSink.exiter_as_until_exception(
                            lambda _: PantsRunFailCheckerExiter()):
                        runner = LocalPantsRunner.create(
                            self.env, options_bootstrapper, specs, session)

                        env_start_time = self.env.pop(
                            "PANTSD_RUNTRACKER_CLIENT_START_TIME", None)
                        start_time = float(
                            env_start_time) if env_start_time else None
                        runner.set_start_time(start_time)
                        runner.run()

            except KeyboardInterrupt:
                self._exiter.exit_and_fail("Interrupted by user.\n")
            except _PantsRunFinishedWithFailureException as e:
                ExceptionSink.log_exception(
                    "Pants run failed with exception: {}; exiting".format(e))
                self._exiter.exit(e.exit_code)
            except Exception as e:
                # TODO: We override sys.excepthook above when we call ExceptionSink.set_exiter(). That
                # excepthook catches `SignalHandledNonLocalExit`s from signal handlers, which isn't
                # happening here, so something is probably overriding the excepthook. By catching Exception
                # and calling this method, we emulate the normal, expected sys.excepthook override.
                ExceptionSink._log_unhandled_exception_and_exit(exc=e)
            else:
                self._exiter.exit(exit_code)