package org.apache.cassandra.utils;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.LockSupport;
/**
* <p>A relatively easy to use utility for general purpose thread signalling.</p>
* <p>Usage on a thread awaiting a state change using a WaitQueue q is:</p>
* <pre>
* {@code
* while (!conditionMet())
* WaitSignal s = q.register();
* if (!conditionMet()) // or, perhaps more correctly, !conditionChanged()
* s.await();
* else
* s.cancel();
* }
* </pre>
* A signalling thread, AFTER changing the state, then calls q.signal() to wake up one, or q.signalAll()
* to wake up all, waiting threads.
*
* <p>A few notes on utilisation:</p>
* <p>1. A thread will only exit await() when it has been signalled, but this does
* not guarantee the condition has not been altered since it was signalled,
* and depending on your design it is likely the outer condition will need to be
* checked in a loop, though this is not always the case.</p>
* <p>2. Each signal is single use, so must be re-registered after each await(). This is true even if it times out.</p>
* <p>3. If you choose not to wait on the signal (because the condition has been met before you waited on it)
* you must cancel() the signal if the signalling thread uses signal() to awake waiters; otherwise signals will be
* lost</p>
* <p>4. Care must be taken when selecting conditionMet() to ensure we are waiting on the condition that actually
* indicates progress is possible. In some complex cases it may be tempting to wait on a condition that is only indicative
* of local progress, not progress on the task we are aiming to complete, and a race may leave us waiting for a condition
* to be met that we no longer need.
* <p>5. This scheme is not fair</p>
* <p>6. Only the thread that calls register() may call await()</p>
* <p>To understand intuitively how this class works, the idea is simply that a thread, once it considers itself
* incapable of making progress, registers itself to be awoken once that condition changes. However, that condition
* could have changed between checking and registering (in which case a thread updating the state would have been unable to signal it),
* so before going to sleep on the signal, it checks the condition again, sleeping only if it hasn't changed.</p>
*/
// TODO : switch to a Lock Free queue
public final class WaitQueue
{
public final class Signal
{
private final Thread thread = Thread.currentThread();
volatile int signalled;
private boolean isSignalled()
{
return signalled == 1;
}
public boolean isCancelled()
{
return signalled == -1;
}
private boolean signal()
{
if (signalledUpdater.compareAndSet(this, 0, 1))
{
LockSupport.unpark(thread);
return true;
}
return false;
}
public void awaitUninterruptibly()
{
assert !isCancelled();
if (thread != Thread.currentThread())
throw new IllegalStateException();
boolean interrupted = false;
while (!isSignalled())
{
if (Thread.interrupted())
interrupted = true;
LockSupport.park();
}
if (interrupted)
thread.interrupt();
}
public void await() throws InterruptedException
{
assert !isCancelled();
while (!isSignalled())
{
if (Thread.interrupted())
{
checkAndClear();
throw new InterruptedException();
}
if (thread != Thread.currentThread())
throw new IllegalStateException();
LockSupport.park();
}
}
public long awaitNanos(long nanosTimeout) throws InterruptedException
{
assert signalled != -1;
long start = System.nanoTime();
while (!isSignalled())
{
if (Thread.interrupted())
{
checkAndClear();
throw new InterruptedException();
}
LockSupport.parkNanos(nanosTimeout);
}
return nanosTimeout - (System.nanoTime() - start);
}
public boolean await(long time, TimeUnit unit) throws InterruptedException
{
// ignores nanos atm
long until = System.currentTimeMillis() + unit.toMillis(time);
if (until < 0)
until = Long.MAX_VALUE;
return awaitUntil(until);
}
public boolean awaitUntil(long until) throws InterruptedException
{
assert !isCancelled();
while (until < System.currentTimeMillis() && !isSignalled())
{
if (Thread.interrupted())
{
checkAndClear();
throw new InterruptedException();
}
LockSupport.parkUntil(until);
}
return checkAndClear();
}
private boolean checkAndClear()
{
if (isSignalled())
{
signalled = -1;
return true;
}
else if (signalledUpdater.compareAndSet(this, 0, -1))
{
cleanUpCancelled();
return false;
}
else
{
// must now be signalled, as checkAndClear() can only be called by
// owning thread if used correctly
signalled = -1;
return true;
}
}
public void cancel()
{
if (signalled < 0)
return;
if (!signalledUpdater.compareAndSet(this, 0, -1))
{
signalled = -1;
signal();
cleanUpCancelled();
}
}
}
private static final AtomicIntegerFieldUpdater signalledUpdater = AtomicIntegerFieldUpdater.newUpdater(Signal.class, "signalled");
// the waiting signals
private final ConcurrentLinkedQueue<Signal> queue = new ConcurrentLinkedQueue<>();
/**
* The calling thread MUST be the thread that uses the signal (for now)
* @return
*/
public Signal register()
{
Signal signal = new Signal();
queue.add(signal);
return signal;
}
/**
* Signal one waiting thread
*/
public void signal()
{
if (queue.isEmpty())
return;
Iterator<Signal> iter = queue.iterator();
while (iter.hasNext())
{
Signal next = iter.next();
if (next.signal())
{
iter.remove();
return;
}
}
}
/**
* Signal all waiting threads
*/
public void signalAll()
{
if (queue.isEmpty())
return;
Iterator<Signal> iter = queue.iterator();
while (iter.hasNext())
{
Signal next = iter.next();
if (next.signal())
iter.remove();
}
}
private void cleanUpCancelled()
{
Iterator<Signal> iter = queue.iterator();
while (iter.hasNext())
{
Signal next = iter.next();
if (next.isCancelled())
iter.remove();
}
}
/**
* Return how many threads are waiting
* @return
*/
public int getWaiting()
{
if (queue.isEmpty())
return 0;
Iterator<Signal> iter = queue.iterator();
int count = 0;
while (iter.hasNext())
{
Signal next = iter.next();
if (next.isCancelled())
iter.remove();
else
count++;
}
return count;
}
}