Exemple #1
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, DaemonExiter.override_global_exiter(
                    self._maybe_shutdown_socket,
                    finalizer), hermetic_environment_as(
                        **self._env), encapsulated_global_logger():
            try:
                # Clean global state.
                clean_global_runtime_state(reset_subsystem=True)

                options, _, options_bootstrapper = LocalPantsRunner.parse_options(
                    self._args, self._env)
                graph_helper, specs, exit_code = self._scheduler_service.prepare_v1_graph_run_v2(
                    options,
                    options_bootstrapper,
                )
                self.exit_code = exit_code

                # Otherwise, conduct a normal run.
                with ExceptionSink.exiter_as_until_exception(
                        lambda _: PantsRunFailCheckerExiter()):
                    runner = LocalPantsRunner.create(
                        self._args,
                        self._env,
                        specs,
                        graph_helper,
                        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 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(self.exit_code if self.
                                  exit_code else PANTS_SUCCEEDED_EXIT_CODE)
Exemple #2
0
 def test_hermetic_environment_unicode(self):
     with environment_as(XXX='¡'):
         self.assertEqual(os.environ['XXX'], '¡')
         with hermetic_environment_as(AAA='¡'):
             self.assertIn('AAA', os.environ)
             self.assertEqual(os.environ['AAA'], '¡')
         self.assertEqual(os.environ['XXX'], '¡')
Exemple #3
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()
        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: join the server to avoid interrupting ongoing runs.
            self._logger.info(
                "Waiting for ongoing runs to complete before exiting...")
            native_engine.nailgun_server_await_shutdown(self._server)
            self._logger.info("Exiting pantsd")
Exemple #4
0
 def test_hermetic_environment_unicode(self) -> None:
     with environment_as(XXX="¡"):
         self.assertEqual(os.environ["XXX"], "¡")
         with hermetic_environment_as(AAA="¡"):
             self.assertIn("AAA", os.environ)
             self.assertEqual(os.environ["AAA"], "¡")
         self.assertEqual(os.environ["XXX"], "¡")
Exemple #5
0
 def __call__(
     self,
     command: str,
     args: Tuple[str, ...],
     env: Dict[str, str],
     working_directory: bytes,
     cancellation_latch: PySessionCancellationLatch,
     stdin_fd: int,
     stdout_fd: int,
     stderr_fd: int,
 ) -> ExitCode:
     request_timeout = float(env.get("PANTSD_REQUEST_TIMEOUT_LIMIT", -1))
     # NB: Order matters: we acquire a lock before mutating either `sys.std*`, `os.environ`, etc.
     with self._one_run_at_a_time(
             stderr_fd,
             cancellation_latch=cancellation_latch,
             timeout=request_timeout,
     ), stdio_as(stdin_fd=stdin_fd,
                 stdout_fd=stdout_fd,
                 stderr_fd=stderr_fd), hermetic_environment_as(
                     **env), argv_as((command, ) + args):
         # NB: Run implements exception handling, so only the most primitive errors will escape
         # this function, where they will be logged to the pantsd.log by the server.
         logger.info(f"handling request: `{' '.join(args)}`")
         try:
             return self.single_daemonized_run(working_directory.decode(),
                                               cancellation_latch)
         finally:
             logger.info(f"request completed: `{' '.join(args)}`")
Exemple #6
0
 def test_hermetic_environment_subprocesses(self):
     with self.ensure_user_defined_in_environment():
         with hermetic_environment_as(AAA='333'):
             output = subprocess.check_output('env', shell=True).decode()
             self.assertNotIn('USER='******'AAA', os.environ)
             self.assertEqual(os.environ['AAA'], '333')
         self.assertIn('USER', os.environ)
         self.assertNotIn('AAA', os.environ)
Exemple #7
0
 def test_hermetic_environment_subprocesses(self) -> None:
     with self.ensure_user_defined_in_environment():
         with hermetic_environment_as(AAA="333"):
             output = subprocess.check_output("env", shell=True).decode()
             self.assertNotIn("USER="******"AAA", os.environ)
             self.assertEqual(os.environ["AAA"], "333")
         self.assertIn("USER", os.environ)
         self.assertNotIn("AAA", os.environ)
 def test_hermetic_environment_subprocesses(self):
   self.assertIn('USER', os.environ)
   with hermetic_environment_as(**dict(AAA='333')):
     output = subprocess.check_output('env', shell=True)
     self.assertNotIn('USER='******'AAA', os.environ)
     self.assertEquals(os.environ['AAA'], '333')
   self.assertIn('USER', os.environ)
   self.assertNotIn('AAA', os.environ)
Exemple #9
0
 def test_hermetic_environment_unicode(self):
     UNICODE_CHAR = '¡'
     ENCODED_CHAR = UNICODE_CHAR.encode('utf-8')
     with environment_as(**dict(XXX=UNICODE_CHAR)):
         self.assertEquals(os.environ['XXX'], ENCODED_CHAR)
         with hermetic_environment_as(**dict(AAA=UNICODE_CHAR)):
             self.assertIn('AAA', os.environ)
             self.assertEquals(os.environ['AAA'], ENCODED_CHAR)
         self.assertEquals(os.environ['XXX'], ENCODED_CHAR)
Exemple #10
0
 def test_hermetic_environment_subprocesses(self):
     self.assertIn('USER', os.environ)
     with hermetic_environment_as(**dict(AAA='333')):
         output = subprocess.check_output('env', shell=True).decode('utf-8')
         self.assertNotIn('USER='******'AAA', os.environ)
         self.assertEquals(os.environ['AAA'], '333')
     self.assertIn('USER', os.environ)
     self.assertNotIn('AAA', os.environ)
Exemple #11
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)
Exemple #12
0
 def test_hermetic_environment_unicode(self):
     UNICODE_CHAR = '¡'
     ENCODED_CHAR = UNICODE_CHAR.encode('utf-8')
     expected_output = UNICODE_CHAR if PY3 else ENCODED_CHAR
     with environment_as(**dict(XXX=UNICODE_CHAR)):
         self.assertEquals(os.environ['XXX'], expected_output)
         with hermetic_environment_as(**dict(AAA=UNICODE_CHAR)):
             self.assertIn('AAA', os.environ)
             self.assertEquals(os.environ['AAA'], expected_output)
         self.assertEquals(os.environ['XXX'], expected_output)
Exemple #13
0
 def test_hermetic_environment_unicode(self):
   UNICODE_CHAR = '¡'
   ENCODED_CHAR = UNICODE_CHAR.encode('utf-8')
   expected_output = UNICODE_CHAR if PY3 else ENCODED_CHAR
   with environment_as(**dict(XXX=UNICODE_CHAR)):
     self.assertEquals(os.environ['XXX'], expected_output)
     with hermetic_environment_as(**dict(AAA=UNICODE_CHAR)):
       self.assertIn('AAA', os.environ)
       self.assertEquals(os.environ['AAA'], expected_output)
     self.assertEquals(os.environ['XXX'], expected_output)
  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)
  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.
    self._exiter.set_except_hook(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)))

    # Setup a SIGINT signal handler.
    self._setup_sigint_handler()

    # 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.
    pid = str(os.getpgrp() * -1).encode('ascii')
    NailgunProtocol.send_pid(self._socket, pid)

    # 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(1, msg='Interrupted by user.\n')
      except Exception:
        self._exiter.handle_unhandled_exception(add_newline=True)
      else:
        self._exiter.exit(0)
Exemple #16
0
 def test_hermetic_environment(self):
   self.assertIn('USER', os.environ)
   with hermetic_environment_as(**{}):
     self.assertNotIn('USER', os.environ)
  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)
Exemple #18
0
 def test_hermetic_environment(self):
     self.assertIn('USER', os.environ)
     with hermetic_environment_as(**{}):
         self.assertNotIn('USER', os.environ)
Exemple #19
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)
Exemple #20
0
 def test_hermetic_environment(self) -> None:
     with self.ensure_user_defined_in_environment():
         with hermetic_environment_as():
             self.assertNotIn("USER", os.environ)