-
Notifications
You must be signed in to change notification settings - Fork 87
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
Differences in calling WriteListener.onError #433
Comments
@markt-asf @stuartwdouglas I'm not sure what the correct behavior is for this? Your thoughts? |
I don't see a reason for |
@markt-asf there is a reason to think that Note that there is also AsyncListener.onError that can be used to report errors in between Io operations. But Io operations errors should go to the write listener at least, maybe both. @lachlan-roberts can you test if AsyncListener.onError is also being called? Perhaps the solution is that Write listener.onError is called IFF there is a pending callback from isReady, otherwise AsyncListener.onError is called.. unless exception can be thrown from write, flush or close? Complex! But at least we can be precise about where an exception is reported and it will only be reported once |
Thinking about it I think that async write should never throw, and the result should always be passed to |
@stuartwdouglas I'm OK with that interpretation (that write should not throw), but then there are still some issues:
|
|
So how about this, in async mode:
|
With regards to 'if there is an IO exception when there is no pending write, flush or close operation, then that is reported via AL.onError' I don't really like the thread safety implications, as you can basically be busy doing work that is not IO related at all, and then get an error callback running in another thread. I guess this is no different to what happens already with timeouts, but I don't like that either :-) |
I know what you mean. It is ugly having 3 onError methods. But if a stream is reset whilst there are no IO operations, then is an exception delivered to the WL, RL or AL? Picking one is going to be intrinsically racey. Maybe the solution is to always deliver it to all of them? |
For that specific instance I think I'd expect the exception on the AL with the WL and RL only seeing exceptions if further write/reads are attempted. ie treat it in a similar manner to timeout. |
Another alternative would be to delay until IO is attempted, which would match HTTP/1 semantics. That said I don't really like that approach, so I think delivering to the AL is probably the way to go. |
So if I'm reading you both right.... in async mode:
I have quibbles with this, but no more or less than other suggestions. I don't think there is a perfect solution, so happy to go with a common OK solution. |
I was thinking a bit different:
IMHO write, flush and close should never throw, we should have one place to handle errors and that should be the listener.
IMHO these should always be reported to the relevant write listener. In this case because we know the issue is related to a specific write operation then delivering to the write listener error handler makes sense. The isReady stipulation really does not make sense for errors in close().
I think you mean onWritePossible, I agree except for the requirement to call isReady.
Makes sense.
|
yeah but no!! There are two listeners we can report errors to, so there is no "the listener". I don't think it ever makes sense to always report to AL.onError (else why have WL.onError), but then it also doesn't work out to always report on WL.onError (but perhaps more sense???). I too would like write/flush/close to never throw in async. Perhaps that can be achieved with always calling WL.onError? Anyway, is my first proposal (3 days ago) better for you? If not, want to have a go at writing up what you think? |
I think the one from 3 days ago is good. I don't super like the idea of IO exceptions being delivered with no IO operations active but I think it is ok, and is really no different to what happens with timeouts. |
This is from 3 days ago, but with the addition that write can throw if it is called after a previously reported exception. In async mode:
|
It looks like there is no circumstances where I would say that if I also don't like that as written if you attempt to read from the underlying socket, get an IO exception on the read, and there is a pending write operation then you will have to deliver the read exception to the write listener. How about: Write:
Read:
General:
|
I like a lot of this latest description. A couple of minor comments. In Tomcat, an IOException during Read needs the same clarification that calling |
I think the Tomcat behavior you describe is consistent with what I have written above, even though it is not explicitly called out. I don't think we need to clarify that read will throw after In terms of symmetry the text above was a bit of a brain dump. I think we are close to agreement so do you want me to write up a PR tomorrow and we can move the conversation there? |
+1 to a PR, |
Fixes jakartaee#433 Signed-off-by: Stuart Douglas <[email protected]>
Fixes jakartaee#433 Signed-off-by: Stuart Douglas <[email protected]>
I have run the same test with also adding an AsyncContext asyncContext = req.startAsync();
asyncContext.addListener(new AsyncListener(){...});
ServletOutputStream out = response.getOutputStream();
out.setWriteListener(new WriteListener()
{
public void onWritePossible()
{
try
{
while (out.isReady())
out.write(someBuffer);
}
catch(Throwable t)
{
t.printStackTrace();
}
}
public void onError(Throwable t)
{
t.printStackTrace();
asyncContext.complete();
}
});
Note that I have added a |
Undertow will call the The version of Undertow we use in Quarkus (which is basically a fork of Undertow on top of Vert.x) will behave much more like the proposed spec text, with methods never throwing and all errors reported via the listeners. |
I have repeated this experiment after updating my test over Jetty/Tomcat/Undertow to what I believe are the latest servlet 6 versions. (Jetty-12.0.5, Tomcat-10.1.18, Undertow-2.3.10.Final) The test is setup like this: The client is continuously reading but aborts when it reads over
The results:
Note: Even if the exception from |
Thanks @lachlan-roberts. I see no problem with ServletOutputStream.write() throwing even when in async mode. The reason being that a call to
OR
I think that I think the following sequence is not valid:
In short:
|
Fixes jakartaee#433 Signed-off-by: Stuart Douglas <[email protected]>
If an async servlet is in a loop like:
and the client receiving the response closes the connection at some point, then we see different behaviors from Jetty, Tomcat and Undertow:
isReady()
is called it returns true.isReady()
is called it returns false.isReady()
is called it return false, but thenonError
is still not called.I'm not sure any of these behaviors is strictly correct:
isReady()
has not been called and has not returned false?isReady()
return false and thenonError
is called with the problem?isReady()
return?The text was updated successfully, but these errors were encountered: