jsr340-experts@servlet-spec.java.net

[jsr340-experts] Re: setWriteListener setReadListener

From: Greg Wilkins <gregw_at_intalio.com>
Date: Tue, 21 May 2013 09:42:08 +1000

Working through the state machine before, I come to the conclusion that
autoblocking is over complex and provides a semantic that we don't want to
provide.

With normal blocking IO, we don't provide any mutual exclusion and the
application is responsible for synchronisation at a higher level.

However with autoblocking, a write that should be autoblocked can happen
before the previous asynchronous write is complete. The application cannot
delay the autoblocking write until the previous request is complete because
it has no way of knowing when it is complete, since if it calls isReady()
that will make the next write an async write rather than an autoblocking
one. Thus to implement autoblocking we have to be prepared to block a
write until such time as the previous asynchronous write completes - which
is a semantic in advance of what we offer for normal blocking. What if
we get multiple simultaneous autoblock writes? Do we have to queue them in
the order they were received and unblock each as the previous write
completes? This is the makings of a dangerous infinite queue!

So on reflection, I think that we should not provide autoblocking and that
once we have switched to asynchronous mode it is an ISE to call write
without a prior call to isReady() that returns true or a onWritePossible()
callback.

The simplified state machine that results is:

ACTION OPEN ASYNC READY PENDING
UNREADY
-------------------------------------------------------------------------------------
setWriteListener() READY->owp ise ise ise
ise
write() OPEN ise PENDING wpe
wpe
isReady() ise READY:true READY:true UNREADY:false
UNREADY:false
write completed - - - ASYNC
READY->owp








On 20 May 2013 18:17, Greg Wilkins <gregw_at_intalio.com> wrote:

