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
>
>

Reply via email to