-
-
Notifications
You must be signed in to change notification settings - Fork 25
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
Support strict_exception_groups=True #188
base: master
Are you sure you want to change the base?
Conversation
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.
Hi John-- I may be slow in reviewing.
Please make sure the PR description summarizes the changes (e.g. mentions the internal error, API policy about exception groups, etc.).
Would need test coverage for any changes, but ok to hold off until I've reviewed the basics (as I can't promise we'll take the direction of this PR).
I'm itching to add Black and type checking to the CI, but that should be in a separate PR.
Please see #177. On type checking, I'm ok running a loose mypy pass, but reluctant to convert the codebase to strict type annotations, since fighting the type system is often a burden on maintenance (especially with a project on life support).
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.
Ah thank you, of course I'd complained about lack of Black before 😅
Agree on loose mypy just to check if the annotations that exist are correct. And I don't have any plans on adding annotations everywhere, just doing it for my own sake when developing.
Will try and make PR summary verbose and descriptive 👍
@belm0 do you have strong opinions on keeping python3.7 support? It's getting problematic to support with lotsa libraries not supporting it. If pinning test requirements to versions that support 3.7 we're not testing anything remotely recent, so would need to generate an additional requirements.txt file, etc etc. Testing trio versions < 0.22 is also getting messy. If we explicitly want to check for possible to get them to work, but I'm skeptical if it's worth the effort to increase the complexity of the test infra to get there. |
I'm ok dropping 3.7 since it's past EOL. (For what it's worth, mainstream packages now think it's ok to drop versions before EOL-- numpy and others dropping 3.8 already.)
My org uses trio < 0.22 and would like to continue using the latest trio-websocket (otherwise I'm no longer using the package I maintain). My view is that trio's handling of the strict exception group transition is problematic (especially making strict_exception_group a runtime option that can have effect globally on transitive dependencies), so we're just avoiding it. |
did some messing around:
|
tests/test_connection.py
Outdated
accepted_streams: list[ | ||
tuple[trio.abc.SendChannel[str], trio.abc.ReceiveChannel[str]] | ||
] = attr.ib(factory=list) | ||
queued_streams = attr.ib(factory=lambda: trio.open_memory_channel[str](1)) |
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.
Is this a mistake? I don't know why the lambda return expression includes a type annotation ([str]
).
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.
the annotation is for trio.open_memory_channel
, specifying the type of the objects being sent over the channels. It's not normally possible to specify the type on functions, so Trio says it's a callable class during type checking to allow for that usage: https://github.com/python-trio/trio/blob/4dcdc6dbe899c9ee295fa9c8e6a077100ae19f16/src/trio/_channel.py#L91
Without the annotation, queued_streams
would need the same long & verbose type annotation as accepted_streams
.
''' In #107, a request handler that throws an exception before finishing the | ||
handshake causes the task to hang. The proper behavior is to raise an | ||
exception to the nursery as soon as possible. ''' | ||
async def handler(request): | ||
raise ValueError() | ||
|
||
with pytest.raises(ValueError): | ||
with pytest.raises((BaseExceptionGroup, ValueError)) as exc: # pylint: disable=possibly-used-before-assignment |
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.
for cases like this, I would rather see an explicit assert exc
in the code over a pylint annotation
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 BaseExceptionGroup
that pylint fails to realize will always be available. But can switch to an assert
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.
...except mypy correctly picks up that it will always be truthy, and raises a warning if doing assert BaseExceptionGroup
>.>
trio_websocket/_impl.py
Outdated
class TrioWebsocketInternalError(Exception): | ||
... | ||
|
||
|
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.
needs docs
If multiple non-Cancelled-exceptions are encountered, then it will raise TrioWebSocketInternalError with the exceptiongroup as its cause. This should only be possible if the background task and the user context both raise exceptions. This would previously raise a MultiError with both Exceptions.
other alternatives could include throwing out the exception from the background task, raising an ExceptionGroup with both errors, or trying to do something fancy with cause or context.
It seems odd to raise an internal error where a user-context exception is implicated. Perhaps we can discard the internal error with a warning message, and propagate the user exception.
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 seems odd to raise an internal error where a user-context exception is implicated.
The "internalerror" bit would be that we're encountering a state that the internal logic assumed to be impossible - similar to TrioInternalError. I should've added a message to the exception to make that clearer.
Perhaps we can discard the internal error with a warning message, and propagate the user exception.
I quite like that solution. Only possible problem would be if this makes exception handling harder, but it 1. shouldn't happen, 2. they can still handle the user exception. We can also log detailed info on the exception we're suppressing, so the warning can be relatively brief.
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.
WebSocketServer.run and WebSocketServer._handle_connection are the other two places that opens a nursery. I've opted not to change these, since I don't think user code should expect any special exceptions from it, and it seems less obscure that it might contain an internal nursery.
In principle I'd like to state that the API doesn't raise exception groups, and that would include the server API. For the user's handler, perhaps add UserRequestHandlerException
?
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 think that for users that are embracing strict_exception_groups=True
and are used to handling exceptiongroups everywhere we shouldn't purposely obscure the groups and make errors harder for them to handle (and Trio's stance is that this is the default and should be embraced). I haven't delved too too deeply into them to see what it would imply to try and avoid exceptiongroups, but I think it might necessitate a change in API where we might end up changing what exceptions are being raised - even for users sticking with strict_exception_groups=False
.
…t in CI. * Adds loose mypy run to Makefile & CI * Adds some type annotations * Adds 3.13-dev test run. It currently fails, but will pass once trio and cffi push new releases. * Adds blurb to readme on status of the project. * Bump package versions in requirements-dev.txt and requirements-dev-full.txt * Adds small tests to slightly bump code coverage
…m within exceptiongroups revert making close_connection CS shielded, as that would be a behaviour change causing very long stalls with the default timeout of 60s add comment for pylint disable move RaisesGroup import
42f718f
to
7eb779b
Compare
Okay I think I'm finally starting to understand |
Split out from #188 with a `--patch` checkout. This does destroy the commit history, so I might need to rewrite the history in #188 to avoid conflicts. * Drops python3.7 support. It still works afaik, but is a pain to test in CI. * Adds loose mypy run to Makefile & CI * Adds some type annotations * ~~Adds 3.13-dev test run. It currently fails, but will pass once trio and cffi push new releases.~~ * Adds blurb to readme on status of the project. * Bump package versions in requirements-dev.txt and requirements-dev-full.txt * Adds small tests to slightly bump code coverage
Big rewrite, fairly happy with the state of things - except:
debugging old_deps is nooooo fun when the only exception being surfaced to me is errors inside TracebackException. 🙃 |
Fixes #132 and #187
open_websocket
to only raise a single exception, even when running understrict_exception_groups=True
KeyboardInterrupt
sTrioWebSocketInternalError
with the exceptiongroup as its__cause__
. This should only be possible if the background task and the user context both raise exceptions. This would previously raise aMultiError
with both Exceptions.__cause__
or__context__
.WebSocketServer.run
andWebSocketServer._handle_connection
are the other two places that opens a nursery. I've opted not to change these, since I don't think user code should expect any special exceptions from it, and it seems less obscure that it might contain an internal nursery.split out into #189
setup.py
. The code almost surely still works on 3.7 though, but testing it is a pain.build_and_test_latest
run will pass