> To be really clear about the async behaviour, I'm wondering if we should
> include a state machine in the MR? That helped clarify the async request
> lifecycle.
>
>
> This is the state machine that I'm currently implementing in Jetty for
> writes, which includes auto blocking:
>
>
> ACTION BLOCKING READY PENDING UNREADY
> COMPLETE AUTOBLOCK
>
> ------------------------------------------------------------------------------------------------
>
> setWriteListener() READY->owp ise ise ise
> ise ise
>
> write() BLOCKING PENDING AUTOBLOCK
> wpe AUTOBLOCK wpe
>
> isReady() ise READY:true UNREADY:false
> UNREADY:false READY:true ise
>
> write completed BLOCKING - COMPLETE READY->owp
> - AUTOBLOCK/COMPLETE
>
>
> ------------------------------------------------------------------------------------------------
>
>
> ise = IllegalStateException
> wpe = WritePendingException
> ->owp = call onWritePossible
>
>
> So some typical sequences of events/states are
>
> Blocking writes:
>
> 1. getOutputStream() BLOCKING
> 2. write() BLOCKING
> 3. write() BLOCKING
> 4. ...
>
> Async writes that complete immediately twice and then is incomplete twice
>
> 1. getOutputStream() BLOCKING
> 2. setWriteListener() READY->owp
> 3. onWritePossible()
> 1. write() PENDING
> 2. write 3.1 completed COMPLETE
> 3. isReady() READY:true
> 4. write() PENDING
> 5. write 3.4 complete COMPLETE
> 6. isReady() READY:true
> 7. write() PENDING
> 8. isReady() UNREADY:false
> 4. write 3.7 complete READY->owp
> 5. onWritePossibe()
> 1. write() PENDING
> 2. isReady() UNREADY:false
> 6. write 5.1 complete READY->owp
> 7. onWritePossibe() READY
>
>
> Async write that starts a thread to do an autoblocking write that happens
> after async write completes
>
> 1. getOutputStream() BLOCKING
> 2. setWriteListener() READY->owp
> 3. onWritePossible()
> 1. write() PENDING
> 2. AsyncContext#start(task)
> 4. write 3.1 complete COMPLETE
> 5. task#run()
> 1. write() AUTOBLOCK
> 2. write complete COMPLETE
> 3. write() AUTOBLOCK
> 4. write complete COMPLETE
> 5. ....
>
> Async write that starts a thread to do an autoblocking write that happens
> before async write completes
>
> 1. getOutputStream() BLOCKING
> 2. setWriteListener() READY->owp
> 3. onWritePossible()
> 1. write() PENDING
> 2. AsyncContext#start(task)
> 4. task#run()
> 1. write() AUTOBLOCK
> 2. write 3.1 complete AUTOBLOCK
> 3. write 4.1 complete COMPLETE
> 4. write() AUTOBLOCK
> 5. write 4.4 complete COMPLETE
> 6. ....
>
> and finally an autoblocking thread can switch back to async:
>
> 1. task#run()
> 1. write() AUTOBLOCK
> 2. write 3.1 complete AUTOBLOCK
> 3. write 4.1 complete COMPLETE
> 4. write() AUTOBLOCK
> 5. write 4.4 complete COMPLETE
> 6. isReady() READY:true
> 7. write() PENDING
> 8. write 4.7 complete COMPLETE
> 9. isReady() READY:true
> 10. write() PENDING
> 11. isReady() UNREADY:false
> 2. write 1.10 completes READY->owp
> 3. onWritePossible()
> 1. write() PENDING
> 2. isReady UNREADY:false
> 4. ...
>
>
> If we don't want autoblocking, then I just think we throw ISE instead of
> entering AUTOBLOCK state
> Does this look similar enough to what others are doing? Is it
> worthwhile to put something like this (perhaps state diagram) in the MR?
>
> cheers
>
>
>
>
>
> On 20 May 2013 14:35, Greg Wilkins <gregw_at_intalio.com> wrote:
>
>>
>> On 17 May 2013 19:25, Rémy Maucherat <rmaucher_at_redhat.com> wrote:
>>
>>> Looking at some of the more creative uses of the API, like websockets,
>>> Mark found some issues and there has been a discussion about having
>>> concurrent read/write, or autoblocking (both should allow implementing
>>> websockets, but experimentation is ongoing).
>>
>>
>> I've read that thread lots of interesting points raised but no clear
>> conclusion. This is a really a significant part of any implementation, so
>> I think it is very important that we clarify what is allowable ASAP.
>>
>> Currently as the spec is written, it implies that only a single thread
>> can be in either a servlet dispatch, a read callback or a write callback.
>> Another way of saying that is that once asynchronous IO is initiated, the
>> container expects that the application will not block in servlet dispatch
>> or any read/write callbacks, as to do so will prevent other calls being
>> made.
>>
>> As Mark says, this is a limitation that will prevent things like
>> simulating blocking APIs with the async IO or even mixing blocking input
>> with async output. However, the AsyncContext#start(Runnable) method
>> is available and we could always say that an application that wishes to
>> block should use that API to access a different thread. So for example
>> the JSR356 implementation should not callback websocket methods directly
>> from IO callbacks nor servlet dispatch as those callbacks could use a
>> blocking websocket API. Instead the JSR356 implementation should use
>> AsyncContext#start(Runnable) to access other threads to do those callbacks,
>> which are free to block.
>>
>> We could say that we will move this thread dispatch mechanism to the
>> container so that applications need not have to deal with dispatching
>> threads themselves, but that would mean that all IO callbacks would have to
>> be dispatched with a thread that we can allow to block. That means that
>> we pay a price even if we don't have any blocking in our callbacks. So
>> I think I prefer that we stick with how the spec is written and only allow
>> a single thread. We just need to be more explicit in a MR that the
>> callbacks cannot block and should use AsyncContext#start if they have
>> might.
>>
>> I also think that this means that the initial callbacks to
>> onWritePossible onReadPossible should not happen until after the thread
>> dispatched to the servlet returns.
>>
>> cheers
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>>
>> --
>> Greg Wilkins <gregw_at_intalio.com>
>> http://www.webtide.com
>> Developer advice and support from the Jetty & CometD experts.
>> Intalio, the modern way to build business applications.
>>
>
>
>
> --
> Greg Wilkins <gregw_at_intalio.com>
> http://www.webtide.com
> Developer advice and support from the Jetty & CometD experts.
> Intalio, the modern way to build business applications.
>



-- 
Greg Wilkins <gregw_at_intalio.com>
http://www.webtide.com
Developer advice and support from the Jetty & CometD experts.
Intalio, the modern way to build business applications.