You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by Christophe Pierret <cp...@sparus-software.com> on 2007/01/23 13:21:15 UTC

Comet API and InputStream.available()

I suggest a minor update to the behaviour exhibited by the CoyoteInputStream.available() method when called from inside a Comet.READ event.
Instead of returning 0 on first call (before trying to read), available() should return 1 when initially called, as it seems guaranteed that reading one byte won't block.  It is still consistent with InputStream.available() documentation :
*  Returns the number of bytes that can be read (or skipped over) from this input stream without blocking by the next caller of a method for this input stream.

When first read occurs, it should then return the real available bytes.

I had to implement this behaviour myself when using Comet API in my product, since I already had a working implementation for a fragmented packet reader based on ByteBuffer (done for NIO kind of IO). 
In this case available() would return byteBuffer.limit() - byteBuffer.position().

Please have a look at code fragment at the end of mail to get the idea.
I call InputStreamByteBuffer.setInputStream() only once for each Comet.READ event.
Since I cannot afford to do a blocking read, I loop while( (avail= myInputStreamByteBuffer.available())==0 ) 
and read inside the loop until (avail - count_bytes_read) ==0 ...
My InputStream adapter code is a bit too much convoluted for my taste, but it currently works with Comet/CoyoteInputStream.

I think that implementing this kind of behaviour in Tomcat in the InputStream returned by HttpServletRequest.getInputStream() would be a good thing.
It could be done either by writing a wrapper around CoyoteInputStream or by adding some extra (optional) behaviour to CoyoteInputStream activated only when inside a Comet read, or by doing it at the InputBuffer level.

What do you think of this idea ?
If you agree, I volunteer to test the updated code (or even propose a patch for it).

Here is the code fragment excerpted from my Inputstream adaptator:
public static class InputStreamByteBuffer implements IByteBuffer
    {
        private InputStream inputStream;
        private boolean fixTomcatAvailable;
        InputStreamByteBuffer(InputStream is)
        {
            this.inputStream = is;
            fixTomcatAvailable = true;
        }

        public InputStream getInputStream()
        {
            return inputStream;
        }

        public void setInputStream(InputStream inputStream)
        {
            fixTomcatAvailable = true;
            this.inputStream = inputStream;
        }

        public int available() throws NPIOException
        {
            try
            {
                int avail = inputStream.available();
                if (avail==0 && fixTomcatAvailable)
                {
                    return 1; 
                }
                return avail;
            }
            catch (IOException se)
            {
                NPIOException.rethrow(se);
            }
            return 0; // never reached
        }
        public byte get() throws NPIOException
        {
            if (fixTomcatAvailable) fixTomcatAvailable = false;
            return (byte)Serializer.readByte(inputStream);
        }
        public void get(byte[] dst, int offset, int length) throws NPIOException
        {
            if (fixTomcatAvailable) fixTomcatAvailable = false;
            try
            {
            	inputStream.read(dst,offset,length);
            }
            catch (IOException se)
            {
                NPIOException.rethrow(se);
            }
        }
    } 
public interface IByteBuffer
    {
        int available() throws NPIOException;
        byte get() throws NPIOException;
        void get(byte[] dst, int offset, int length) throws NPIOException;
    }

Sorry, it was a bit long, but shorter would not have been quite clear... Even if I don't think this is very clear yet ;-)
Christophe Pierret


-----Message d'origine-----
De : Remy Maucherat [mailto:remm@apache.org] 
Envoyé : lundi 22 janvier 2007 20:37
À : Tomcat Developers List
Objet : Re: Congratulations to tomcat developers for the Comet API

Christophe Pierret wrote:
> I only had to port the patches for
> http://issues.apache.org/bugzilla/show_bug.cgi?id=40960
> and
> http://issues.apache.org/bugzilla/show_bug.cgi?id=37869

These two patches have been merged in HEAD.

