We are facing an issue in Jetty where on timeout it replays the original request again if we don't complete the request from async context. Here is the behavior, for every request we set a async listener with timeout, so we have 2 threads in play, one (Jetty Thread1
) is listening on timeout and other (Thread2
) is serving thread. Now let us say write data to client takes longer than timeout, since the request is not completed timeout thread gets triggered, it checks that someone is writing data so it returns silently. Jetty doesn't like returning silently, it replays the request back so another serving and timeout thread gets created and it goes on until data is written and async context is completed.
The code in question is here - In HttpChannelState
in expired()
method
if (aListeners!=null)
{
for (AsyncListener listener : aListeners)
{
try
{
listener.onTimeout(event);
}
catch(Exception e)
{
LOG.debug(e);
event.setThrowable(e);
_channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
break;
}
}
}
boolean dispatch=false;
synchronized (this)
{
if (_async==Async.EXPIRING)
{
_async=Async.EXPIRED;
if (_state==State.ASYNC_WAIT)
{
_state=State.ASYNC_WOKEN;
dispatch=true;
}
}
}
if (dispatch)
scheduleDispatch(); // <------------ dispatch again why
}
This is normal behaviour. You have put the request into async state and then not handled the timeout, so the request is redispatch with a DispatcherType of ASYNC.
If you add your own timeout listener and within that timeout you either complete or dispatch the asyncContext, then jetty will not redispatch it (unless your listener called dispatch).
You can also protect your async servlet code with a test for the DispatcherType, although that can be confused if you have multiple concerns that might be handled async.
asyncContext.addListener(new AsyncListener()
{
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
event.getAsyncContext().complete();
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
}
@Override
public void onError(AsyncEvent event) throws IOException
{
}
@Override
public void onComplete(AsyncEvent event) throws IOException
{
}
});