edu.stanford.ppl.concurrent.EpochNode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of snaptree Show documentation
Show all versions of snaptree Show documentation
The reference implementation of SnapTree,
a concurrent AVL tree with fast cloning, snapshots, and consistent
iteration.
The newest version!
/*
* Copyright (c) 2009 Stanford University, unless otherwise specified.
* All rights reserved.
*
* This software was developed by the Pervasive Parallelism Laboratory of
* Stanford University, California, USA.
*
* Permission to use, copy, modify, and distribute this software in source
* or binary form for any purpose with or without fee is hereby granted,
* provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of Stanford University nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package edu.stanford.ppl.concurrent;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/** Provides an implementation of the behavior of an {@link Epoch}. */
abstract class EpochNode extends AtomicLong implements Epoch.Ticket {
private static final int TRIES_BEFORE_SUBTREE = 2;
private static final int CLOSER_HEAD_START = 1000;
/** This includes the root. 7 or fewer procs gets 2, 63 or fewer gets
* 3, 511 or fewer 4. We observe that the node count reported by {@link
* #computeSpread} is roughly twice the number of hardware contexts in
* use.
*/
private static final int MAX_LEVELS = 2 + log8(Runtime.getRuntime().availableProcessors());
/** Returns floor(log_base_8(value)). */
private static int log8(final int value) {
return (31 - Integer.numberOfLeadingZeros(value)) / 3;
}
//////////////// branching factor
private static final int LOG_BF = 3;
private static final int BF = 1 << LOG_BF;
private static final int BF_MASK = BF - 1;
//////////////// bit packing
private static final int DATA_SUM_SHIFT = 32;
private static int dataSum(long state) { return (int)(state >> DATA_SUM_SHIFT); }
private static long withDataDelta(long state, int delta) { return state + (((long) delta) << DATA_SUM_SHIFT); }
private static final int CHILD_CLOSED_SHIFT = 32 - BF;
private static long ALL_CHILDREN_CLOSED = ((1L << BF) - 1L) << CHILD_CLOSED_SHIFT;
private static long childClosedBit(int which) { return 1L << (CHILD_CLOSED_SHIFT + which); }
private static boolean isChildClosed(long state, int which) { return (state & childClosedBit(which)) != 0; }
private static long withChildClosed(long state, int which, long childState) {
assert(!isChildClosed(state, which));
return withDataDelta(state | childClosedBit(which), dataSum(childState));
}
private static boolean isAllChildrenClosed(long state) { return (state & ALL_CHILDREN_CLOSED) == ALL_CHILDREN_CLOSED; }
private static final int CHILD_PRESENT_SHIFT = CHILD_CLOSED_SHIFT - BF;
private static final long ANY_CHILD_PRESENT = ((1L << BF) - 1L) << CHILD_PRESENT_SHIFT;
private static long childPresentBit(int which) { return 1L << (CHILD_PRESENT_SHIFT + which); }
private static boolean isChildPresent(long state, int which) { return (state & childPresentBit(which)) != 0; }
private static long withChildPresent(long state, int which) { return state | childPresentBit(which); }
private static boolean isAnyChildPresent(long state) { return (state & ANY_CHILD_PRESENT) != 0; }
private static final long MARK = (1L << (CHILD_PRESENT_SHIFT - 1));
private static boolean isMarked(long state) { return (state & MARK) != 0L; }
/** Records all non-present children as closed. */
private static long withMarked(long state) {
final int missingChildren = (~((int) state) >> CHILD_PRESENT_SHIFT) & ((1 << BF) - 1);
return state | MARK | (((long) missingChildren) << CHILD_CLOSED_SHIFT);
}
private static final long ENTRY_COUNT_MASK = MARK - 1;
private static int entryCount(long state) { return (int) (state & ENTRY_COUNT_MASK); }
private static long withArrive(long state) { return state + 1; }
private static long withLeave(long state, int dataDelta) { return withDataDelta(state - 1, dataDelta); }
private static boolean mayArrive(long state) { return entryCount(state) != ENTRY_COUNT_MASK; }
private static boolean mayLeave(long state) { return entryCount(state) != 0; }
private static final long CLOSED_MASK = MARK | ALL_CHILDREN_CLOSED | ENTRY_COUNT_MASK;
private static final long CLOSED_VALUE = MARK | ALL_CHILDREN_CLOSED;
private static boolean isClosed(long state) { return (state & CLOSED_MASK) == CLOSED_VALUE; }
private static final long ENTRY_FAST_PATH_MASK = ANY_CHILD_PRESENT | MARK | (ENTRY_COUNT_MASK - (ENTRY_COUNT_MASK >> 1));
/** Not marked, no children, and no overflow possible. */
private static boolean isEntryFastPath(long state) { return (state & ENTRY_FAST_PATH_MASK) == 0L; }
//////////////// subclasses
private static class Child extends EpochNode {
private Child(final EpochNode parent, final int whichInParent) {
super(parent, whichInParent);
}
protected void onClosed(final int dataSum) {
throw new Error();
}
}
//////////////// instance state
private static final AtomicReferenceFieldUpdater[] childrenUpdaters = {
AtomicReferenceFieldUpdater.newUpdater(EpochNode.class, EpochNode.class, "_child0"),
AtomicReferenceFieldUpdater.newUpdater(EpochNode.class, EpochNode.class, "_child1"),
AtomicReferenceFieldUpdater.newUpdater(EpochNode.class, EpochNode.class, "_child2"),
AtomicReferenceFieldUpdater.newUpdater(EpochNode.class, EpochNode.class, "_child3"),
AtomicReferenceFieldUpdater.newUpdater(EpochNode.class, EpochNode.class, "_child4"),
AtomicReferenceFieldUpdater.newUpdater(EpochNode.class, EpochNode.class, "_child5"),
AtomicReferenceFieldUpdater.newUpdater(EpochNode.class, EpochNode.class, "_child6"),
AtomicReferenceFieldUpdater.newUpdater(EpochNode.class, EpochNode.class, "_child7")
};
private final EpochNode _parent;
private final int _whichInParent;
// It would be cleaner to use an array of children, but we want to force
// all of the bulk into the same object as the AtomicLong.value.
// To avoid races between creating a child and marking a node as closed,
// we add a bit to the state for each child that records whether it
// *should* exist. If we find that the bit is set but a child is missing,
// we can create it ourself.
private volatile EpochNode _child0;
private volatile EpochNode _child1;
private volatile EpochNode _child2;
private volatile EpochNode _child3;
private volatile EpochNode _child4;
private volatile EpochNode _child5;
private volatile EpochNode _child6;
private volatile EpochNode _child7;
EpochNode() {
_parent = null;
_whichInParent = 0;
}
private EpochNode(final EpochNode parent, final int whichInParent) {
_parent = parent;
_whichInParent = whichInParent;
}
//////////////// provided by the caller
abstract protected void onClosed(int dataSum);
//////////////// child management
private EpochNode getChildFromField(final int which) {
switch (which) {
case 0: return _child0;
case 1: return _child1;
case 2: return _child2;
case 3: return _child3;
case 4: return _child4;
case 5: return _child5;
case 6: return _child6;
case 7: return _child7;
default: return null;
}
}
private EpochNode getChild(final long state, final int which) {
if (!isChildPresent(state, which)) {
return null;
}
final EpochNode existing = getChildFromField(which);
return (existing != null) ? existing : constructPresentChild(which);
}
@SuppressWarnings("unchecked")
private EpochNode constructPresentChild(final int which) {
final EpochNode n = new Child(this, which);
return childrenUpdaters[which].compareAndSet(this, null, n) ? n : getChildFromField(which);
}
private EpochNode getOrCreateChild(final int which) {
final EpochNode existing = getChildFromField(which);
return (existing != null) ? existing : createChild(which);
}
private EpochNode createChild(final int which) {
while (true) {
final long state = get();
if (isMarked(state)) {
// whatever we've got is what we've got
return getChild(state, which);
}
if (compareAndSet(state, withChildPresent(state, which))) {
// the child now should exist, but we must still actually
// construct and link in the instance
return constructPresentChild(which);
}
}
}
/** Returns the Node
to decr on success, null if
* {@link #beginClose} has already been called on this instance.
*/
public EpochNode attemptArrive() {
final long state = get();
if (isEntryFastPath(state) && compareAndSet(state, withArrive(state))) {
return this;
}
else {
return attemptArrive(0, 1);
}
}
private int getIdentity() {
final int h = System.identityHashCode(Thread.currentThread());
// Multiply by -127, as suggested by java.util.IdentityHashMap.
// We also set an bit we don't use, to make sure it is never zero.
return (h - (h << 7)) | (1 << 31);
}
/** level 1 is the root. */
private EpochNode attemptArrive(int id, final int level) {
int tries = 0;
while (true) {
final long state = get();
if (isMarked(state)) {
return null;
}
if (isAnyChildPresent(state) ||
(tries >= TRIES_BEFORE_SUBTREE && level < MAX_LEVELS)) {
// Go deeper if we have previously detected contention, or if
// we are currently detecting it. Lazy computation of our
// current identity.
if (id == 0) {
id = getIdentity();
}
final EpochNode child = getOrCreateChild(id & BF_MASK);
if (child == null) {
return null;
}
return child.attemptArrive(id >> LOG_BF, level + 1);
}
if (!mayArrive(state)) {
throw new IllegalStateException("maximum arrival count of " + ENTRY_COUNT_MASK + " exceeded");
}
if (compareAndSet(state, withArrive(state))) {
// success
return this;
}
++tries;
}
}
/** Should be called on every non-null return value from attemptArrive. */
public void leave(final int dataDelta) {
while (true) {
final long state = get();
if (!mayLeave(state)) {
throw new IllegalStateException("incorrect call to Epoch.leave");
}
final long after = withLeave(state, dataDelta);
if (compareAndSet(state, after)) {
if (isClosed(after)) {
newlyClosed(after);
}
return;
}
}
}
private void newlyClosed(final long state) {
if (_parent != null) {
// propogate
_parent.childIsNowClosed(_whichInParent, state);
}
else {
// report
onClosed(dataSum(state));
}
}
private void childIsNowClosed(final int which, final long childState) {
while (true) {
final long state = get();
if (isChildClosed(state, which)) {
// not our problem
return;
}
final long after = withChildClosed(state, which, childState);
if (compareAndSet(state, after)) {
if (isClosed(after)) {
newlyClosed(after);
}
return;
}
}
}
/** Prevents subsequent calls to {@link #attemptArrive} from succeeding. */
public void beginClose() {
int attempts = 0;
long state;
while (true) {
++attempts;
state = get();
if (isClosed(state)) {
return;
}
if (isMarked(state)) {
// give the thread that actually performed this transition a
// bit of a head start
if (attempts < CLOSER_HEAD_START) {
continue;
}
break;
}
// every child that is not present will be recorded as closed by withMarked
final long after = withMarked(state);
if (compareAndSet(state, after)) {
if (isAllChildrenClosed(after)) {
if (isClosed(after) && _parent == null) {
// finished in one CAS, yeah!
onClosed(dataSum(after));
}
// no second stage necessary
return;
}
// CAS successful, so now we need to beginClose() the children
break;
}
}
// no new child bits can be set after marking, so this gets everyone
for (int which = 0; which < BF; ++which) {
final EpochNode child = getChild(state, which);
if (child != null) {
child.beginClose();
}
}
// Rather than have each child bubble up its closure, we gather it
// here to reduce the number of CASs required.
while (true) {
final long before = get();
long after = before;
for (int which = 0; which < BF; ++which) {
if (!isChildClosed(before, which)) {
final long childState = getChildFromField(which).get();
if (isClosed(childState)) {
after = withChildClosed(after, which, childState);
}
}
}
if (before == after) {
return;
}
if (compareAndSet(before, after)) {
if (isClosed(after) && _parent == null) {
onClosed(dataSum(after));
}
return;
}
}
}
/** If possible returns the dataSum
that would be delivered
* to {@link #onClosed(int)} if this epoch were closed at this moment,
* otherwise returns null. This will succeed if and only if the tree
* consists only of a single node.
*/
public Integer attemptDataSum() {
final long state = get();
if (!isAnyChildPresent(state) && entryCount(state) == 0) {
// this is better than Integer.valueOf for dynamic escape analysis
//return new Integer(dataSum(state));
// this is better than new Integer() for object creation
return Integer.valueOf(dataSum(state));
}
else {
return null;
}
}
/** For debugging purposes. */
int computeSpread() {
final long state = get();
if (isAnyChildPresent(state)) {
int sum = 0;
for (int which = 0; which < BF; ++which) {
final EpochNode child = getChild(state, which);
if (child != null) {
sum += child.computeSpread();
}
else {
// child would be created for arrive, so count it
sum += 1;
}
}
return sum;
}
else {
return 1;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy