RFR JDK-8087113: Websocket API and implementation

Pavel Rappo pavel.rappo at oracle.com
Tue Apr 5 18:27:15 UTC 2016


Hi Roger, thanks for looking into this.

> On 5 Apr 2016, at 17:37, Roger Riggs <Roger.Riggs at oracle.com> wrote:
> 
> It would be helpful if the classnames/filenames reflected the participation in the WebSocket implementation
> to keep them distinct from the HTTP 2.0 implementation in the same directory.
> For example, Writer, Reader, etc.  perhaps a 'Ws' prefix would be sufficient.
> 
> This would be alleviated if websocket was in its in its own package. java.net.ws
> (though I realize that may already have been settled, and I'm sure there are some dependencies
> that would be nice to keep as package private).

We've been through this. WebSocket has a very tight relationship with the new
HttpClient. It's more like an addition to it rather than a standalone API.
Having said this, the consensus was there's little justification for it to be in
its own API package.

That doesn't mean though we can't put the _implementation_ in its own package
inside java.httpclient module. That would make the implementation look a lot
cleaner.

> Logging is sprinkled throughout the APIs of the implementation; it would be cleaner to have
> a shared WS logging class with a static logger similar to the Http Log (or perhaps some sharing and reuse)
> so the logging does not clutter the implementation interfaces.  It would rare that separate logger
> instances are needed.

I tried hard to move higher level logging to a separate decorators, so the
implementation code wouldn't have to bother (see java.net.http.LoggingWebSocketFactory).

Unfortunately, sometimes I have to mix logging code with the payload code in
order to log the actual processing and not just the input parameters.

I agree I should use statically imported log instance everywhere. But at the
same time I would prefer to keep logging calls as they are today. In other
words, directly to the log instance instead of to some facade. I don't see much
difference right now between

    Log.logTrace("cancelling stream: %s\n", e.toString());

and 

    log.log(TRACE, "cancelling stream: %s\n", e.toString());

Sometimes I pass java.lang.System.Logger as a parameter to the code that might
be shared in the future with HttpClient (e.g. java.net.http.Shared, sorry for
the funny naming coincidence in this case).

> Writer.java:
> 
> - line 143:  keep track of the current/next buffer index of the first non-empty buffer; instead of replacing with NULL_BYTE_BUFFER;

Thanks.

> a combination of the buffers array index vs length and hasRemaining could eliminate the need to copy the buffers array
> and perform the initialization to determine the total length (though that is informative for logging)
>  - not sure why you are using the 3 arg form of GatheringWriter since the byteBuffers array is exactly the length.

Sorry, I don't think I understand. There's no need to copy. There's a need to
map Shared<ByteBuffer>[] -> ByteBuffer[] in order to provide a correspondence
between buffers and their enclosing containers for the sake of disposing
the buffers that have been fully written from.

> - line 184:   editorial, remove "from" from the logger

Thanks.

> - line 228: can the Throwable be discarded?  Does it contain no information?

I have to think about it. Writer is a Subscriber. So whatever happens in the
corresponding Publisher (MessageSender) should probably be logged by only one of
them. People generally don't like to see echoed stacktraces.  There seems to be
no information for Writer except for there won't be any more onNext.

> Reader.java:
> - line 137:  Why should the reader force a new task?  Should it be left to the subscriber to
>    decide whether its processing is lightweight and can be done immediately or to re-submit to its own executor.

I was thinking about getting Reader back to reading ASAP, and not _potentially_
wasting its time in the subscriber. I might rethink this. It is a decoupling obsession :-)
Thanks for pointing this out.

> java.net.Utils:
> - line 231:  SATURATING_INCREMENT (and its uses) is wasted code; long is not going to roll over.

It's in order to go with the flow :-) I mean Flow/Reactive Streams API. I
believe it makes the implementation more robust. As [1] mentions demand greater
than Long.MAX_VALUE. It's a corner case anyway.

Would you suggest throwing an IAE in case where the implementation detects
overflow?

> - line 236: NULL_BYTE_BUFFER isn't null - prehaps rename to EMPTY_BYTE_BUFFER

Thanks. It's been already mentioned by Andrej Golovnin. The name is bad, sorry
about that (though I referred to the Null Object pattern, rather than to null
literal).

I don't think EMPTY is a perfect name. What is an *empty* ByteBuffer? In my
opinion it's not necessarily the one that has a capacity of 0. It's the one that
reports hasRemaining() == false. I want to reflect both qualities in the
buffer's name. But maybe I'm overcomplicating this naming thing.

Thanks.
--------------------------------------------------------------------------------
[1] https://github.com/reactive-streams/reactive-streams-jvm/#3-subscription-code rule #17




More information about the net-dev mailing list