> Feedback on the Comet API:
> - There may be some ways to improve the documentation of the API: from 
> what I saw (I got caught by this one :-), it seems that one need to 
> call
> CometEvent.close() before throwing an exception in READ events or the 
> event keeps coming back forever.  I could not find a reference to this 
> behaviour in documentation.

Throw an exception like what ? If an exception is thrown by something in the event method, it should close the connection with an error without further problems (CoyoteAdapter.event will return false to the connector's event method, which does return a code asking for closing the socket - and more importantly, doesn't put it back in the poller). 
CometEvent.close() doesn't do much, so I don't understand how it can cause a different behavior.

> - Is there a rationale for receiving READ events when 
> request.getInputStream().available()==0  ?

There's a reason: the actual read will be done on the socket when you read on the Java input stream, so it's normal to have available == 0. 
The event guarantees that the blocking read will not block. Filip suggested having the read done before calling event, but I thought it added complexity.

Rémy

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org For additional commands, e-mail: dev-help@tomcat.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: Comet API and InputStream.available()

Posted by Filip Hanik - Dev Lists <de...@hanik.com>.
In most cases, almost all cases there will be data to be read when a 
READ event occurs.
An example where there is none, is when we recieve a request, and there 
is no data in the body yet.
as we parse the GET request, we invoke READ so that you can read any 
client data, if any.
And that is the case when it returns 0, a fully valid use case.

Filip

Christophe Pierret wrote:
> OK, I understand your rationale.
> It is a tough job to retrofit asynchronous IOs in a synchronous framework like the "servlet specification" and you did great given the constraints !
> Continue the good job...
>   Christophe
>
>
> -----Message d'origine-----
> De : Remy Maucherat [mailto:remm@apache.org] 
> Envoyé : mercredi 24 janvier 2007 14:48
> À : Tomcat Developers List
> Objet : Re: Comet API and InputStream.available()
>
> Christophe Pierret wrote:
>   
>> Here is what I understood:
>>
>> Assumption #1: ================ When you receive a 
>> CometEvent.EventType.READ event, you can always read() at least one 
>> byte from the request's InputStream without blocking.
>>
>> Is this correct ? If not correct, then this mail goes directly to 
>> trash (and sorry for the inconvenience...)
>>     
>
> It's not 100% correct: you may do one blocking read without waiting for client input (as the poller signaled data was available). However, the InputStream that is given to you in the Servlet is a buffered stream that has no relation with the socket. I don't like the idea of returning "1", which is a hack. The only non hack way of doing it is to do the read somewhere (in CometAdapter.event) to fill the buffer of the input stream before calling the Servlet.
>
> At the moment, I use this read method (which is compatible with both behaviors of available()) in the chat servlet from the examples:
>          do {
>              int n = is.read(buf);
>              if (n > 0) {
>                  log("Read " + n + " bytes: " + new String(buf, 0, n)
>                          + " for session: " + request.getSession(true).getId());
>              } else if (n < 0) {
>                  // Error: do cleanup
>                  event.close();
>                  return;
>              }
>          } while (is.available() > 0);
>
> Rémy
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org For additional commands, e-mail: dev-help@tomcat.apache.org
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: dev-help@tomcat.apache.org
>
>
>
>   


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


RE: Comet API and InputStream.available()

Posted by Christophe Pierret <cp...@sparus-software.com>.
OK, I understand your rationale.
It is a tough job to retrofit asynchronous IOs in a synchronous framework like the "servlet specification" and you did great given the constraints !
Continue the good job...
  Christophe


-----Message d'origine-----
De : Remy Maucherat [mailto:remm@apache.org] 
Envoyé : mercredi 24 janvier 2007 14:48
À : Tomcat Developers List
Objet : Re: Comet API and InputStream.available()

Christophe Pierret wrote:
> Here is what I understood:
> 
> Assumption #1: ================ When you receive a 
> CometEvent.EventType.READ event, you can always read() at least one 
> byte from the request's InputStream without blocking.
> 
> Is this correct ? If not correct, then this mail goes directly to 
> trash (and sorry for the inconvenience...)

It's not 100% correct: you may do one blocking read without waiting for client input (as the poller signaled data was available). However, the InputStream that is given to you in the Servlet is a buffered stream that has no relation with the socket. I don't like the idea of returning "1", which is a hack. The only non hack way of doing it is to do the read somewhere (in CometAdapter.event) to fill the buffer of the input stream before calling the Servlet.

At the moment, I use this read method (which is compatible with both behaviors of available()) in the chat servlet from the examples:
         do {
             int n = is.read(buf);
             if (n > 0) {
                 log("Read " + n + " bytes: " + new String(buf, 0, n)
                         + " for session: " + request.getSession(true).getId());
             } else if (n < 0) {
                 // Error: do cleanup
                 event.close();
                 return;
             }
         } while (is.available() > 0);

Rémy


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org For additional commands, e-mail: dev-help@tomcat.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: Comet API and InputStream.available()

Posted by Remy Maucherat <re...@apache.org>.
Christophe Pierret wrote:
> Here is what I understood:
> 
> Assumption #1: ================ When you receive a
> CometEvent.EventType.READ event, you can always read() at least one
> byte from the request's InputStream without blocking.
> 
> Is this correct ? If not correct, then this mail goes directly to
> trash (and sorry for the inconvenience...)

It's not 100% correct: you may do one blocking read without waiting for 
client input (as the poller signaled data was available). However, the 
InputStream that is given to you in the Servlet is a buffered stream 
that has no relation with the socket. I don't like the idea of returning 
"1", which is a hack. The only non hack way of doing it is to do the 
read somewhere (in CometAdapter.event) to fill the buffer of the input 
stream before calling the Servlet.

At the moment, I use this read method (which is compatible with both 
behaviors of available()) in the chat servlet from the examples:
         do {
             int n = is.read(buf);
             if (n > 0) {
                 log("Read " + n + " bytes: " + new String(buf, 0, n)
                         + " for session: " + 
request.getSession(true).getId());
             } else if (n < 0) {
                 // Error: do cleanup
                 event.close();
                 return;
             }
         } while (is.available() > 0);

Rémy


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


RE: Comet API and InputStream.available()

Posted by Christophe Pierret <cp...@sparus-software.com>.
Here is what I understood:

Assumption #1:
================
When you receive a CometEvent.EventType.READ event, you can always read() at least one byte from the request's InputStream without blocking.

Is this correct ?
If not correct, then this mail goes directly to trash (and sorry for the inconvenience...)

If correct, then returning 1 for InputStream.available() in this context makes sense and gives an indication that a read can occur without blocking (without the need to convey the same information by a side-channel).
For reference: http://java.sun.com/j2se/1.4.2/docs/api/java/io/InputStream.html for InputStream.available() method contract with the caller. 

Note that available() returning 0 makes sense also. In this context, it means "I cannot guarantee that you can read without blocking".
But returning 0 will force the Comet API user to use the kind of hack I used, e.g.: I have read in the Comet API doc that I can read at least one byte without blocking and I will use another channel to convey information about it.
For example: 
public void doOnlyNonBlockingReads(InputStream is, boolean canReadOneByteEvenIfAvailableReturns0)

The fact that data is not yet read "under the hood" is not an issue as long as Assumption#1 is correct, you can read one byte of data without blocking, when you decide to call one of the read() methods.

I believe that when making a blocking read in a thread would be a critical issue, one should never call InputStream.read() methods if InputStream.available() returns 0 ... You simply don't have the guarantee that it won't block.

If you don't think that returning 1 for available() in the "CometEvent.EventType.READ event case" is a good thing, then 
I can live with a hack in my code ;^) 
I just believe this would make the Comet API better for others and easier to use. 
Reading the data on socket before going into the READ event could also be a solution to this kind of issue but raises other kind of problems.

Christophe

-----Message d'origine-----
De : Filip Hanik - Dev Lists [mailto:devlists@hanik.com] 
Envoyé : mardi 23 janvier 2007 18:14
À : Tomcat Developers List
Objet : Re: Comet API and InputStream.available()

ok, I'm confused, why would available return 1 when there is no data to be read?
the values for available are
 >0 data to be read
0 no data to be read
-1 end of stream reached

I don't think we have -1 in our values, as that would only happen if the client closed the connection, and at that point we will probably call another event, although initially I know the NIO connector calls read with 0 data to be read

Filip

Christophe Pierret wrote:
> I suggest a minor update to the behaviour exhibited by the CoyoteInputStream.available() method when called from inside a Comet.READ event.
> Instead of returning 0 on first call (before trying to read), available() should return 1 when initially called, as it seems guaranteed that reading one byte won't block.  It is still consistent with InputStream.available() documentation :
> *  Returns the number of bytes that can be read (or skipped over) from this input stream without blocking by the next caller of a method for this input stream.
>
> When first read occurs, it should then return the real available bytes.
>
> I had to implement this behaviour myself when using Comet API in my product, since I already had a working implementation for a fragmented packet reader based on ByteBuffer (done for NIO kind of IO). 
> In this case available() would return byteBuffer.limit() - byteBuffer.position().
>
> Please have a look at code fragment at the end of mail to get the idea.
> I call InputStreamByteBuffer.setInputStream() only once for each Comet.READ event.
> Since I cannot afford to do a blocking read, I loop while( (avail= 
> myInputStreamByteBuffer.available())==0 ) and read inside the loop until (avail - count_bytes_read) ==0 ...
> My InputStream adapter code is a bit too much convoluted for my taste, but it currently works with Comet/CoyoteInputStream.
>
> I think that implementing this kind of behaviour in Tomcat in the InputStream returned by HttpServletRequest.getInputStream() would be a good thing.
> It could be done either by writing a wrapper around CoyoteInputStream or by adding some extra (optional) behaviour to CoyoteInputStream activated only when inside a Comet read, or by doing it at the InputBuffer level.
>
> What do you think of this idea ?
> If you agree, I volunteer to test the updated code (or even propose a patch for it).
>
> Here is the code fragment excerpted from my Inputstream adaptator:
> public static class InputStreamByteBuffer implements IByteBuffer
>     {
>         private InputStream inputStream;
>         private boolean fixTomcatAvailable;
>         InputStreamByteBuffer(InputStream is)
>         {
>             this.inputStream = is;
>             fixTomcatAvailable = true;
>         }
>
>         public InputStream getInputStream()
>         {
>             return inputStream;
>         }
>
>         public void setInputStream(InputStream inputStream)
>         {
>             fixTomcatAvailable = true;
>             this.inputStream = inputStream;
>         }
>
>         public int available() throws NPIOException
>         {
>             try
>             {
>                 int avail = inputStream.available();
>                 if (avail==0 && fixTomcatAvailable)
>                 {
>                     return 1; 
>                 }
>                 return avail;
>             }
>             catch (IOException se)
>             {
>                 NPIOException.rethrow(se);
>             }
>             return 0; // never reached
>         }
>         public byte get() throws NPIOException
>         {
>             if (fixTomcatAvailable) fixTomcatAvailable = false;
>             return (byte)Serializer.readByte(inputStream);
>         }
>         public void get(byte[] dst, int offset, int length) throws NPIOException
>         {
>             if (fixTomcatAvailable) fixTomcatAvailable = false;
>             try
>             {
>             	inputStream.read(dst,offset,length);
>             }
>             catch (IOException se)
>             {
>                 NPIOException.rethrow(se);
>             }
>         }
>     }
> public interface IByteBuffer
>     {
>         int available() throws NPIOException;
>         byte get() throws NPIOException;
>         void get(byte[] dst, int offset, int length) throws NPIOException;
>     }
>
> Sorry, it was a bit long, but shorter would not have been quite 
> clear... Even if I don't think this is very clear yet ;-) Christophe 
> Pierret
>
>
> -----Message d'origine-----
> De : Remy Maucherat [mailto:remm@apache.org] Envoyé : lundi 22 janvier 
> 2007 20:37 À : Tomcat Developers List Objet : Re: Congratulations to 
> tomcat developers for the Comet API
>
> Christophe Pierret wrote:
>   
>> I only had to port the patches for
>> http://issues.apache.org/bugzilla/show_bug.cgi?id=40960
>> and
>> http://issues.apache.org/bugzilla/show_bug.cgi?id=37869
>>     
>
> These two patches have been merged in HEAD.
>
>   
>> Feedback on the Comet API:
>> - There may be some ways to improve the documentation of the API: 
>> from what I saw (I got caught by this one :-), it seems that one need 
>> to call
>> CometEvent.close() before throwing an exception in READ events or the 
>> event keeps coming back forever.  I could not find a reference to 
>> this behaviour in documentation.
>>     
>
> Throw an exception like what ? If an exception is thrown by something in the event method, it should close the connection with an error without further problems (CoyoteAdapter.event will return false to the connector's event method, which does return a code asking for closing the socket - and more importantly, doesn't put it back in the poller). 
> CometEvent.close() doesn't do much, so I don't understand how it can cause a different behavior.
>
>   
>> - Is there a rationale for receiving READ events when 
>> request.getInputStream().available()==0  ?
>>     
>
> There's a reason: the actual read will be done on the socket when you read on the Java input stream, so it's normal to have available == 0. 
> The event guarantees that the blocking read will not block. Filip suggested having the read done before calling event, but I thought it added complexity.
>
> Rémy
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org For 
> additional commands, e-mail: dev-help@tomcat.apache.org
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org For 
> additional commands, e-mail: dev-help@tomcat.apache.org
>
>
>
>   


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org For additional commands, e-mail: dev-help@tomcat.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: Comet API and InputStream.available()

Posted by Filip Hanik - Dev Lists <de...@hanik.com>.
ok, I'm confused, why would available return 1 when there is no data to 
be read?
the values for available are
 >0 data to be read
0 no data to be read
-1 end of stream reached

I don't think we have -1 in our values, as that would only happen if the 
client closed the connection, and at that point we will probably call 
another event, although initially I know the NIO connector calls read 
with 0 data to be read

Filip

Christophe Pierret wrote:
> I suggest a minor update to the behaviour exhibited by the CoyoteInputStream.available() method when called from inside a Comet.READ event.
> Instead of returning 0 on first call (before trying to read), available() should return 1 when initially called, as it seems guaranteed that reading one byte won't block.  It is still consistent with InputStream.available() documentation :
> *  Returns the number of bytes that can be read (or skipped over) from this input stream without blocking by the next caller of a method for this input stream.
>
> When first read occurs, it should then return the real available bytes.
>
> I had to implement this behaviour myself when using Comet API in my product, since I already had a working implementation for a fragmented packet reader based on ByteBuffer (done for NIO kind of IO). 
> In this case available() would return byteBuffer.limit() - byteBuffer.position().
>
> Please have a look at code fragment at the end of mail to get the idea.
> I call InputStreamByteBuffer.setInputStream() only once for each Comet.READ event.
> Since I cannot afford to do a blocking read, I loop while( (avail= myInputStreamByteBuffer.available())==0 ) 
> and read inside the loop until (avail - count_bytes_read) ==0 ...
> My InputStream adapter code is a bit too much convoluted for my taste, but it currently works with Comet/CoyoteInputStream.
>
> I think that implementing this kind of behaviour in Tomcat in the InputStream returned by HttpServletRequest.getInputStream() would be a good thing.
> It could be done either by writing a wrapper around CoyoteInputStream or by adding some extra (optional) behaviour to CoyoteInputStream activated only when inside a Comet read, or by doing it at the InputBuffer level.
>
> What do you think of this idea ?
> If you agree, I volunteer to test the updated code (or even propose a patch for it).
>
> Here is the code fragment excerpted from my Inputstream adaptator:
> public static class InputStreamByteBuffer implements IByteBuffer
>     {
>         private InputStream inputStream;
>         private boolean fixTomcatAvailable;
>         InputStreamByteBuffer(InputStream is)
>         {
>             this.inputStream = is;
>             fixTomcatAvailable = true;
>         }
>
>         public InputStream getInputStream()
>         {
>             return inputStream;
>         }
>
>         public void setInputStream(InputStream inputStream)
>         {
>             fixTomcatAvailable = true;
>             this.inputStream = inputStream;
>         }
>
>         public int available() throws NPIOException
>         {
>             try
>             {
>                 int avail = inputStream.available();
>                 if (avail==0 && fixTomcatAvailable)
>                 {
>                     return 1; 
>                 }
>                 return avail;
>             }
>             catch (IOException se)
>             {
>                 NPIOException.rethrow(se);
>             }
>             return 0; // never reached
>         }
>         public byte get() throws NPIOException
>         {
>             if (fixTomcatAvailable) fixTomcatAvailable = false;
>             return (byte)Serializer.readByte(inputStream);
>         }
>         public void get(byte[] dst, int offset, int length) throws NPIOException
>         {
>             if (fixTomcatAvailable) fixTomcatAvailable = false;
>             try
>             {
>             	inputStream.read(dst,offset,length);
>             }
>             catch (IOException se)
>             {
>                 NPIOException.rethrow(se);
>             }
>         }
>     } 
> public interface IByteBuffer
>     {
>         int available() throws NPIOException;
>         byte get() throws NPIOException;
>         void get(byte[] dst, int offset, int length) throws NPIOException;
>     }
>
> Sorry, it was a bit long, but shorter would not have been quite clear... Even if I don't think this is very clear yet ;-)
> Christophe Pierret
>
>
> -----Message d'origine-----
> De : Remy Maucherat [mailto:remm@apache.org] 
> Envoyé : lundi 22 janvier 2007 20:37
> À : Tomcat Developers List
> Objet : Re: Congratulations to tomcat developers for the Comet API
>
> Christophe Pierret wrote:
>   
>> I only had to port the patches for
>> http://issues.apache.org/bugzilla/show_bug.cgi?id=40960
>> and
>> http://issues.apache.org/bugzilla/show_bug.cgi?id=37869
>>     
>
> These two patches have been merged in HEAD.
>
>   
>> Feedback on the Comet API:
>> - There may be some ways to improve the documentation of the API: from 
>> what I saw (I got caught by this one :-), it seems that one need to 
>> call
>> CometEvent.close() before throwing an exception in READ events or the 
>> event keeps coming back forever.  I could not find a reference to this 
>> behaviour in documentation.
>>     
>
> Throw an exception like what ? If an exception is thrown by something in the event method, it should close the connection with an error without further problems (CoyoteAdapter.event will return false to the connector's event method, which does return a code asking for closing the socket - and more importantly, doesn't put it back in the poller). 
> CometEvent.close() doesn't do much, so I don't understand how it can cause a different behavior.
>
>   
>> - Is there a rationale for receiving READ events when 
>> request.getInputStream().available()==0  ?
>>     
>
> There's a reason: the actual read will be done on the socket when you read on the Java input stream, so it's normal to have available == 0. 
> The event guarantees that the blocking read will not block. Filip suggested having the read done before calling event, but I thought it added complexity.
>
> Rémy
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org For additional commands, e-mail: dev-help@tomcat.apache.org
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: dev-help@tomcat.apache.org
>
>
>
>   


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org