Folks,
I have been giving some more thought to how best to unify blocking and
non-blocking handlers, and I have come up with a new approach.
At the moment what we have is a mess. We have two types of handlers,
confusingly called blocking and non-blocking. This terminology is not in
any way accurate, it is quite possible for a non-blocking handler to
dispatch to a worker thread and perform a blocking operation (such as
looking up a user from the IDM). It is also possible for blocking
handlers to perform non-blocking IO, such as when using the Servlet 3.1
async IO API.
These two different types of handlers make it very difficult to write
general purpose handlers that work in all situations. e.g. at the moment
we have a requirement for the security handlers to sit in the middle of
the servlet handler chain, however due to servlet handlers being
'blocking' and security being 'non-blocking' this is not really possible
without some form of adaptor.
I am proposing that we make the following changes:
1) Change the signature of HttpHandler#handleRequest to
void handleRequest(HttpServerExchange exchange) throws Exception;
This will allow exceptions to propagate up the exchange to the root
handler. If an exception propagates all the way the exchange will be
ended with a 500 error code.
2) Change the default behavior so that when the handler call stack
returns the exchange is ended. This is consistent with how blocking
handlers work at the moment. If an async handler wants to either
dispatch to a worker or perform async IO it has to either call
HttpServerExchange.dispatch() or resume reads/writes. In either case the
call stack will return, and then the requested action will be
dispatched. This means that there will only ever be a single thread
active in the exchange.
3) Add the methods isInIoThread() to the exchange. This means that
handlers that need to perform blocking actions will do the following:
if (exchange.isInIoThread()) {
exchange.dispatch(this);
return;
}
Rather than the current approach using WorkerDispatcher, that requires a
thread local read and an object creation.
4) Add the isBlocking() and startBlocking() method to the exchange
(actually these are already there). The startBlocking() method starts
blocking IO, and enables the use of get(Input/Output)Stream on the
exchange.
I think these changes have a number of advantages. In particular:
- It will be much less likely to 'loose' a request due to a buggy
handler. At the moment if an async handler returns without ending the
exchange or dispatching then the request just disappears, which can be
quite difficult to debug.
- Handlers that do not perform IO will work will all types of requests,
and can be placed anywhere in the handler chain.
- The threading model should become simpler for blocking requests that
need to do async work (such as servlet 3.1), as the new model means that
only one thread will ever be active in an exchange.
I have made a start of these changes here if anyone is interested, it
compiles and the tests pass, however there are probably still some bugs:
https://github.com/stuartwdouglas/undertow/compare/remove-blocking
Stuart