jsr356-experts@websocket-spec.java.net

[jsr356-experts] Re: [jsr356-users] How to safely handle callbacks from other threads

From: Joe Walnes <joe_at_walnes.com>
Date: Wed, 27 Feb 2013 20:07:17 -0600

I understand that it's late in the day and we want to minimize changes.

A fundamental benefit of WebSockets is that the server can push data to the
client at any time, without it having to be in response to a request, but
our API does not currently allow a safe means to do that. Pretty much all
the example we've talked about have pushed data in response to an
on{open,message,close} event, but never an external event (e.g. timer,
network result, background compute task, JMS message, etc).

To draw a parallel, without a way to schedule work safely, would be like
trying to build multi-threaded Swing applications without
SwingUtilities.invokeLater(). It can be done, but it's very messy and your
chances (ok, my chances) of getting it right are low.

If we cannot address it in the API, can we at least like to come up with
guidelines on how external threads should safely interact with endpoints,
so users don't unknowingly create race conditions. If the user is
responsible for managing locks themselves, how do they determine where the
locks should be?

cheers
-Joe


On Wed, Feb 27, 2013 at 6:49 PM, Danny Coward <danny.coward_at_oracle.com>wrote:

> Hi Joe,
>
> I'm definitely sympathetic with the scenario, and as you point out the
> developer can schedule the 'external' thread by hand, even if its not ideal.
>
> However, I suspect that coming up with a good, general solution to this
> will require quite some thought, and from my perspective we're just not in
> a position to design and implement new mechanisms like this at this late
> point in the spec.
>
> I know we have been accommodating smaller API changes up until this week,
> but they were for pre-existing features like programmatic deployment, and
> relatively well-understood areas like endpoint instance creation. Both of
> which we've had time to chew on a few occasions before. Most of our time
> since the new year has been spent, very profitably I think, on clarifying
> the features we already have.
>
> So I would like to punt on this for this version, and get it on the list
> for the next version.
>
> Thanks,
>
> - Danny
>
>
> On 2/26/13 12:59 PM, Joe Walnes wrote:
>
> In section 5.1.Threading considerations:
>
> "In all cases, the implementation must not invoke an endpoint instance
> with more than one thread per peer at a time. ... This guarantees that a
> websocket endpoint instance is never called by more than one
> container thread at a time per peer."
>
> A scenario I run into a lot is:
> * An endpoint needs to call into another library, which will run something
> in the background on another thread and then run a callback when complete.
> This might be a long running third party library, or maybe a
> java.util.Timer.
> * The callback needs to interact with state belonging to the endpoint
> instance (e.g. send a message, or change an in memory data-structure).
> * Because the callback has not been initiated by the container, we have no
> guarantee that the container is not calling other methods on the endpoint
> at the same time that the callback is invoked. Race conditions ahoy.
>
> It is possible to work around this by implementing locks in the
> endpoint. But this puts lots of responsibility on the end user, is messy
> and very easy to get wrong.
>
> I propose we add a mechanism to allow user code running in other threads
> to schedule some code to be run by a container thread that follows the same
> semantics as the other endpoint methods invoked by the container - i.e.
> only one at a time per instance endpoint.
>
> To do this, we could make Session implement
> java.util.concurrent.Executor. Any Runnable passed to it will be run by the
> container in a safe way.
>
> Example:
>
> class MyEndpoint {
>
> @OnMessage
> public void onMsg(String msg, final Session session) {
>
> Runnable callback = new Runnable() {
> public void run() {
> // 4. ok, we're back on the Endpoint thread now.
> // we can safely use interact with other objects
> session.getRemoteBasic().sendString("Hello! Remember me.");
> }
> }
>
> // 1. schedule something to happen in the future, without blocking.
> new Timer().schedule(new TimerTask() {
> public void run() {
> // 3. sometime later, the Timer library calls this on its
> // own unsafe thread.
> session.execute(callback); // thunk to container thread
> }
> }, 10000);
> // 2. return immediately.
>
> }
>
> }
>
> Thoughts?
>
> -Joe
>
>
>
> --
> <http://www.oracle.com> * Danny Coward *
> Java EE
> Oracle Corporation
>