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

Fix signalctl for Python 3.11 #4374

Merged
merged 6 commits into from
Sep 19, 2022
Merged

Fix signalctl for Python 3.11 #4374

merged 6 commits into from
Sep 19, 2022

Conversation

fantix
Copy link
Member

@fantix fantix commented Sep 16, 2022

Python 3.11 deprecated the msg param in Future.cancel(), we can no longer depend on the msg to mark the reason of cancellations. That means, it is impossible to match recorded signals to CancelledErrors and replace them with SignalErrors.

The solution here is to raise an ExceptionGroup containing all signal errors, and its __cause__ will be set to the cancellation error chain displaying where the signal hit the user code. User error will be merged into the same ExceptionGroup with signal errors, so be prepared to use the except* grammar to handle exceptions.

One exception is CancelledError itself. If SignalController.wait_for() is cancelled from outside (and no signals captured after that), a naked CancelledError will still be raised. This made it possible to chain SignalController.wait_for() to asyncio.wait_for() for timeouts. Similarly, a group of signal errors can still be found in the __cause__.


UPDATE: the use of ExceptionGroup is dropped because mypy isn't ready yet, now we're chain the signal errors with __context__.

Python 3.11 deprecated the `msg` param in `Future.cancel()`, we can no
longer depend on the `msg` to mark the reason of cancellations. That
means, it is impossible to match recorded signals to CancelledErrors and
replace them with SignalErrors.

The solution here is to raise an ExceptionGroup containing all signal
errors, and its `__cause__` will be set to the cancellation error chain
displaying where the signal hit the user code. User error will be merged
into the same ExceptionGroup with signal errors, so be prepared to use
the `except*` grammar to handle exceptions.

One exception is CancelledError itself. If `SignalController.wait_for()`
is cancelled from outside (and no signals captured after that), a naked
CancelledError will still be raised. This made it possible to chain
`SignalController.wait_for()` to `asyncio.wait_for()` for timeouts.
Similarly, a group of signal errors can still be found in the __cause__.
@fantix fantix requested review from 1st1 and elprans September 16, 2022 21:52
@fantix
Copy link
Member Author

fantix commented Sep 16, 2022

Oh mypy! python/mypy#12840

@elprans
Copy link
Member

elprans commented Sep 16, 2022

Hm, is there a way to retain Python 3.10 compatibility for now without too much hassle? If not, then I think we should sit on this until Python 3.11 is out and all dependencies are in order.

@fantix
Copy link
Member Author

fantix commented Sep 16, 2022

is there a way to retain Python 3.10 compatibility for now without too much hassle?

Yeah I think so, instead of using ExceptionGroup, we could just chain the signal errors with __context__, which should be just fine.

edb/common/signalctl.py Outdated Show resolved Hide resolved
edb/common/signalctl.py Outdated Show resolved Hide resolved
# Event 3: cancelled by outer code.
if not fut.done():
fut.cancel(msg=e)
fut.cancel()
outer_cancelled_at_last = True
Copy link
Member

Choose a reason for hiding this comment

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

The name is a bit cryptic. Maybe just call it waiter_was_cancelled?

Copy link
Member Author

Choose a reason for hiding this comment

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

waiter_was_cancelled means "event 3 happened at least once", while we need "event 3 is the last event", because without ExceptionGroup, we can only raise one error, and it should be the last one in user-code. For examplye, if an outer cancel - say asyncio.wait_for() expired - was handled in a "finally" block in user-code, but we captured a sginal in the "finally" block, we should raise the SignalError instead of the CancelledError, in which case this flag should be False even though "waiter was cancelled" once.

On the same page I'm still open to suggestions for naming.

Copy link
Member

Choose a reason for hiding this comment

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

OK, sounds good.

@fantix fantix merged this pull request into py311 Sep 19, 2022
@fantix fantix deleted the signalctl-py311 branch September 19, 2022 22:02
elprans pushed a commit that referenced this pull request Sep 22, 2022
Python 3.11 deprecated the `msg` param in `Future.cancel()`, we can no
longer depend on the `msg` to mark the reason of cancellations. That
means, it is impossible to match recorded signals to CancelledErrors and
replace them with SignalErrors.

The solution here is to chain all signal errors with `__context__` and raise
the last error, and its `__cause__` will be set to the cancellation error chain
displaying where the signal hit the user code.

* Bump CI to use Python 3.11
* Compute CI cache key hash with major Python version
* Fix cargo-test with venv cache key
elprans pushed a commit that referenced this pull request Sep 23, 2022
Python 3.11 deprecated the `msg` param in `Future.cancel()`, we can no
longer depend on the `msg` to mark the reason of cancellations. That
means, it is impossible to match recorded signals to CancelledErrors and
replace them with SignalErrors.

The solution here is to chain all signal errors with `__context__` and raise
the last error, and its `__cause__` will be set to the cancellation error chain
displaying where the signal hit the user code.

* Bump CI to use Python 3.11
* Compute CI cache key hash with major Python version
* Fix cargo-test with venv cache key
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.

3 participants