-
Notifications
You must be signed in to change notification settings - Fork 89
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
Clarify under what circumstances onError is called #434
base: master
Are you sure you want to change the base?
Conversation
I have ended up making some changes compared to what was discussed, as the more I thought about it I thought there were some issues with the proposals we talked about on the issue. In particular I think the issues are:
Basically the more I thought about it the more I came to the conclusion that if we want to support eager notification of IO failure (e.g. RST_STREAM) we should add a special listener just for this that is usable in both blocking and non blocking mode. I also think that delivering |
Stuart,
It is a not uncommon use case that some compute intensive job is done asynchronous, but it is desirable to stop that work if the connection is ever closed. This is an oft requested feature, but in the days of http1 was just not possible because you could not detect close without attempting IO. But this is now possible with http2 and http3. Jetty definitely has users that plug into AL.onError and extect to see some exception or other if the stream is reset, even if no IO is attempted. Now that we have h2/h3 it seams silly to make such applications waste all that CPU finishing their compute intensive task, only to finally discover the connection has been closed when they try to write the response. We would have to put in a non-compliant mode to support such users if we end up hiding exceptions without IO. Now they don't need to be delivered to AL.onError... so long as they are delivered to one of the onError listeners in a timely fashion, then we are good. |
The issue I have with this approach is that you can still have that requirement even with blocking IO and without using |
Hmmm the text is rather complex and on my third read through I'm still not sure I entirely understand. I'm wondering if we are trying to be too smart by avoiding multiple reports of the same issue. What about if whenever there is an issue with the connection, we report it once to each and any of the onError listeners registered. So how about:
|
Basically something like this: #435 |
+1, that should already be covered by the text
I would rather deliver do both a WL and RL, as long as the relevant streams are still open (e.g. if read() has returned -1 then we should not deliver to the RL, same if close has been called on either stream). If we can't deliver to a RL or WL then I would rather add a mechanism like in #435 that can always be notified even when async is not in use. |
@stuartwdouglas I'm OK with not calling WL.onError or RL.onError if they have been closed, which means:
As for AL.onError, I'm happy for it to always be called, or if we want to avoid duplication, it could be called IFF one of the RL or WL is either not set or is closed. |
I agree with your description of 'RL is closed', however 'WL is closed if OutputStream.close has been called' should be 'OutputStream.close has been called and any buffered data has been successfully written to the client', as the close could trigger a write which could then fail. Like I mentioned above I would much rather we add a connection specific listener rather than repurpose AL.onError to handle async connection failures. If everyone else is in favor I guess I can live with it but it feels wrong to me, and can change the behaviour of existing applications as it can now be called at times when it would not otherwise have been called. |
The current Tomcat behaviour during async IO can be summarised as:
On reflection, I share Stuart's concern about starting to send async connection issues to AL. I can see the benefits of an new listener for these. I wonder if we can simplify the "is RL/WL closed" element to "if the request is in async mode"? I think we should include the handling of exceptions thrown from RL.onDataAvailable and friends in the list of reasons RL/WL onError will be called. |
@stuartwdouglas Can you wake up this PR and let's get it merged. |
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.
updates in line with my comments on the issue #433.
I also think we should update AsyncListener
to say that it's onError
will be called for errors when there are no IO operations current (i.e. no ReadListener#onError
and no WriteListener#onError
applicable) or if an exception is thrown from onWritePossible
or onDataAvailable
etc.
I think I am OK with the direction this is heading in but I'd like to review the changes once the PR has been rebased to take account of the clarifications that have already been made to the affected classes. |
Fixes jakartaee#433 Signed-off-by: Stuart Douglas <[email protected]>
162a20a
to
50f278b
Compare
Sorry, I was on PTO, I have rebased and applied Greg's suggestions. |
Co-authored-by: Greg Wilkins <[email protected]>
* Invoked when an error occurs writing data after {@link ServletOutputStream#setWriteListener(WriteListener)} has been | ||
* called. This method will be invoked if there is a problem while data is being written to the stream and either: | ||
* <ul> | ||
* <li>{@link ServletOutputStream#isReady()} has been invoked and returned false</li> |
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 this text has some problems.
Say I am streaming some data from a remote source and call write()
, I am expecting more data in future, but I don't have any more data ready yet.
If the async write files in this case then we have no way of reporting this to the user until they attempt to call isReady()
. Because they have not call isReady
and it returned false we are not allowed to invoke the listener.
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 also really don't like the idea of doubling up error handling. The original proposal meant that you only had to implement onError
, with this change you now need to handle errors thrown from the stream. Should we add a section that if onWritePossible
throws IOException
then the onError
method is called? It seems like obvious behavior but I am not sure if it is called out anywhere. This would mean that if write
throws you can just let the exception propagate and the listener will handle 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.
Stuart, by onError
in your comment, I'm assuming that you mean AsyncListener.onError
and not WriteListener.onError
.
So I do like the "if OWP
throw then the AL.onError
method is called" as a good way to give control to the application about how write errors are reported to AL.onError
.
I agree it there is something strange about not reporting a known error to WL.onError
until isReady()
is called. But if we do not, then we do not have an easy way to tell if a previous write has completed or not. Currently the only way we have on knowing a previous write has completed is if isReady()
has returned true. Perhaps you could also say that if onWritePossible
has been called, that also indicates completion... but really isReady()
should be called from within OWP and checked for a true response to protect from spurious wakeups. If WL.onError
can be called at any time, then it may be called simultaneously to another thread calling isReady()
and then the app will never know if the call to WL.onError
was the result of the false return from isReady
or if it just happened anyway and another call is on its way.
I'm not sure there is a good solution given the current API. I think the best we can do is be rigorous with the scheduling so we at least avoid races like the one above.
Note that if the app you described really wants to know about an error before the next write is ready, there is nothing stopping it calling isReady()
immediately after the write
and then it will know that the operation has either completed or that either ODA or WL.onError will be called as soon as the operation is complete.
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.
Thinking about it some more I agree with your concerns about thread safety. The only way I can think to make this work is to allow this use case via the flush method. We could add something like 'If flush() is called in async mode then isReady must not return true until the data is written out to the client'.
Then if you really care about error notification and are not going to immediately write again you can call flush + isReady
to see the results.
Fixes #433
Signed-off-by: Stuart Douglas [email protected]