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

Conversation

ity
Copy link
Contributor

@ity ity commented Apr 23, 2019

Problem

see #7597

Solution

Result

"""A contextmanager which temporarily overrides the exiter."""
try:
previous_exiter = cls._exiter
cls._exiter = exiter
Copy link
Member

@stuhood stuhood Apr 23, 2019

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)?

Copy link
Contributor Author

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

@stuhood stuhood requested a review from cosmicexplorer April 23, 2019 01:09
@ity ity requested a review from blorente April 24, 2019 07:55
Copy link
Contributor

@blorente blorente left a 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
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)

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.

"""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.

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)

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())

@@ -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.

@@ -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.

@ity
Copy link
Contributor Author

ity commented May 2, 2019

@ity ity mentioned this pull request May 2, 2019
2 tasks
@ity
Copy link
Contributor Author

ity commented Jun 19, 2019

@blorente I think this is still relevant - can look at this next, lmk if you think otherwise.

@blorente
Copy link
Contributor

This is very much relevant, will take a look myself and hand off if it's not done in half a day.

blorente added a commit that referenced this pull request Jul 28, 2019
**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.
@Eric-Arellano
Copy link
Contributor

Closing due to being stale.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants