> On 12 Feb 2018, at 19:29, Chuck Davis <cjgun...@gmail.com> wrote:
> 
> <snip>
> 
> There is never a way to tell how much data may be
> returned.
> 

Reactive Streams speak in terms of numbers of invocations to the onNext method.
Not the amount of data. (Please disregard if that's not what you meant.)

> <snip>
> 
> I am under the impression that this new API requires the developer to
> buffer the messages until all the data is complete. As I recall (it's
> been a few months since I implemented) decoders handled that in the
> old API and if I am correct that is going to put a lot more work on
> the plate of developers.  If my understanding is correct I'd suggest
> listeners should do the buffering and deliver a complete message.  If
> it's written once in the implementation it would save thousands of
> developer hours recreating the same wheel.  At least this approach
> makes sense in my use case.  Partial pieces of serialized objects are
> useless.
> 

Different developers have different requirements. Some would benefit from having
the ability to receive partial messages. If this API only provided whole message
receiving, it would not be possible to satisfy those developers.
If in your case it makes no sense to use partial messages, you can still use the
API. It will only require some extra work though. In the simplest case the
amount of work is not that big really:

   @Override
   public CompletionStage<?> onText(WebSocket webSocket,
                                    CharSequence message,
                                    WebSocket.MessagePart part) {
       webSocket.request(1);
       String str = null;
       switch (part) {
           case FIRST:
           case PART:
               text.append(message);
               return null;
           case LAST:
               text.append(message);
               str = text.toString();
               text.setLength(0);
               break;
           case WHOLE:
               str = message.toString();
               break;
       }
       processWholeText(str);
       return null;
   }

   private void processWholeText(String string) {
       // -- your code here --
   }

However, with this approach you will need to go the extra mile if you ever need
to talk to WebSocket.request outside of the context of the associated listener.
Because the WebSocket client will continue to speak in terms of the number of
unspecified (could be whole, could be partial) messages.

We might provide this "buffering" ability internally in WebSocket in the future.
One option would be to add an intermediate method to the WebSocket.Builder such
that this method would give an extra guarantee for the attached listener:
 
   Builder receiveWholeMessages(boolean whole)

Another option would be to provide a slightly different listener that would
reflect this behaviour in a more type-safe manner (i.e. the listener won't have
those extra MessagePart arguments in the onText and onBinary methods):

   CompletableFuture<WebSocket> buildAsync(URI uri, WholeListener listener)

And these are probably not the only possible options.

It does however seem that optimization-wise there is not much to be gained from
doing this. As different parts of a whole message (i.e. payloads of WebSocket
frames) do not occupy contiguous parts of the underlying memory. In order to be
concatenated, payload pieces would need to be cut out of their frames first.
Which means copying. In this context it might have been better if we decided to
use ByteBuffer[] instead of ByteBuffer in the first place. But that seemed like
an overkill back then.

> 
> Can you expound a little more on this:  "Finally, each of the
> listener's methods asks the user to signal when the user has processed
> the message it bears [2]."  All I see is that the method returns a
> CompletionStage which triggers a procession of CompletionStage objects
> (not sure yet how this ends except by returning a null?) and I don't
> see in the docs how this signals the listener except that the method
> exits and may be called again.  I'm sorry to be so dense here.
> 

Let me explain this the following way. With WebSocket there is an exchange of
messages. You create a new message for the WebSocket client and tell it to send
the message by calling one of the send methods. You'd probably like to know
though when the WebSocket client has actually processed the message. That's
where a CompletableFuture returned from the method comes in handy. When the
WebSocket client completes this future, you know the message has been processed.

Now consider that when the WebSocket client receives a message it calls one of
the receive methods on you. The WebSocket client needs you to tell it when you
finished processing the message it gave you. You do this by returning a
CompletionStage from the method. Once this stage reaches the WebSocket client,
the client then attaches dependant actions on this stage internally. But that's
details of the implementation, the point is the scenario is really symmetric.

Now, in the common case, when you process the message synchronously (before the
call to the receive method ends) you should return an already completed
CompletionStage. Why? Because by the time the method returns, you will have
processed the message. Since this case will probably be the most common, 
it was designed so that you just return null.

Consequently, you can always return a stage that completes later than when you
processed the message. But you must never return a stage that completes earlier
than that.

Think about it as a trivial protocol for handing over the objects between
asynchronous operations. Who owns the buffer (or the character sequence) and
when.

Hopefully this answers your question.

> 
> Finally, is there a target release to graduate from incubator?  I'm
> kinda stuck until the dust settles.  May it hit jdk10 or 11?
> "Incubating Feature. Will be removed in a future release." sounds
> so.....ominous!
> 
> <snip>
> 

A short answer: JDK 11 
A bit longer one: 
http://mail.openjdk.java.net/pipermail/net-dev/2018-February/011155.html

Thanks,
-Pavel

Reply via email to