Hi,

On Thu, Jun 9, 2016 at 3:20 PM, Pavel Rappo <pavel.ra...@oracle.com> wrote:
> Yes, you are correct. Here are some reasons behind this decision:
>
> 1. This is more friendly. One doesn't *have to* carefully build a tree of CS
> dependencies.

This is a bit weak.
On the other hand is less "friendly" because it allows application to
blow up the heap, and discover that only in production.

> 2. "One outstanding write" works perfect only in one-by-one mode:
>
>    webSocket.sendX(message).thenRun(() -> webSocket.request(1))
>
> Though it might look good for a conference slide, as soon as we decide to use
> some other strategy, e.g. having a window akin to one used in this example in
> java.util.concurrent.Flow:
>
> *   public void onSubscribe(Subscription subscription) {
> *     long initialRequestSize = bufferSize;
> *     count = bufferSize - bufferSize / 2; // re-request when half consumed
> *     (this.subscription = subscription).request(initialRequestSize);
> *   }
> *   public void onNext(T item) {
> *     if (--count <= 0)
> *       subscription.request(count = bufferSize - bufferSize / 2);
> *     consumer.accept(item);
> *   }
>
> it becomes a noticeably more difficult to maintain a strict non-overlapping
> order. A user will *have to* have their own queue of last `bufferSize` CFs.

This contradicts what you say down in bullet 4.
An application that calls request(n) where n > 1 should be prepared to
handle n items, either by buffering or by some other gathering
mechanism.
Such application may have a queue of CFs or a queue of items, but the
complexity is identical.

> 3. It's good for implementations. A performance obsessed implementation might
> decide to group several writes into one gathering write on the lowe level,
> squeezing extra microseconds out of latency and reducing the number of calls 
> to
> OS.

This may be true, but it will need to be measured.
It may well be that the additional queueing you have to do internally
(with possible additional allocation) obliterates the gains you have
with gathered writes.
And WebSocket being about low latency, you may not want to contend a
lock on a queue to add/gather items to write.

> 4. I don't think it somehow conflicts with the back-pressure we have in the 
> API.
> After all, it's up to a user how many outstanding writes they want to have. 
> The
> implementation controls *the incoming flow*, not allowing unrequested messages
> to be read/cached/stored. Well, it's a user's responsibility to keep an eye on
> their outgoing queue to not allow it to overflow. After all, the user is
> provided with everything to do this in a non-blocking async fashion!
>
> I would appreciate to hear from you on this.

With this bullet you are basically saying that it's applications'
responsibility to handle A) non-completed sequential writes and B)
concurrent writes.
If this holds, then an "at most one outstanding write" is as good as
any other policy, probably even better because the application will
not depend on some implementation decisions that may later change.

In summary, none of the reasons you listed above is valid for me to
justify this change.

Having said that, I think this is more an implementation problem than
an API problem.
The API supports both a model with an "at most one outstanding write"
as well as other models, such as:

* allow only 1 outstanding write from the same thread, but allow
concurrent writes
* allow a bounded number of outstanding writes from any thread
* allow an unbounded number of outstanding writes from any thread

FYI, we had this problem in javax.websocket, where Jetty chose one
policy, but Tomcat another.
Wrote a chat application using standard APIs against Jetty, worked
fine; deployed in Tomcat failed.
This resulted in changing the application to assume the minimum
behavior (i.e. at most 1 outstanding write).

If, for any reason, you have to change the implementation and go back
to an at most 1 outstanding write model, you will break all
applications out there.

Again FYI, API design is not only about the signature of the API, but
also about their semantic.
In Servlet 3.1 the semantic of
Servlet[Input|Output]Stream.[read|write]() was changed (from blocking
to non-blocking), despite no signature changes in the API.
This resulted in breaking all Servlet Filters implementation that were
wrapping the request or the response to process the content.

I think that having a stricter semantic such as at most 1 outstanding
write helps since applications will know exactly what to expect and
will be forced to think about handling non-completed sequential writes
and concurrent writes, and do that properly, and be in full control.

A wider semantic of unbounded outstanding writes may lead to write
applications that don't care about non-completed sequential writes and
concurrent writes, that may possibly blow up the heap in production.
The fix is the same as above, write the application thinking this
properly, but now there is a double work: the application queueing,
and the implementation queueing.

Alternatively, you can make this semantic explicitly configurable in
the Builder ?

-- 
Simone Bordet
http://bordet.blogspot.com
---
Finally, no matter how good the architecture and design are,
to deliver bug-free software with optimal performance and reliability,
the implementation technique must be flawless.   Victoria Livschitz

Reply via email to