Tuesday, February 18, 2014

Re-entrant Threads in a Distributed Cache (Coherence)

There's a fundamental nastiness in making a call to a distributed cache that then accessing the cache in that call.

In Oracle Coherence, if you have something like this:

public class MyEntryProcessor implements Serializable, EntryProcessor { 
.
.
        public Object process(Entry entry) { 
            // this doesn't work...
            NamedCache myCache = CacheFactory.getCache(CACHE_NAME);
.
.

(where CACHE_NAME is the name of the cache against which you run this entry processor) you'll get:

2014-02-18 17:15:17.909/10.736 Oracle Coherence GE 3.7.1.0 (thread=DistributedCache, member=2): Assertion failed: poll() is a blocking call and cannot be called on the Service thread

(Note: if CACHE_NAME is different to the cache against which we run this entry processor, we still get this problem.).

This is because we "are making a re-entrant call back into a cache service from the service thread or worker thread of a cache service. This is a bad thing to do as you risk deadlocking your cluster by consuming all of the threads in the service." [1]

One way around it is to enforce an access order. "As with traditional locking, using ordered access (for example, service A can call service B, but not vice versa) can help." [2]

Another is the slightly hacky:

        public Object process(Entry entry) { 
            BackingMapManagerContext    context     = getContext(entry); 
            Map<Binary, Binary>         myCache     = getBackingMap(context); 
            
            Binary                      binaryKey   = (Binary) context.getKeyToInternalConverter().convert(myKey); 
            Binary                      binaryValue = (Binary) context.getValueToInternalConverter().convert(myValue); 
            boolean                     wasEmpty    = myCache.get(binaryKey) == null; 
            myCache.put(binaryKey, binaryValue); 
            
            return wasEmpty; 
        } 

        protected BackingMapManagerContext getContext(Entry entry) {
            BinaryEntry binaryEntry = (BinaryEntry) entry;
            BackingMapManagerContext context = binaryEntry.getContext();
            return context;
        }

        private Map getBackingMap(BackingMapManagerContext context) {
            Map backingMap = context.getBackingMap(CACHE_NAME);
            return backingMap;
        } 

This uses the deprecated BackingMapManagerContext.getBackingMap method but other people seem to have used this approach as well.

[1] Assertion failed: poll() is a blocking call - Oracle Community
[2] Constraints on Re-entrant calls.

No comments:

Post a Comment