-
-
Notifications
You must be signed in to change notification settings - Fork 652
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
Conversation
"""A contextmanager which temporarily overrides the exiter.""" | ||
try: | ||
previous_exiter = cls._exiter | ||
cls._exiter = exiter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's possible that cls._exiter
might no longer necessary, since we should always be able to use the global mutable exiter? If that's the case, then both DaemonPantsRunner.set_exiter
and LocalPantsRunner.set_exiter
could be merged into the same method somewhere. Maybe with ExceptionSink.exiter_as(mk_exiter=lambda previous_exiter: ..):
or something (where the mk_exiter
function could optionally wrap the existing exiter, or replace it)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, absolutely, was thinking the same that its duplicated for no good reason - will look into this tomorrow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the architecture, I think what's left are my comments and adding unit tests.
"""A contextmanager which temporarily overrides the exiter.""" | ||
try: | ||
previous_exiter = cls._exiter if cls._exiter else exiter | ||
cls._exiter = exiter |
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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.
pants/src/python/pants/base/exception_sink.py
Lines 278 to 311 in f9b0313
@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) |
run_tracker_result | ||
) | ||
self._exiter.exit(final_exit_code) | ||
self._maybe_handle_help() |
There was a problem hiding this comment.
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.
"""A contextmanager which temporarily overrides the exiter.""" | ||
try: | ||
previous_exiter = cls._exiter if cls._exiter else exiter | ||
cls._exiter = exiter |
There was a problem hiding this comment.
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.
pants/src/python/pants/base/exception_sink.py
Lines 278 to 311 in f9b0313
@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) |
goal_runner_result = self._maybe_run_v1() | ||
finally: | ||
# wrap the outer exiter | ||
local_exiter = LocalExiter(self._run_tracker, self._repro, exiter=self._exiter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
local_exiter = LocalExiter(self._run_tracker, self._repro, exiter=self._exiter) | |
local_exiter = LocalExiter(self._run_tracker, self._repro, exiter=ExceptionSink.global_exiter()) |
@@ -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: |
There was a problem hiding this comment.
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.
@@ -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()) |
There was a problem hiding this comment.
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.
update with https://github.com/pantsbuild/pants/pull/7596/files/2a46298b3ad112244b42e2b8fe80719baf20b7c4#r280397362 or create another issue |
@blorente I think this is still relevant - can look at this next, lmk if you think otherwise. |
This is very much relevant, will take a look myself and hand off if it's not done in half a day. |
**Problem** There is a bug where, once the LocalPantsRunner has reset the global exiter to its own LocalExiter, it won't un-set it ever, and therefore unhandled exceptions will use that exiter to exit. This has caused issues, around this line, because the LocalPantsRunner reset the global exiter and the DaemonPantsRunner didn't reset it. There is a regression test to demonstrate this issue. **Solution** Following the discussion on #7606, implement a contextmanager that temporarily overrides the global exiter. Remove the concept of "my exiter", and make classes (LocalPantsRunner, DaemonPantsRunner, PantsRunner and PantsDaemon) use the global exiter. **Result** The regression test passes, and _log_unhandled_exceptions_and_exit works as expected in DaemonPantsRunner.
Closing due to being stale. |
Problem
see #7597
Solution
Result