Thread safety and nethod handle chains

John Rose john.r.rose at oracle.com
Fri Aug 19 23:14:00 PDT 2011


On Aug 19, 2011, at 9:15 PM, Mark Roos wrote:

> The end fallback/lookup is synchronized and so there is only one update at a time,  but the 
> new GWT is added to the start of the chain and the second thread is past that point.  So 
> when the second gets to the fallback it still thinks the class is missing so it adds it again. 
> 
> The issue would be that the entry to the target is not synchronized. 
> 
> Does this make sense? 

Although the GWT chain is opaque you can store a summary of what's in it on the MCS, in subclass field(s).

The update might look something like this:

  class MyMCS extends MutableCallSite { ...
    List<Class> casesHandled;
    synchronized MethodHandle ensureCaseIsHandled(Class recv) {
      if (casesHandled.contains(recv))  return;
      setTarget(addCaseToChain(getTarget(), recv));
      casesHandled.add(recv);
      return getTarget();
    }
    Object handleMethodNotFound(Object... args) throws Throwable {
      return ensureCaseIsHandled(args[0].getClass()).invokeWithArguments(args);
    }
  }

Here, the previous state of the MCS.target chain is updated incrementally by pushing new receiver type logic on the head.

Or, the target chain could be completely regenerated each time the MCS state changes:

    synchronized MethodHandle ensureCaseIsHandled(Class recv) {
      if (casesHandled.contains(recv))  return;
      casesHandled.add(recv);
      optimizeCaseOrdering(casesHandled);
      setTarget(generateNewDispatchTree(casesHandled));
      return getTarget();
    }

Of course, the metadata used to regenerate the MH chain will probably be more complex than a simple list of receiver classes.  My point is that it can all be managed under a lock, and then the results can be digested down to an immutable, executable MH chain, which can then be read outside of the lock.

Also, new logic can be injected incrementally into the end of an MH chain, if there is an MCS hooked in there.
In this example, the last fallback is always the dynamicInvoker of a fresh MCS, pointed to by the head MCS.

    MutableCallSite tail = new MutableCallSite(this#methodNotFound);
    synchronized MethodHandle ensureCaseIsHandled(Class recv) {
      if (casesHandled.contains(recv))  return;
      casesHandled.add(recv);
      MutableCallSite newTail = new MutableCallSite(tail.getTarget());
      tail.setTarget(generateNewCase(recv, newTail.dynamicInvoker()));
      tail = newTail;
      return getTarget();
    }

Actually, in that last case, the head CS could be another sort of CS, not just a MCS.  But if your language allows radical scheme changes, or if you just want to be able to rebuild GWT trees, having the head be an MCS means you can always tidy things up again.

In this last case, we assume that the overhead of running through all the extra layers of MCS is not too expensive.  With Christian's latest no-polling change, that will be true, generally.

Best wishes,
-- John



More information about the mlvm-dev mailing list