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