users@servlet-spec.java.net

[servlet-spec users] [jsr340-experts] Re: Async IO and Upgrade proposal updated

From: Oleksiy Stashok <oleksiy.stashok_at_oracle.com>
Date: Fri, 30 Mar 2012 19:23:34 +0200

> On 30 March 2012 03:27, Rajiv Mordani<rajiv.mordani_at_oracle.com> wrote:
>> Ok we have been discussing this also internally - where we remove the
>> parameter in
>> the canWrite method - so the method signature would be
>>
>> boolean canWrite();
>>
>> and of course I already suggested that we have the write methods return an
>> int which
>> indicates. So we will do that.

Once we don't have integer parameter neither in canWrite nor in
notifyCanWrite, AFAIU we can guarantee that if canWrite() returned true
- next "write" method call (despite the size of the passed data) will
not block.*

*-------------------------------------------------------------------
if (os.canWrite()) {
         os.write(appBuffer); <---- no blocking here even if appBuffer
is huge
}
-------------------------------------------------------------------

It sounds good to me because once we have the appBuffer in memory ready
to be written, it makes no difference (application maintains the
reference or the write queue does) where the buffer reference is kept.

The biggest question for me is if/how/when I can reuse appBuffer again?
If the appBuffer is less than the sevlet buffer, we can just copy it
into the servlet buffer. I don't see any problems with that usecase.
I'm more interested in the usecase where the appBuffer is larger than
the servlet buffer.

Remy proposes to copy the appBuffer remainder, left after our
Channel.write(...) attempt(s), to an internal buffer, so the appBuffer
can be reused right after returning from the "write" call.

-------------------------------------------------------------------
if (os.canWrite()) {
         os.write(appBuffer);
}
<------------- starting here we can reuse appBuffer
-------------------------------------------------------------------


 From usability point of view it sounds great, however it's not
efficient when we deal w/ big appBuffers, especially if we try to write
direct/mapped ByteBuffers.

If we want to avoid buffers copy and still want to reuse appBuffer, the
code may look like:

-------------------------------------------------------------------
if (os.canWrite()) {
         os.write(appBuffer);
}

os.flush(); <---------- flush servlet buffer

if (os.canWrite()) {
     (1) <---------------- starting here we can reuse appBuffer
} else {
         os.notifyCanWrite(new WriteListener() {
                     void onWritePossible() {
                                 (2) <---------------- starting here we
can reuse appBuffer
                     }
         }
}
-------------------------------------------------------------------

So in this case we always have to flush() and then make sure we
canWrite() before reusing appBuffer.

For sure, as we see, from the usability point of view the cost of
copy-free approach is relatively high, but if we deal w/ large
mapped/direct buffers, may be it's worth?

Just an idea, may be it's not good, but anyway:
1) When dealing w/ byte[] (existing OutputStream API) we go w/ Remy's
suggestion, which may involve internal byte[] copying. IMO this approach
is clear and very intuitive to Servlet developers, even though it's not
optimal in some cases.

2) Introduce new method(s) (Servlet)OutputStream.write(ByteBuffer),
which will be implemented in a copy-free fashion. We'll document the
fact, that developer can reuse the ByteBuffer only after making sure
it's not used by container.

Thanks.

WBR,
Alexey.