Ping and Pong can interleave between frames (if a large message is being broken up into frames that is).
Ping and Pong goto the head of the frame queue (post-extension processing) in a manner of speaking. Close should occur after the actively being sent message (aka once a fin==true is encountered in the frame queue) On Sat, Jun 11, 2016 at 4:20 AM, Pavel Rappo <pavel.ra...@oracle.com> wrote: > Simone, > > What's your opinion on how send(Ping|Pong|Close) should work? I mean with > "one > outstanding send" policy you're advocating for, should all types of > messages > sent by an app honor this policy? > > If we allow Pings, unsolicited Pongs and Close messages to jump ahead of > the > "queue" and define this as an implementation specific detail, then we > might run > into one of these traps you've mentioned. When the same application might > work > pretty differently against different implementations. > > Should the application be in charge of chopping outgoing messages in > chunks in > order to provide better responsiveness when needed? > > Thanks, > -Pavel > > > On 10 Jun 2016, at 11:09, Simone Bordet <simone.bor...@gmail.com> wrote: > > > > 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 > >