Skip to content

Instantly share code, notes, and snippets.

@puredanger
Last active August 29, 2015 14:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save puredanger/06008478ae7beeaced82 to your computer and use it in GitHub Desktop.
Save puredanger/06008478ae7beeaced82 to your computer and use it in GitHub Desktop.
MultiFn.findAndCacheBestMethod()
private IFn findAndCacheBestMethod(Object dispatchVal) {
rw.readLock().lock();
Object bestValue; // remember the best match IFn
IPersistentMap mt = methodTable;
IPersistentMap pt = preferTable;
Object ch = cachedHierarchy;
try
{
Map.Entry bestEntry = null;
for(Object o : getMethodTable())
{
Map.Entry e = (Map.Entry) o;
if(isA(dispatchVal, e.getKey()))
{
if(bestEntry == null || dominates(e.getKey(), bestEntry.getKey()))
bestEntry = e;
if(!dominates(bestEntry.getKey(), e.getKey()))
throw new IllegalArgumentException(
String.format(
"Multiple methods in multimethod '%s' match dispatch value: %s -> %s and %s, and neither is preferred",
name, dispatchVal, e.getKey(), bestEntry.getKey()));
}
}
if(bestEntry == null)
{
// instead of returning null, look up the default dispatch IFn,
// based on the default dispatch value (which is usually :default)
bestValue = methodTable.valAt(defaultDispatchVal);
if(bestValue == null)
// if there is no default dispatch value, then we do really need to return null
return null;
}
else
bestValue = bestEntry.getValue();
}
finally
{
rw.readLock().unlock();
}
//ensure basis has stayed stable throughout, else redo
rw.writeLock().lock();
try
{
if( mt == methodTable &&
pt == preferTable &&
ch == cachedHierarchy &&
cachedHierarchy == hierarchy.deref())
{
//place in cache
methodCache = methodCache.assoc(dispatchVal, bestValue);
return (IFn) bestValue;
}
else
{
resetCache();
return findAndCacheBestMethod(dispatchVal);
}
}
finally
{
rw.writeLock().unlock();
}
}
// annotated version of part of MultiFn (as of Clojure 1.6.0)
final ReentrantReadWriteLock rw; // Protects the volatile state
volatile IPersistentMap methodTable; // dispatch value -> IFn (altered by new defmethod)
volatile IPersistentMap preferTable; // dispatch value -> IPersistentSet of dispatch value
volatile IPersistentMap methodCache; // dispatch value -> IFn
volatile Object cachedHierarchy;
// this method is called with the dispatchVal returned from the dispatch function and
// is responsible for finding the correct function to call and caching that decision.
private IFn findAndCacheBestMethod(Object dispatchVal) {
rw.readLock().lock(); // get (concurrent) read lock on the state of the MultiFn
Map.Entry bestEntry; // track best entry found so far
// Remember the instances we see at start - we'll check these later to detect
// that a new method has been added
IPersistentMap mt = methodTable;
IPersistentMap pt = preferTable;
Object ch = cachedHierarchy;
try
{
bestEntry = null;
for(Object o : getMethodTable()) // walk through every entry in the method table
{
Map.Entry e = (Map.Entry) o;
if(isA(dispatchVal, e.getKey())) // is this a possible match for dispatchVal?
{
// yes, this is a possibility
// if we don't have a match yet or the new one dominates, keep it
if(bestEntry == null || dominates(e.getKey(), bestEntry.getKey()))
bestEntry = e;
// if there are multiple matches and none dominate, this is an error
if(!dominates(bestEntry.getKey(), e.getKey()))
throw new IllegalArgumentException(
String.format(
"Multiple methods in multimethod '%s' match dispatch value: %s -> %s and %s, and neither is preferred",
name, dispatchVal, e.getKey(), bestEntry.getKey()));
}
}
// if no matching entry was found, return null indicating the :default
// THIS IS THE BUG - we have not cached the result of this process so we must do it every time!!
if(bestEntry == null)
return null;
}
finally // undo read lock in any condition
{
rw.readLock().unlock();
}
//ensure basis has stayed stable throughout, else redo
rw.writeLock().lock(); // now take *exclusive* write lock
try
{
// check whether the methodTable/preferTable/cachedHierarchy are the same as when we
// started this decision process. If so, we can proceed. Else we have to dump the cache and start over.
if( mt == methodTable &&
pt == preferTable &&
ch == cachedHierarchy &&
cachedHierarchy == hierarchy.deref())
{
// place decision in cache and return the IFn we chose
methodCache = methodCache.assoc(dispatchVal, bestEntry.getValue());
return (IFn) bestEntry.getValue();
}
else
{
resetCache();
return findAndCacheBestMethod(dispatchVal);
}
}
finally
{
rw.writeLock().unlock(); // release the exclusive write lock
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment