Last active
August 29, 2015 14:11
-
-
Save puredanger/06008478ae7beeaced82 to your computer and use it in GitHub Desktop.
MultiFn.findAndCacheBestMethod()
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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