com.maxifier.mxcache.impl.resource.DependencyTracker Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2014 Maxifier Ltd. All Rights Reserved.
*/
package com.maxifier.mxcache.impl.resource;
import com.maxifier.mxcache.InternalProbeFailedError;
import com.maxifier.mxcache.caches.CleaningNode;
import com.maxifier.mxcache.impl.resource.nodes.ResourceViewable;
import com.maxifier.mxcache.util.HashWeakReference;
import com.maxifier.mxcache.util.TIdentityHashSet;
import gnu.trove.map.hash.TCustomHashMap;
import gnu.trove.strategy.IdentityHashingStrategy;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.*;
/**
* DependencyTracker tracks dependencies between caches.
*
* Each cache has a DependencyNode associated with it. The dependency node may be shared across few cache instances
* (e.g. all instances of certain method share a single dependency node) or created for each cache instance
* individually. DependencyNodes form a directed graph where edge from A to B means that all caches associated with B
* depend on caches associated with A and should be invalidated once any of caches associated with A are invalidated.
*
* When a cached method enters a stack, it does the following:
*
* - gets the top dependency node from {@link #NODE} and adds an edge to dependency graph from its own node to this
* top node;
* - puts a reference to its dependency node to {@link #NODE}.
*
*
* @author Alexander Kochurov ([email protected])
*/
public final class DependencyTracker {
private static final ThreadLocal NODE = new ThreadLocal();
public static final DependencyNode DUMMY_NODE = new DummyDependencyNode("");
public static final DependencyNode PROBE_NODE = new DummyDependencyNode("");
public static final DependencyNode NOCACHE_NODE = new DummyDependencyNode("");
public static final DependencyNode HIDDEN_CALLER_NODE = new HiddenCallerDependencyNode();
private DependencyTracker() {
}
/**
* This method marks current top of stack dependency node as dependant of given node; it doesn't push given node to
* stack. It is equivalent to exit(track(node))
sequence, but works much faster.
*
* @param node dependency node
*/
public static void mark(DependencyNode node) {
if (node != DUMMY_NODE) {
track(node, NODE.get());
}
}
/**
* This method pushes given node to top of stack, and marks current on-stack node as dependant of given node.
* This method returns current stack top; later it should be passed as an argument to
* {@link DependencyTracker#exit(DependencyNode)}.
*
* @param node node to push
* @return previous on-stack dependency node
*/
public static DependencyNode track(DependencyNode node) {
DependencyNode oldNode = NODE.get();
assert oldNode == null || node != null : "Could not reassign node to null";
if (oldNode == PROBE_NODE) {
throw new InternalProbeFailedError();
}
if (node != DUMMY_NODE) {
track(node, oldNode);
NODE.set(node);
} else if (oldNode == null) {
// ???? ???? ???? ??????, ?? ???-???? ???????? ??? ????-?????????
NODE.set(node);
}
return oldNode;
}
private static void track(DependencyNode node, DependencyNode oldNode) {
if (oldNode != null && oldNode != DUMMY_NODE && node != oldNode) {
node.trackDependency(oldNode);
}
}
public static void exit(@Nullable DependencyNode callerNode) {
NODE.set(callerNode);
}
public static DependencyNode get() {
return NODE.get();
}
public static boolean hasUnderlyingNode() {
return NODE.get() != null;
}
public static void deepVisit(Iterable src, DependencyNode.Visitor visitor) {
for (DependencyNode node : src) {
visitor.visit(node);
}
Queue queue = visitor.getQueue();
while (!queue.isEmpty()) {
queue.poll().visitDependantNodes(visitor);
}
}
public static boolean isDummyNode(DependencyNode node) {
return node instanceof DummyDependencyNode;
}
public static boolean isBypassCaches() {
return NOCACHE_NODE.equals(get());
}
public static void deepInvalidate(DependencyNode node) {
deepVisit(Collections.singleton(node), new InvalidateAllVisitor());
}
public static void deepInvalidateWithResourceView(DependencyNode node) {
deepVisit(Collections.singleton(node), new InvalidateChangedVisitor());
}
public static void deepInvalidate(final Collection elements) {
deepInvalidate(new WrapDependencyNode(elements));
}
private static final class DummyDependencyNode implements DependencyNode {
private final Reference selfReference = new HashWeakReference(this);
private final String name;
private DummyDependencyNode(String name) {
this.name = name;
}
@Override
public void visitDependantNodes(Visitor visitor) {
// do nothing, we don't track dependencies for that cache
}
@Override
public void invalidate() {
// do nothing, we don't track dependencies for that cache
}
@Override
public void trackDependency(DependencyNode node) {
// do nothing, we don't track dependencies for that cache
}
@Override
public void addNode(@Nonnull CleaningNode cache) {
// do nothing, we don't track dependencies for that cache
}
@Override
public String toString() {
return name;
}
@Override
public Reference getSelfReference() {
return selfReference;
}
}
private static class HiddenCallerDependencyNode implements DependencyNode {
private final Reference thisReference = new WeakReference(this);
@Override
public Reference getSelfReference() {
return thisReference;
}
@Override
public void visitDependantNodes(Visitor visitor) {
// do nothing
}
@Override
public void invalidate() {
// do nothing
}
@Override
public void trackDependency(DependencyNode node) {
// do nothing
}
@Override
public void addNode(@Nonnull CleaningNode cache) {
// do nothing
}
@Override
public String toString() {
return "HiddenCaller - see MxCache.hideCallerDependencies";
}
}
private static class WrapDependencyNode implements DependencyNode {
private final Collection elements;
public WrapDependencyNode(Collection elements) {
this.elements = elements;
}
@Override
public Reference getSelfReference() {
throw new UnsupportedOperationException();
}
@Override
public void visitDependantNodes(Visitor visitor) {
for (CleaningNode element : elements) {
visitor.visit(element.getDependencyNode());
}
}
@Override
public void trackDependency(DependencyNode node) {
throw new UnsupportedOperationException();
}
@Override
public void invalidate() {
for (CleaningNode element : elements) {
element.invalidate();
}
}
@Override
public void addNode(@Nonnull CleaningNode cache) {
throw new UnsupportedOperationException();
}
}
private static class InvalidateAllVisitor implements DependencyNode.Visitor {
private final Queue queue = new LinkedList();
private final TIdentityHashSet visitedNodes = new TIdentityHashSet();
@Override
public Queue getQueue() {
return queue;
}
@Override
public void visit(DependencyNode node) {
if (visitedNodes.add(node)) {
queue.add(node);
node.invalidate();
}
}
}
private static class InvalidateChangedVisitor implements DependencyNode.Visitor {
private final Queue queue = new LinkedList();
private final TIdentityHashSet visitedNodes = new TIdentityHashSet();
private final Map changedCache = new TCustomHashMap(IdentityHashingStrategy.INSTANCE);
private boolean isChanged(ResourceViewable resourceViewable) {
Boolean res = changedCache.get(resourceViewable);
if (res == null) {
res = resourceViewable.isChanged();
changedCache.put(resourceViewable, res);
}
return res;
}
@Override
public Queue getQueue() {
return queue;
}
@Override
public void visit(DependencyNode node) {
if (visitedNodes.add(node) && (!(node instanceof ResourceViewable) || isChanged((ResourceViewable) node))) {
queue.add(node);
node.invalidate();
}
}
}
}