Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip - add contextmanager'd exiters and change return type of run() #7606

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/python/pants/base/exception_sink.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,18 @@ def _handle_signal_gracefully(cls, signum, signame, traceback_lines):
# Exit, printing the output to the terminal.
cls._exit_with_failure(terminal_log_entry)

@classmethod
@contextmanager
def exiter_as(cls, exiter):
"""A contextmanager which temporarily overrides the exiter."""
try:
previous_exiter = cls._exiter if cls._exiter else exiter
cls._exiter = exiter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is needed, as ExceptionSink.reset_exiter() already overrides the class variables :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to edit reset_exiter() the way we did reset_signal_handler() in order to support trapped_signals() in #6574. Luckily all that would mean is making reset_exiter() return the previous exiter.

@classmethod
def reset_signal_handler(cls, signal_handler):
"""
Class state:
- Overwrites `cls._signal_handler`.
OS state:
- Overwrites signal handlers for SIGINT, SIGQUIT, and SIGTERM.
:returns: The :class:`SignalHandler` that was previously registered, or None if this is
the first time this method was called.
"""
assert(isinstance(signal_handler, SignalHandler))
# NB: Modify process-global state!
for signum, handler in signal_handler.signal_handler_mapping.items():
signal.signal(signum, handler)
# Retry any system calls interrupted by any of the signals we just installed handlers for
# (instead of having them raise EINTR). siginterrupt(3) says this is the default behavior on
# Linux and OSX.
signal.siginterrupt(signum, False)
previous_signal_handler = cls._signal_handler
# NB: Mutate the class variables!
cls._signal_handler = signal_handler
return previous_signal_handler
@classmethod
@contextmanager
def trapped_signals(cls, new_signal_handler):
"""A contextmanager which temporarily overrides signal handling."""
try:
previous_signal_handler = cls.reset_signal_handler(new_signal_handler)
yield
finally:
cls.reset_signal_handler(previous_signal_handler)

ExceptionSink.reset_exiter(cls._exiter)
yield cls._exiter
finally:
cls._exiter = previous_exiter
ExceptionSink.reset_exiter(cls._exiter)

# Setup global state such as signal handlers and sys.excepthook with probably-safe values at module
# import time.
Expand Down
22 changes: 9 additions & 13 deletions src/python/pants/bin/daemon_pants_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,10 +273,6 @@ def _raise_deferred_exc(self):
# destructuring), treat it like a bare exception.
raise self._deferred_exception

def _maybe_get_client_start_time_from_env(self, env):
client_start_time = env.pop('PANTSD_RUNTRACKER_CLIENT_START_TIME', None)
return None if client_start_time is None else float(client_start_time)

def run(self):
"""Fork, daemonize and invoke self.post_fork_child() (via ProcessManager).

Expand Down Expand Up @@ -332,40 +328,40 @@ def post_fork_child(self):

# 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):
hermetic_environment_as(**self._env),\
ExceptionSink.exiter_as(self._exiter) as daemon_exiter:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we could avoid having .exiter_as() have to yield a value, by instead exposing an accessor to the globally registered exiter. This could look like the edit suggestion I've made in local_pants_runner.py. The only reason that might be nicer is because we then wouldn't have to pass the exiter as an argument into LocalPantsRunner, it would just read the global value.

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

# Clean global state.
clean_global_runtime_state(reset_subsystem=True)

# Otherwise, conduct a normal run.
runner = LocalPantsRunner.create(
self._exiter,
daemon_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()
exit_code = runner.run()
daemon_exiter.exit(exit_code)
except KeyboardInterrupt:
self._exiter.exit_and_fail('Interrupted by user.\n')
daemon_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)
daemon_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)
daemon_exiter.exit(PANTS_SUCCEEDED_EXIT_CODE)
48 changes: 25 additions & 23 deletions src/python/pants/bin/local_pants_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from __future__ import absolute_import, division, print_function, unicode_literals

import logging
import time
from builtins import object, str

from pants.base.build_environment import get_buildroot
Expand Down Expand Up @@ -213,7 +214,7 @@ def __init__(self, build_root, exiter, options, options_bootstrapper, build_conf
self._repro = None
self._global_options = options.for_global_scope()

def set_start_time(self, start_time):
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()
Expand All @@ -226,13 +227,9 @@ def set_start_time(self, start_time):
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)

def run(self):
with maybe_profiled(self._profile_path):
self._run()
return self._run()

def _maybe_handle_help(self):
"""Handle requests for `help` information."""
Expand Down Expand Up @@ -286,22 +283,27 @@ def _compute_final_exit_code(*codes):
return max_code

def _run(self):
try:
self._maybe_handle_help()
self._set_start_time(time.time())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not allowing the start time to be injected via environment variable anymore sounds like something we would want to note in the PR description perhaps.


engine_result = self._maybe_run_v2()
goal_runner_result = self._maybe_run_v1()
finally:
# wrap the outer exiter
local_exiter = LocalExiter(self._run_tracker, self._repro, exiter=self._exiter)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
local_exiter = LocalExiter(self._run_tracker, self._repro, exiter=self._exiter)
local_exiter = LocalExiter(self._run_tracker, self._repro, exiter=ExceptionSink.global_exiter())

with ExceptionSink.exiter_as(local_exiter):
try:
run_tracker_result = self._run_tracker.end()
except ValueError as e:
# Calling .end() sometimes writes to a closed file, so we return a dummy result here.
logger.exception(e)
run_tracker_result = PANTS_SUCCEEDED_EXIT_CODE

final_exit_code = self._compute_final_exit_code(
engine_result,
goal_runner_result,
run_tracker_result
)
self._exiter.exit(final_exit_code)
self._maybe_handle_help()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this will exit(), so if you're returning in this function, it might be worth to return here as well.


engine_result = self._maybe_run_v2()
goal_runner_result = self._maybe_run_v1()
finally:
try:
run_tracker_result = self._run_tracker.end()
except ValueError as e:
# Calling .end() sometimes writes to a closed file, so we return a dummy result here.
logger.exception(e)
run_tracker_result = PANTS_SUCCEEDED_EXIT_CODE

final_exit_code = self._compute_final_exit_code(
engine_result,
goal_runner_result,
run_tracker_result
)
return final_exit_code
1 change: 0 additions & 1 deletion src/python/pants/bin/pants_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,4 @@ def run(self):
self._env,
options_bootstrapper=options_bootstrapper
)
runner.set_start_time(self._start_time)
return runner.run()