-
Notifications
You must be signed in to change notification settings - Fork 23
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
Give spawned threads control over async exceptions #77
Conversation
My impression is that the change of |
A global exception handler like that would need to be passed an argument in order to be able to distinguish one context from another, and then I guess that context should be passed as an argument to |
Would you show examples of the usage of the first argument? |
Perhaps, it is a good idea to add a new constructor like |
I'd be fine with that. I will give you an example of how I am using this, just in the middle of some other things at the moment. I will get back to you shortly :) |
it would indeed be hard to use, as ClientConfig is constructed once, and the exceptions need to be handled when the request is sent... This is somewhat over my head :), but from the user's point of view I just want to be to catch exceptions thrown in a streaming function where I send the request. |
So let me sketch the way I would want to use this. As I mentioned previously, I am working on a gRPC library, based on This is tricky to get completely right, so I have written a new abstraction to help me with this; see https://github.com/well-typed/grapesy/blob/main/src/Network/GRPC/Util/Thread.hs if you'd like to see the details. It's a bit like The way that this should ideally work is something like this: mask_ $ forkWithUnmask $ \unmask -> -- this bit lives in http2
-- this bit lives in my code :
threadBody tvar ... $ unmask $ do
-- do whatever needs to happen in this request This would ensure that all exceptions are disabled until we are safely instead the body of forkIO $ -- this lives in http2
-- this bit lives in my code :
mask $ \unmask ->
threadBody tvar ... $ unmask $ do
-- do whatever needs to happen in this request Almost the same, but not quite: there is a gap between that the thread gets started by It's true that I'm being a bit pedantic here, as there is no real reason to assume that an async exception will be delivered to the thread in that gap. But I don't like solutions that work most of the time; I prefer a solution that works all of the time :) Having the |
Obsoleted by #80. |
@kazu-yamamoto am I right that this is now fixed in 4.2? I was trying to migrate to it but it seems not as straightforward with all the changed types... |
@epoberezkin The answer is yes. |
@epoberezkin Off topic but I'm sure that you are interested in haskellfoundation/tech-proposals#57 |
@epoberezkin See 8871654. Its test code is an example of migration. |
@kazu-yamamoto , I'm not 100% sure if I got this right, because I don't yet have a complete picture of all the control flow in
http2
.http2
spawns various threads on behalf of the client code. The main idea is of this PR is to give that client code a way to handle asynchronous exceptions; specifically, to make sure that they have an exception handler of their own installed before async exceptions are enabled.The key change is this one:
Client side
On the client side, I am fairly confident that what I have is right. I have modified
forkManaged
(from #74) to not callunmask
directly, but instead pass it as an argument to the thread, so that it can decide to unmask exceptions if and where it wants; we then just pass that on as an argument to the function in theOutBodyStreaming
.Server side
On the server side, I am a bit less certain. Here threads are spawned by the manager; so I have changed the
Action
type toI then propagate the
unmask
argument through, and inresponse
pass it to the function insideOutBodyStreaming
; for all other calls, I just callunmask
directly (this is important, otherwise timeouts would not be possible).What I am unsure about is if this limits the scope of where async exceptions are enabled too much; in particular, what about the rest of
go
inworker
?Alternative approach
So far, I have only really needed this on the client, I haven't started on the server side implementation of gRPC yet; so I'm not sure if this change is even useful on the server side (mostly since those threads seem to do more than just execute user code). I suspect that it is, but I don't have hard evidence for it currently.
The only reason I made the server modifications at this point is that the client and the server share the
OutBody
type. So an alternative approach would be to decouple these, and allow the client and the server to diverge in the type of the handler; those it's nice of course to have the consistency.Finally, if you think that this whole thing is not a good idea and we should not give the user this control, then feel free to close the PR again; it allows me to make my async exception handling more airtight, but I don't depend on it critically.
(BTW, haven't forgotten about #76 , I will get to it soon.)