/* SnapTree - (c) 2009 Stanford University - PPL */
// StripedSizedEpoch
package trees.lockbased.stanfordutils;
import java.util.concurrent.atomic.AtomicLongArray;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
class StripedSizedEpoch {
/** NumStripes*sizeof(long) should cross multiple cache lines. */
static final int NumStripes = nextPowerOfTwo(Integer.valueOf(System.getProperty("epoch.stripes", "64")));
private static int nextPowerOfTwo(final int n) {
return 1 << (32 - Integer.numberOfLeadingZeros(n - 1));
}
static final AtomicReferenceFieldUpdater<StripedSizedEpoch,Object> WakeupUpdater
= AtomicReferenceFieldUpdater.newUpdater(StripedSizedEpoch.class, Object.class, "wakeup");
static final AtomicLongFieldUpdater<StripedSizedEpoch> CleanupAcquiredUpdater
= AtomicLongFieldUpdater.newUpdater(StripedSizedEpoch.class, "cleanupAcquired");
/** Size is in the high int (<< 32), entryCount in the low int. */
private final AtomicLongArray entryCountsAndSizes = new AtomicLongArray(NumStripes);
private volatile Object wakeup;
private volatile int completedSize = -1;
private volatile long cleanupAcquired;
// TODO: fix the problem of an unfortunate thread hash code collision causing long-term false sharing
public StripedSizedEpoch(final int initialSize) {
entryCountsAndSizes.set(0, ((long) initialSize) << 32);
}
/** Returns true if entry was successful, false if shutdown has already begun on this Epoch. */
public boolean enter(final int id) {
entryCountsAndSizes.getAndIncrement(id & (NumStripes - 1));
if (wakeup != null) {
exit(id, 0);
return false;
} else {
return true;
}
}
public void exit(final int id, final int sizeDelta) {
final long ecasDelta = (((long) sizeDelta) << 32) - 1L;
final long after = entryCountsAndSizes.addAndGet(id & (NumStripes - 1), ecasDelta);
if (((int) after) == 0) {
final Object w = wakeup;
if (w != null) {
synchronized(w) {
w.notifyAll();
}
}
}
}
/** Triggers shutdown, but does not accept cleanup responsibility. */
public void shutdown() {
awaitShutdown(triggerShutdown(), false);
}
/** Returns the wakeup object. */
private Object triggerShutdown() {
Object w = wakeup;
if (w == null) {
WakeupUpdater.compareAndSet(this, null, new Object());
w = wakeup;
}
return w;
}
/** Awaits a pending shutdown, then returns true if the caller has been
* assigned cleanup responsibility. The caller will only be assigned
* cleanup responsibility if they have requested it.
*/
public boolean awaitShutdown(final boolean canAcceptCleanup) {
return awaitShutdown(wakeup, canAcceptCleanup);
}
private boolean awaitShutdown(final Object w, final boolean canAcceptCleanup) {
boolean done = false;
boolean interrupted = false;
while (!done && isPending()) {
synchronized(w) {
if (isPending()) {
try {
w.wait();
} catch (final InterruptedException xx) {
interrupted = true;
}
} else {
done = true;
}
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
return canAcceptCleanup &&
cleanupAcquired == 0 &&
CleanupAcquiredUpdater.compareAndSet(this, 0L, 1L);
}
private boolean isPending() {
if (completedSize != -1) {
return false;
}
int size = 0;
for (int i = 0; i < NumStripes; ++i) {
final long ecas = entryCountsAndSizes.get(i);
if (((int) ecas) != 0) {
return true;
}
size += (int)(ecas >> 32);
}
if (completedSize == -1) {
completedSize = size;
}
return false;
}
public int size() {
final int z = completedSize;
if (z == -1) {
throw new IllegalStateException("can't read size prior to shutdown");
}
return z;
}
}