/**
* Alipay.com Inc.
* Copyright (c) 2004-2012 All Rights Reserved.
*/
package com.alipay.zdal.datasource.resource.util.timeout;
import com.alipay.zdal.datasource.resource.util.ThrowableHandler;
import com.alipay.zdal.datasource.resource.util.threadpool.BasicThreadPool;
import com.alipay.zdal.datasource.resource.util.threadpool.BlockingMode;
import com.alipay.zdal.datasource.resource.util.threadpool.ThreadPool;
/**
* The timeout factory.
*
* This is written with performance in mind. In case of <code>n</code>
* active timeouts, creating, cancelling and firing timeouts all operate
* in time <code>O(log(n))</code>.
*
* If a timeout is cancelled, the timeout is not discarded. Instead the
* timeout is saved to be reused for another timeout. This means that if
* no timeouts are fired, this class will eventually operate without
* allocating anything on the heap.
*
* @author ����
* @version $Id: TimeoutFactory.java, v 0.1 2014-1-6 ����05:46:16 Exp $
*/
public class TimeoutFactory {
// private static final Logger logger = Logger.getLogger(TimeoutFactory.class);
// Code commented out with the mark "INV:" are runtime checks
// of invariants that are not needed for a production system.
// For problem solving, you can remove these comments.
// Multithreading notes:
//
// While a TimeoutImpl is enqueued, its index field contains the index
// of the instance in the queue; that is, for 1 <= n <= size,
// q[n].index = n.
// Modifications of an enqueued TimeoutImpl instance may only happen
// in code synchronized on the TimeoutFactory instance that has it
// enqueued.
// Modifications on the priority queue may only happen while running in
// code synchronized on the TimeoutFactory instance that holds the queue.
// When a TimeoutImpl instance is no longer enqueued, its index field
// changes to one of the negative constants declared in the TimeoutImpl
// class.
// When a TimeoutImpl is not in use, its index field is TimeoutImpl.DONE
// and it is on the freeList.
//
// Cancellation may race with the timeout.
// To avoid problems with this, the TimeoutImpl index field is set to
// TimeoutImpl.TIMEOUT when the TimeoutImpl is taken out of the queue.
// Finally the index field is set to TimeoutImpl.DONE, and
// the TimeoutImpl instance is discarded.
// Static --------------------------------------------------------
/** Our singleton instance */
private static TimeoutFactory singleton;
/** Number of TimeoutFactories created */
private static int timeoutFactoriesCount = 0;
/** The default threadpool used to execute timeouts */
private static BasicThreadPool DEFAULT_TP = new BasicThreadPool("Timeouts");
static {
DEFAULT_TP.setBlockingMode(BlockingMode.RUN);
}
/** Lazy constructions of the TimeoutFactory singleton */
private synchronized static TimeoutFactory getSingleton() {
if (singleton == null) {
singleton = new TimeoutFactory(DEFAULT_TP);
}
return singleton;
}
// Private data --------------------------------------------------
/** Used for graceful exiting */
private boolean cancelled;
/** The daemon thread that dequeues timeouts tasks and issues
them for execution to the thread pool */
private Thread workerThread;
/** Per TimeoutFactory thread pool used to execute timeouts */
private ThreadPool threadPool;
/** Linked list of free TimeoutImpl instances. */
private TimeoutImpl freeList;
/** The size of the timeout queue. */
private int size;
/**
* Our priority queue.
*
* This is a balanced binary tree. If nonempty, the root is at index 1,
* and all nodes are at indices 1..size. Nodes with index greater than
* size are null. Index 0 is never used.
* Children of the node at index <code>j</code> are at <code>j*2</code>
* and <code>j*2+1</code>. The children of a node always fire the timeout
* no earlier than the node.
*
*
* Or, more formally:
*
* Only indices <code>1</code>..<code>size</code> of this array are used.
* All other indices contain the null reference.
* This array represent a balanced binary tree.
*
* If <code>size</code> is <code>0</code> the tree is empty, otherwise
* the root of the tree is at index <code>1</code>.
*
* Given an arbitrary node at index <code>n</code> that is not the root
* node, the parent node of <code>n</code> is at index <code>n/2</code>.
*
* Given an arbitrary node at index <code>n</code>; if
* <code>2*n <= size</code> the node at <code>n</code> has its left child
* at index <code>2*n</code>, otherwise the node at <code>n</code> has
* no left child.
*
* Given an arbitrary node at index <code>n</code>; if
* <code>2*n+1 <= size</code> the node at <code>n</code> has its right child
* at index <code>2*n+1</code>, otherwise the node at <code>n</code> has
* no right child.
*
* The priority function is called T. Given a node <code>n</code>,
* <code>T(n)</code> denotes the absolute time (in milliseconds since
* the epoch) that the timeout for node <code>n</code> should happen.
* Smaller values of <code>T</code> means higher priority.
*
* The tree satisfies the following invariant:
* <i>
* For any node <code>n</code> in the tree:
* If node <code>n</code> has a left child <code>l</code>,
* <code>T(n) <= T(l)</code>.
* If node <code>n</code> has a right child <code>r</code>,
* <code>T(n) <= T(r)</code>.
* </i>
*
*
* The invariant may be temporarily broken while executing synchronized
* on <code>this</code> instance, but is always reestablished before
* leaving the synchronized code.
*
* The node at index <code>1</code> is always the first node to timeout,
* as can be deduced from the invariant.
*
* For the following algorithm pseudocode, the operation
* <code>swap(n,m)</code> denotes the exchange of the nodes at indices
* <code>n</code> and <code>m</code> in the tree.
*
* Insertion of a new node happend as follows:
* <pre>
* IF size = q.length THEN
* "expand q array to be larger";
* ENDIF
* size <- size + 1;
* q[size] <- "new node";
* n <- size;
* WHILE n > 1 AND T(n/2) > T(n) DO
* swap(n/2, n);
* n <- n/2;
* ENDWHILE
* </pre>
* Proof that this insertion algorithm respects the invariant is left to
* the interested reader.
*
* The removal algorithm is a bit more complicated. To remove the node
* at index <code>n</code>:
* <pre>
* swap(n, size);
* size <- size - 1;
* IF n > 1 AND T(n/2) > T(n) THEN
* WHILE n > 1 AND T(n/2) > T(n) DO
* swap(n/2, n);
* n <- n/2;
* ENDWHILE
* ELSE
* WHILE 2*n <= size DO
* IF 2*n+1 <= size THEN
* // Both children present
* IF T(2*n) <= T(2*n+1) THEN
* IF T(n) <= T(2*n) THEN
* EXIT;
* ENDIF
* swap(n, 2*n);
* n <- 2*n;
* ELSE
* IF T(n) <= T(2*n+1) THEN
* EXIT;
* ENDIF
* swap(n, 2*n+1);
* n <- 2*n+1;
* ENDIF
* ELSE
* // Only left child, right child not present.
* IF T(n) <= T(2*n) THEN
* EXIT;
* ENDIF
* swap(n, 2*n);
* n <- 2*n;
* ENDIF
* ENDWHILE
* ENDIF
* </pre>
* Proof that this removal algorithm respects the invariant is left to
* the interested reader. Really, I am not going to prove it here.
*
* If you are interested, you can find this data structure and its
* associated operations in most textbooks on algorithmics.
*
* @see checkTree
*/
private TimeoutImpl[] q;
// Public API ----------------------------------------------------
/**
* Schedules a new timeout using the singleton TimeoutFactory
*/
static public Timeout createTimeout(long time, TimeoutTarget target) {
return getSingleton().schedule(time, target);
}
/**
* Constructs a new TimeoutFactory that uses the provided ThreadPool
*/
public TimeoutFactory(ThreadPool threadPool) {
this.threadPool = threadPool;
q = new TimeoutImpl[16];
freeList = null;
size = 0;
// setup the workerThread
workerThread = new Thread("TimeoutFactory-" + timeoutFactoriesCount++) {
@Override
public void run() {
doWork();
}
};
workerThread.setDaemon(true);
workerThread.start();
}
/**
* Constructs a new TimeoutFactory that uses the default thread pool
*/
public TimeoutFactory() {
this(DEFAULT_TP);
}
/**
* Schedules a new timeout.
*
* @param time absolute time
* @param target target to fire
*/
public Timeout schedule(long time, TimeoutTarget target) {
if (cancelled == true)
throw new IllegalStateException("TimeoutFactory has been cancelled");
if (time < 0)
throw new IllegalArgumentException("Negative time");
if (target == null)
throw new IllegalArgumentException("Null timeout target");
return newTimeout(time, target);
}
/**
* Schedules a new timeout.
*
* @param time absolute time
* @param run runnable to run
*/
public Timeout schedule(long time, Runnable run) {
return schedule(time, new TimeoutTargetImpl(run));
}
/**
* Cancels all submitted tasks, stops the worker
* thread and clean-ups everything except for the
* thread pool. Scheduling new timeouts after cancel
* is called results in a IllegalStateException.
*/
public void cancel() {
// obviously the singleton TimeoutFactory cannot
// be cancelled since its reference is not accessible
// let the worker thread cleanup
cancelled = true;
// signal the worker if idle or waiting for the next timeout
synchronized (this) {
notify();
}
}
/**
* Returns true if the TimeoutFactory has been cancelled,
* false if it is operational (i.e. accepts timeout schedules).
*/
public boolean isCancelled() {
return cancelled;
}
/**
* Swap two nodes in the tree.
*/
private void swap(int a, int b) {
// INV: assertExpr(a > 0);
// INV: assertExpr(a <= size);
// INV: assertExpr(b > 0);
// INV: assertExpr(b <= size);
// INV: assertExpr(q[a] != null);
// INV: assertExpr(q[b] != null);
// INV: assertExpr(q[a].index == a);
// INV: assertExpr(q[b].index == b);
TimeoutImpl temp = q[a];
q[a] = q[b];
q[a].index = a;
q[b] = temp;
q[b].index = b;
}
/**
* A new node has been added at index <code>index</code>.
* Normalize the tree by moving the new node up the tree.
*
* @return True iff the tree was modified.
*/
private boolean normalizeUp(int index) {
// INV: assertExpr(index > 0);
// INV: assertExpr(index <= size);
// INV: assertExpr(q[index] != null);
if (index == 1)
return false; // at root
boolean ret = false;
long t = q[index].time;
int p = index >> 1;
while (q[p].time > t) {
// INV: assertExpr(q[index].time == t);
swap(p, index);
ret = true;
if (p == 1)
break; // at root
index = p;
p >>= 1;
}
return ret;
}
/**
* Remove a node from the tree and normalize.
*
* @return The removed node.
*/
private TimeoutImpl removeNode(int index) {
// INV: assertExpr(index > 0);
// INV: assertExpr(index <= size);
TimeoutImpl res = q[index];
// INV: assertExpr(res != null);
// INV: assertExpr(res.index == index);
if (index == size) {
--size;
q[index] = null;
return res;
}
swap(index, size); // Exchange removed node with last leaf node
--size;
// INV: assertExpr(res.index == size + 1);
q[res.index] = null;
if (normalizeUp(index))
return res; // Node moved up, so it shouldn't move down
long t = q[index].time;
int c = index << 1;
while (c <= size) {
// INV: assertExpr(q[index].time == t);
TimeoutImpl l = q[c];
// INV: assertExpr(l != null);
// INV: assertExpr(l.index == c);
if (c + 1 <= size) {
// two children, swap with smallest
TimeoutImpl r = q[c + 1];
// INV: assertExpr(r != null);
// INV: assertExpr(r.index == c+1);
if (l.time <= r.time) {
if (t <= l.time)
break; // done
swap(index, c);
index = c;
} else {
if (t <= r.time)
break; // done
swap(index, c + 1);
index = c + 1;
}
} else { // one child
if (t <= l.time)
break; // done
swap(index, c);
index = c;
}
c = index << 1;
}
return res;
}
/**
* Create a new timeout.
*/
private synchronized Timeout newTimeout(long time, TimeoutTarget target) {
// INV: checkTree();
// INV: assertExpr(size < q.length);
if (++size == q.length) {
TimeoutImpl[] newQ = new TimeoutImpl[2 * q.length];
System.arraycopy(q, 0, newQ, 0, q.length);
q = newQ;
}
// INV: assertExpr(size < q.length);
// INV: assertExpr(q[size] == null);
TimeoutImpl timeout;
if (freeList != null) {
timeout = q[size] = freeList;
freeList = timeout.nextFree;
timeout.nextFree = null;
// INV: checkFreeList();
// INV: assertExpr(timeout.index == TimeoutImpl.DONE);
} else
timeout = q[size] = new TimeoutImpl();
timeout.index = size;
timeout.time = time;
timeout.target = target;
normalizeUp(size);
if (timeout.index == 1)
notify();
// INV: checkTree();
return timeout;
}
/**
* Cancel a timeout.
*/
private boolean dropTimeout(TimeoutImpl timeout) {
synchronized (this) {
if (timeout.index > 0) {
// Active timeout, remove it.
// INV: assertExpr(q[timeout.index] == timeout);
// INV: checkTree();
removeNode(timeout.index);
// INV: checkTree();
timeout.index = TimeoutImpl.DONE;
timeout.nextFree = freeList;
freeList = timeout;
// INV: checkFreeList();
// execution cancelled
return true;
} else {
// has already been executed (DONE) or
// is currently executing (TIMEOUT)
return false;
}
}
}
/**
* Timeout worker method.
*/
private void doWork() {
while (!cancelled) {
TimeoutImpl work = null;
// Look for work
synchronized (this) {
if (size == 0 && !cancelled) {
try {
wait();
} catch (InterruptedException ex) {
}
} else {
long now = System.currentTimeMillis();
if (q[1].time > now && !cancelled) {
try {
wait(q[1].time - now);
} catch (InterruptedException ex) {
}
}
if (size > 0 && q[1].time <= System.currentTimeMillis() && !cancelled) {
work = removeNode(1);
work.index = TimeoutImpl.TIMEOUT;
}
}
}
// Do work, if any
if (work != null) {
// Wrap the TimeoutImpl with a runnable that invokes the target callback
TimeoutWorker worker = new TimeoutWorker(work);
try {
threadPool.run(worker);
} catch (Throwable t) {
// protect the worker thread from pool enqueue errors
ThrowableHandler.add(ThrowableHandler.Type.ERROR, t);
}
synchronized (work) {
// maybe we should have a state POOL_ENQUEUE_FAILED
work.index = TimeoutImpl.DONE;
}
}
}
// TimeoutFactory was cancelled
cleanup();
}
/**
* Cleanup everything; threadPool needs stop()
*/
private void cleanup() {
// clean free list
freeList = cleanupTimeoutImpl(freeList);
// cleanup queue
for (int i = 1; i <= size; i++) {
q[i] = cleanupTimeoutImpl(q[i]);
}
q = null;
threadPool = null;
workerThread = null;
}
/**
* Recursive cleanup of a TimeoutImpl
* @return null
*/
private TimeoutImpl cleanupTimeoutImpl(TimeoutImpl timeout) {
if (timeout != null) {
timeout.target = null;
timeout.nextFree = cleanupTimeoutImpl(timeout.nextFree);
}
return null;
}
// Private Inner Classes -----------------------------------------
/**
* Our private Timeout implementation.
*/
private class TimeoutImpl implements Timeout {
static final int DONE = -1; // done, may be finalized or reused
static final int TIMEOUT = -2; // target being called
int index; // index in queue, or one of constants above.
long time; // time to fire
TimeoutTarget target; // target to fire at
TimeoutImpl nextFree; // next on free list
public boolean cancel() {
return TimeoutFactory.this.dropTimeout(this);
}
}
/**
* A runnable that fires the timeout callback
*/
private static class TimeoutWorker implements Runnable {
private final TimeoutImpl work;
/**
* Create a new instance.
*
* @param work The timeout that should be fired.
*/
TimeoutWorker(TimeoutImpl work) {
this.work = work;
}
/**
* Override to fire the timeout.
*/
public void run() {
try {
work.target.timedOut(work);
} catch (Throwable t) {
// protect the thread pool thread from receiving this error
ThrowableHandler.add(ThrowableHandler.Type.ERROR, t);
}
synchronized (work) {
// maybe we should have a state EXECUTION_FAILED
work.index = TimeoutImpl.DONE;
}
}
}
/**
* Simple TimeoutTarget implementation that wraps a Runnable
*/
private static class TimeoutTargetImpl implements TimeoutTarget {
Runnable runnable;
TimeoutTargetImpl(Runnable runnable) {
this.runnable = runnable;
}
public void timedOut(Timeout ignored) {
runnable.run();
}
}
}