jsr356-experts@websocket-spec.java.net

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

From: Joe Walnes <joe_at_walnes.com>
Date: Tue, 26 Feb 2013 14:59:00 -0600

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