diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java index 4af7bd0ab3c2..f2a7917e51ac 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/IntervalTreeImpl.java @@ -21,6 +21,7 @@ import com.intellij.util.IncorrectOperationException; import com.intellij.util.Processor; import com.intellij.util.SmartList; import com.intellij.util.WalkingState; +import com.intellij.util.concurrency.AtomicFieldUpdater; import com.intellij.util.containers.ContainerUtil; import gnu.trove.TLongHashSet; import org.jetbrains.annotations.NonNls; @@ -54,9 +55,7 @@ public abstract class IntervalTreeImpl extends RedBla protected int maxEnd; // max of all intervalEnd()s among all children. protected int delta; // delta of startOffset. getStartOffset() = myStartOffset + Sum of deltas up to root - private volatile int modCount; // if it equals to the com.intellij.openapi.editor.impl.RedBlackTree.modCount then deltaUpToRoot can be used, otherwise it is expired - private volatile int deltaUpToRoot; // sum of all deltas up to the root (including this node' delta). Has valid value only if modCount == IntervalTreeImpl.this.modCount - private volatile boolean allDeltasUpAreNull; // true if all deltas up the tree (including this node) are 0. Has valid value only if modCount == IntervalTreeImpl.this.modCount + private volatile long cachedDeltaUpToRoot; // field (packed to long for atomicity) containing deltaUpToRoot, node modCount and allDeltasUpAreNull flag private final IntervalTreeImpl myIntervalTree; public IntervalNode(IntervalTreeImpl intervalTree, @NotNull E key, int start, int end) { @@ -117,7 +116,7 @@ public abstract class IntervalTreeImpl extends RedBla // removes interval and the node, if node became empty // returns true if node was removed - public boolean removeInterval(@NotNull E key) { + private boolean removeInterval(@NotNull E key) { myIntervalTree.checkBelongsToTheTree(key, true); myIntervalTree.assertUnderWriteLock(); for (int i = intervals.size() - 1; i >= 0; i--) { @@ -154,79 +153,81 @@ public abstract class IntervalTreeImpl extends RedBla } protected int computeDeltaUpToRoot() { + return computeDeltaUpToRoot(new NodeCachedOffsets()); + } + protected int computeDeltaUpToRoot(NodeCachedOffsets cached) { restart: while (true) { // have to restart on failure to update cached offsets in case of concurrent modification if (!isValid()) return 0; int treeModCount = myIntervalTree.modCount; - if (modCount == treeModCount) { - return deltaUpToRoot; + unpackCachedValuesTo(cached); + if (cached.modCount == treeModCount) { + return cached.deltaUpToRoot; } - IntervalNode node = this; - IntervalNode treeRoot = myIntervalTree.getRoot(); - if (treeRoot == null) return delta; // someone modified the tree in the meantime - int deltaUp = 0; - boolean allDeltasAreNull = true; - int height = 0; - long path = 0; // path to this node from the root; 0 bit means we choose left subtree, 1 bit means we choose right subtree - while (node != treeRoot) { - if (node.isValid() && node.modCount == treeModCount) { - deltaUp = node.deltaUpToRoot - node.delta; - allDeltasAreNull = node.allDeltasUpAreNull; - break; - } - IntervalNode parent = node.getParent(); - if (parent == null) { - break; // can happen when remove node and explicitly set valid to true (e.g. in RangeMarkerTree) - } - path = (path << 1) | (parent.getLeft() == node ? 0 : 1); - node = parent; - height++; - } - // path to this node fits to long - assert height < 63 : height; + try { + myIntervalTree.l.readLock().lock(); - // cache deltas in every node from the root down this - while (true) { - if (node.isValid()) { - int nodeDelta = node.delta; - deltaUp += nodeDelta; - allDeltasAreNull &= nodeDelta == 0; - if (!setCachedOffsetsAtomically(deltaUp, allDeltasAreNull, treeModCount)) { - continue restart; + IntervalNode node = this; + IntervalNode treeRoot = myIntervalTree.getRoot(); + if (treeRoot == null) return delta; // someone modified the tree in the meantime + int deltaUp = 0; + boolean allDeltasAreNull = true; + int height = 0; + long path = 0; // path to this node from the root; 0 bit means we choose left subtree, 1 bit means we choose right subtree + while (node != treeRoot) { + node.unpackCachedValuesTo(cached); + if (node.isValid() && cached.modCount == treeModCount) { + deltaUp = cached.deltaUpToRoot - node.delta; + allDeltasAreNull = cached.allDeltasUpAreNull; + break; } + IntervalNode parent = node.getParent(); + if (parent == null) { + break; // can happen when remove node and explicitly set valid to true (e.g. in RangeMarkerTree) + } + path = (path << 1) | (parent.getLeft() == node ? 0 : 1); + node = parent; + height++; + } + // path to this node fits to long + assert height < 63 : height; + + // cache deltas in every node from the root down this + while (true) { + if (node.isValid()) { + int nodeDelta = node.delta; + deltaUp += nodeDelta; + allDeltasAreNull &= nodeDelta == 0; + if (!node.tryToSetCachedValues(deltaUp, allDeltasAreNull, treeModCount)) { + continue restart; + } + } + + if (node == this) break; + node = (path & 1) == 0 ? node.getLeft() : node.getRight(); + path >>= 1; + if (node == null) continue restart; // can only happen in case of concurrently modification } - if (node == this) break; - node = (path & 1) == 0 ? node.getLeft() : node.getRight(); - path >>= 1; - if (node == null) continue restart; // can only happen in case of concurrently modification + assert deltaUp == 0 || !allDeltasAreNull; + return deltaUp; + } + finally { + myIntervalTree.l.readLock().unlock(); } - - assert deltaUp == 0 || !allDeltasAreNull; - return deltaUp; - } - } - - private boolean setCachedOffsetsAtomically(int deltaUpToRoot, boolean allDeltasUpAreNull, int treeModCount) { - synchronized (intervals) { // synchronize for mutual exclusion - if (myIntervalTree.modCount != treeModCount) return false; - this.deltaUpToRoot = deltaUpToRoot; - this.allDeltasUpAreNull = allDeltasUpAreNull; - modCount = treeModCount; //set modCount last - return true; } } protected int changeDelta(int change) { if (change != 0) { - modCount = 0; // deltaUpToRoot is not valid anymore + setCachedValues(0, false, 0); // deltaUpToRoot is not valid anymore return delta += change; } return delta; } protected void clearDelta() { if (delta != 0) { - modCount = 0; // deltaUpToRoot is not valid anymore + setCachedValues(0, false, 0); // deltaUpToRoot is not valid anymore delta = 0; } } @@ -264,6 +265,44 @@ public abstract class IntervalTreeImpl extends RedBla public IntervalTreeImpl getTree() { return myIntervalTree; } + + /** + * packing/unpacking cachedDeltaUpToRoot field parts + * Bits layout: + * XXXXXXXXNMMMMMMMM where + * XXXXXXXX - 31bit int containing cached delta up to root + * N - 1bit flag. if set then all deltas up to root are null + * MMMMMMMM - 32bit int containing this node modification count + */ + private static AtomicFieldUpdater cachedDeltaUpdater = AtomicFieldUpdater.forLongField(IntervalNode.class); + + private void setCachedValues(int deltaUpToRoot, boolean allDeltaUpToRootAreNull, int modCount) { + cachedDeltaUpToRoot = packValues(deltaUpToRoot, allDeltaUpToRootAreNull, modCount); + } + + private static long packValues(long deltaUpToRoot, boolean allDeltaUpToRootAreNull, int modCount) { + return deltaUpToRoot << 33 | (allDeltaUpToRootAreNull ? 0x100000000L : 0) | modCount; + } + + private boolean tryToSetCachedValues(int deltaUpToRoot, boolean allDeltasUpAreNull, int treeModCount) { + if (myIntervalTree.modCount != treeModCount) return false; + long newValue = packValues(deltaUpToRoot, allDeltasUpAreNull, treeModCount); + long oldValue = cachedDeltaUpToRoot; + return cachedDeltaUpdater.compareAndSetLong(this, oldValue, newValue); + } + + public void unpackCachedValuesTo(NodeCachedOffsets t) { + long value = cachedDeltaUpToRoot; + t.deltaUpToRoot = (int)(value >> 33); + t.modCount = (int)value; + t.allDeltasUpAreNull = ((value >> 32) & 1) != 0; + } + } + + static class NodeCachedOffsets { + private int modCount; // if it equals to the com.intellij.openapi.editor.impl.RedBlackTree.modCount then deltaUpToRoot can be used, otherwise it is expired + private int deltaUpToRoot; // sum of all deltas up to the root (including this node' delta). Has valid value only if modCount == IntervalTreeImpl.this.modCount + private boolean allDeltasUpAreNull; // true if all deltas up the tree (including this node) are 0. Has valid value only if modCount == IntervalTreeImpl.this.modCount } private void assertUnderWriteLock() { @@ -274,11 +313,12 @@ public abstract class IntervalTreeImpl extends RedBla return s.contains("Locked by thread"); } - private void pushDeltaFromRoot(IntervalNode node) { + private void pushDeltaFromRoot(IntervalNode node, NodeCachedOffsets cached) { if (node != null) { - if (node.allDeltasUpAreNull && node.isValid() && node.modCount == modCount) return; - pushDeltaFromRoot(node.getParent()); - pushDelta(node); + node.unpackCachedValuesTo(cached); + if (cached.allDeltasUpAreNull && node.isValid() && cached.modCount == modCount) return; + pushDeltaFromRoot(node.getParent(), cached); + pushDelta(node, cached); } } @@ -469,33 +509,39 @@ public abstract class IntervalTreeImpl extends RedBla public boolean hasNext() { if (current != null) return true; if (currentNode == null) return false; + try { + l.readLock().lock(); - if (modCount != modCountBefore) throw new ConcurrentModificationException(); - while (indexInCurrentList != currentNode.intervals.size()) { - T t = currentNode.intervals.get(indexInCurrentList++).get(); - if (t != null) { - current = t; - return true; + if (modCount != modCountBefore) throw new ConcurrentModificationException(); + while (indexInCurrentList != currentNode.intervals.size()) { + T t = currentNode.intervals.get(indexInCurrentList++).get(); + if (t != null) { + current = t; + return true; + } + } + indexInCurrentList = 0; + while (true) { + currentNode = nextNode(currentNode); + if (currentNode == null) { + return false; + } + if (overlaps(currentNode, startOffset, endOffset, deltaUpToRootExclusive)) { + assert currentNode.intervalStart() + deltaUpToRootExclusive + currentNode.delta >= firstOverlapStart; + indexInCurrentList = 0; + while (indexInCurrentList != currentNode.intervals.size()) { + T t = currentNode.intervals.get(indexInCurrentList++).get(); + if (t != null) { + current = t; + return true; + } + } + indexInCurrentList = 0; + } } } - indexInCurrentList = 0; - while (true) { - currentNode = nextNode(currentNode); - if (currentNode == null) { - return false; - } - if (overlaps(currentNode, startOffset, endOffset, deltaUpToRootExclusive)) { - assert currentNode.intervalStart() + deltaUpToRootExclusive + currentNode.delta >= firstOverlapStart; - indexInCurrentList = 0; - while (indexInCurrentList != currentNode.intervals.size()) { - T t = currentNode.intervals.get(indexInCurrentList++).get(); - if (t != null) { - current = t; - return true; - } - } - indexInCurrentList = 0; - } + finally { + l.readLock().unlock(); } } @@ -517,7 +563,7 @@ public abstract class IntervalTreeImpl extends RedBla assert root.isValid(); int delta = deltaUpToRootExclusive + root.delta; int myMaxEnd = maxEndOf(root, deltaUpToRootExclusive); - assert startOffset <= myMaxEnd; + if (startOffset > myMaxEnd) return null; // tree changed // try to go right down IntervalNode right = root.getRight(); @@ -588,13 +634,14 @@ public abstract class IntervalTreeImpl extends RedBla node.setRight(null); List> gced = new SmartList>(); + NodeCachedOffsets cached = new NodeCachedOffsets(); if (root == null) { root = node; } else { IntervalNode current = getRoot(); while (true) { - pushDelta(current); + pushDelta(current, cached); int compResult = compareNodes(node, 0, current, 0, gced); if (compResult == 0) { return current; @@ -616,10 +663,9 @@ public abstract class IntervalTreeImpl extends RedBla } node.setParent(current); } - node.setCachedOffsetsAtomically(0, true, modCount); - correctMaxUp(node); + node.setCachedValues(0, true, modCount); + correctMaxUp(node, cached); onInsertNode(); - assertUnderWriteLock(); keySize += node.intervals.size(); insertCase1(node); verifyProperties(); @@ -698,9 +744,11 @@ public abstract class IntervalTreeImpl extends RedBla int[] nodeCounter, TLongHashSet ids, boolean allDeltasUpAreNull) { if (root == null) return Trinity.create(Integer.MAX_VALUE,Integer.MIN_VALUE,Integer.MIN_VALUE); - if (root.modCount == modCount) { - assert root.allDeltasUpAreNull == (root.delta == 0 && allDeltasUpAreNull); - assert root.deltaUpToRoot == root.delta + deltaUpToRootExclusive; + NodeCachedOffsets cached = new NodeCachedOffsets(); + root.unpackCachedValuesTo(cached); + if (cached.modCount == modCount) { + assert cached.allDeltasUpAreNull == (root.delta == 0 && allDeltasUpAreNull); + assert cached.deltaUpToRoot == root.delta + deltaUpToRootExclusive; } T liveInterval = null; for (int i = root.intervals.size() - 1; i >= 0; i--) { @@ -753,11 +801,12 @@ public abstract class IntervalTreeImpl extends RedBla @Override protected Node maximumNode(Node n) { IntervalNode root = (IntervalNode)n; - pushDelta(root.getParent()); - pushDelta(root); + NodeCachedOffsets cached = new NodeCachedOffsets(); + pushDelta(root.getParent(), cached); + pushDelta(root, cached); while (root.getRight() != null) { root = root.getRight(); - pushDelta(root); + pushDelta(root, cached); } return root; } @@ -819,15 +868,16 @@ public abstract class IntervalTreeImpl extends RedBla void removeNode(@NotNull IntervalNode node) { deleteNode(node); IntervalNode parent = node.getParent(); - correctMaxUp(parent); + correctMaxUp(parent, new NodeCachedOffsets()); } @Override protected void deleteNode(@NotNull Node n) { assertUnderWriteLock(); IntervalNode node = (IntervalNode)n; - pushDeltaFromRoot(node); - assertAllDeltasAreNull(node); + NodeCachedOffsets cached = new NodeCachedOffsets(); + pushDeltaFromRoot(node, cached); + assertAllDeltasAreNull(node, cached); super.deleteNode(n); keySize -= node.intervals.size(); @@ -840,12 +890,12 @@ public abstract class IntervalTreeImpl extends RedBla } // returns true if all deltas involved are still 0 - protected boolean pushDelta(IntervalNode root) { + protected boolean pushDelta(IntervalNode root, NodeCachedOffsets cached) { if (root == null || !root.isValid()) return true; IntervalNode parent = root.getParent(); - assertAllDeltasAreNull(parent); + assertAllDeltasAreNull(parent, cached); int delta = root.delta; - root.modCount = 0; + root.setCachedValues(0, true, 0); if (delta != 0) { root.setIntervalStart(root.intervalStart() + delta); root.setIntervalEnd(root.intervalEnd() + delta); @@ -856,7 +906,7 @@ public abstract class IntervalTreeImpl extends RedBla incDelta(root.getLeft(), delta) & incDelta(root.getRight(), delta); } - root.setCachedOffsetsAtomically(0, true, modCount); + root.setCachedValues(0, true, modCount); return true; } @@ -896,7 +946,7 @@ public abstract class IntervalTreeImpl extends RedBla //correctMaxUp(a); a.color = dcolor; d.color = acolor; - correctMaxUp(a); + correctMaxUp(a, new NodeCachedOffsets()); checkMax(false); assert a.delta == 0 : a.delta; @@ -956,8 +1006,8 @@ public abstract class IntervalTreeImpl extends RedBla node.maxEnd = realMax - deltaUpToRoot; } - private void correctMaxUp(IntervalNode node) { - int delta = node == null ? 0 : node.computeDeltaUpToRoot(); + private void correctMaxUp(IntervalNode node, NodeCachedOffsets cached) { + int delta = node == null ? 0 : node.computeDeltaUpToRoot(cached); assert delta == 0 : delta; while (node != null) { if (node.isValid()) { @@ -977,11 +1027,12 @@ public abstract class IntervalTreeImpl extends RedBla IntervalNode node2 = node1.getLeft(); IntervalNode node3 = node1.getRight(); + NodeCachedOffsets cached = new NodeCachedOffsets(); IntervalNode parent = node1.getParent(); - int deltaUp = parent == null ? 0 : parent.computeDeltaUpToRoot(); - pushDelta(node1); - pushDelta(node2); - pushDelta(node3); + int deltaUp = parent == null ? 0 : parent.computeDeltaUpToRoot(cached); + pushDelta(node1, cached); + pushDelta(node2, cached); + pushDelta(node3, cached); super.rotateRight(node1); @@ -990,9 +1041,9 @@ public abstract class IntervalTreeImpl extends RedBla } correctMax(node1, deltaUp); correctMax(node2, deltaUp); - assertAllDeltasAreNull(node1); - assertAllDeltasAreNull(node2); - assertAllDeltasAreNull(node3); + assertAllDeltasAreNull(node1, cached); + assertAllDeltasAreNull(node2, cached); + assertAllDeltasAreNull(node3, cached); checkMax(false); } @@ -1003,11 +1054,12 @@ public abstract class IntervalTreeImpl extends RedBla IntervalNode node2 = node1.getLeft(); IntervalNode node3 = node1.getRight(); + NodeCachedOffsets cached = new NodeCachedOffsets(); IntervalNode parent = node1.getParent(); - int deltaUp = parent == null ? 0 : parent.computeDeltaUpToRoot(); - pushDelta(node1); - pushDelta(node2); - pushDelta(node3); + int deltaUp = parent == null ? 0 : parent.computeDeltaUpToRoot(cached); + pushDelta(node1, cached); + pushDelta(node2, cached); + pushDelta(node3, cached); checkMax(false); super.rotateLeft(node1); @@ -1016,9 +1068,9 @@ public abstract class IntervalTreeImpl extends RedBla } correctMax(node1, deltaUp); correctMax(node3, deltaUp); - assertAllDeltasAreNull(node1); - assertAllDeltasAreNull(node2); - assertAllDeltasAreNull(node3); + assertAllDeltasAreNull(node1, cached); + assertAllDeltasAreNull(node2, cached); + assertAllDeltasAreNull(node3, cached); checkMax(false); } @@ -1026,8 +1078,9 @@ public abstract class IntervalTreeImpl extends RedBla @Override protected void replaceNode(@NotNull Node node, Node child) { IntervalNode myNode = (IntervalNode)node; - pushDelta(myNode); - pushDelta((IntervalNode)child); + NodeCachedOffsets cached = new NodeCachedOffsets(); + pushDelta(myNode, cached); + pushDelta((IntervalNode)child, cached); super.replaceNode(node, child); if (child != null && myNode.isValid()) { @@ -1036,11 +1089,12 @@ public abstract class IntervalTreeImpl extends RedBla } } - private void assertAllDeltasAreNull(IntervalNode node) { + private void assertAllDeltasAreNull(IntervalNode node, NodeCachedOffsets cached) { if (node == null) return; if (!node.isValid()) return; assert node.delta == 0; - assert node.modCount != modCount || node.allDeltasUpAreNull; + node.unpackCachedValuesTo(cached); + assert cached.modCount != modCount || cached.allDeltasUpAreNull; } private IntervalNode findMinOverlappingWith(IntervalNode root, Interval interval, int modCountBefore, int deltaUpToRootExclusive) { diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/RangeMarkerImpl.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/RangeMarkerImpl.java index 9db718dd71df..c836ea0f4f07 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/RangeMarkerImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/RangeMarkerImpl.java @@ -29,7 +29,7 @@ public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.RangeMarkerImpl"); protected final DocumentEx myDocument; - protected RangeMarkerTree.RMNode myNode; + protected RangeMarkerTree.RMNode myNode; private final long myId; private static final StripedIDGenerator counter = new StripedIDGenerator(); @@ -41,10 +41,10 @@ public class RangeMarkerImpl extends UserDataHolderBase implements RangeMarkerEx if (start < 0) { throw new IllegalArgumentException("Wrong start: " + start+"; end="+end); } - else if (end > document.getTextLength()) { + if (end > document.getTextLength()) { throw new IllegalArgumentException("Wrong end: " + end+ "; document length="+document.getTextLength()+"; start="+start); } - else if (start > end){ + if (start > end){ throw new IllegalArgumentException("start > end: start=" + start+"; end="+end); } diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java index fa01a11bbc96..b0495d8f5894 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/RangeMarkerTree.java @@ -148,7 +148,7 @@ public class RangeMarkerTree extends IntervalTreeImpl intervalNode) { - ((RangeMarkerImpl)key).myNode = (RMNode)intervalNode; + ((RangeMarkerImpl)key).myNode = (RangeMarkerTree.RMNode)intervalNode; } public class RMNode extends IntervalTreeImpl.IntervalNode { @@ -183,7 +183,7 @@ public class RangeMarkerTree extends IntervalTreeImpl> affected = new SmartList>(); - collectAffectedMarkersAndShiftSubtrees(getRoot(), e, affected); + collectAffectedMarkersAndShiftSubtrees(getRoot(), e, affected, new NodeCachedOffsets()); checkMax(false); if (!affected.isEmpty()) { @@ -250,9 +250,11 @@ public class RangeMarkerTree extends IntervalTreeImpl root, @NotNull DocumentEvent e, @NotNull List> affected) { + private boolean collectAffectedMarkersAndShiftSubtrees(IntervalNode root, + @NotNull DocumentEvent e, + @NotNull List> affected, NodeCachedOffsets cached) { if (root == null) return true; - boolean norm = pushDelta(root); + boolean norm = pushDelta(root, cached); int maxEnd = root.maxEnd; assert root.isValid(); @@ -277,8 +279,8 @@ public class RangeMarkerTree extends IntervalTreeImpl extends IntervalTreeImpl { public static AtomicFieldUpdater forFieldOfType(@NotNull Class ownerClass, @NotNull Class fieldType) { return new AtomicFieldUpdater(ownerClass, fieldType); } + @NotNull + public static AtomicFieldUpdater forLongField(@NotNull Class ownerClass) { + return new AtomicFieldUpdater(ownerClass, long.class); + } private AtomicFieldUpdater(Class ownerClass, Class fieldType) { Field[] declaredFields = ownerClass.getDeclaredFields(); @@ -82,7 +86,7 @@ public class AtomicFieldUpdater { throw new IllegalArgumentException("Field "+found+" in the "+ownerClass+" must be volatile"); } if ((found.getModifiers() & (Modifier.STATIC | Modifier.FINAL)) != 0) { - throw new IllegalArgumentException("Field "+found+" in the "+ownerClass+" must non-final instance"); + throw new IllegalArgumentException("Field "+found+" in the "+ownerClass+" must be non-final non-static"); } offset = unsafe.objectFieldOffset(found); } @@ -90,4 +94,7 @@ public class AtomicFieldUpdater { public boolean compareAndSet(@NotNull T owner, V expected, V newValue) { return unsafe.compareAndSwapObject(owner, offset, expected, newValue); } + public boolean compareAndSetLong(@NotNull T owner, long expected, long newValue) { + return unsafe.compareAndSwapLong(owner, offset, expected, newValue); + } }