/**
* Copyright (c) Rich Hickey. All rights reserved.
* The use and distribution terms for this software are covered by the
* Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
* which can be found in the file epl-v10.html at the root of this distribution.
* By using this software in any fashion, you are agreeing to be bound by
* the terms of this license.
* You must not remove this notice, or any other, from this software.
**/
/* rich Nov 17, 2007 */
package clojure.lang;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Map;
public class Agent extends ARef {
volatile Object state;
AtomicReference<IPersistentStack> q = new AtomicReference(PersistentQueue.EMPTY);
volatile ISeq errors = null;
final public static ExecutorService pooledExecutor =
Executors.newFixedThreadPool(2 + Runtime.getRuntime().availableProcessors());
final public static ExecutorService soloExecutor = Executors.newCachedThreadPool();
final static ThreadLocal<IPersistentVector> nested = new ThreadLocal<IPersistentVector>();
public static void shutdown(){
soloExecutor.shutdown();
pooledExecutor.shutdown();
}
static class Action implements Runnable{
final Agent agent;
final IFn fn;
final ISeq args;
final boolean solo;
public Action(Agent agent, IFn fn, ISeq args, boolean solo){
this.agent = agent;
this.args = args;
this.fn = fn;
this.solo = solo;
}
void execute(){
if(solo)
soloExecutor.execute(this);
else
pooledExecutor.execute(this);
}
static void doRun(Action action){
try
{
Var.pushThreadBindings(RT.map(RT.AGENT, action.agent));
nested.set(PersistentVector.EMPTY);
boolean hadError = false;
try
{
Object oldval = action.agent.state;
Object newval = action.fn.applyTo(RT.cons(action.agent.state, action.args));
action.agent.setState(newval);
action.agent.notifyWatches(oldval,newval);
}
catch(Throwable e)
{
//todo report/callback
action.agent.errors = RT.cons(e, action.agent.errors);
hadError = true;
}
if(!hadError)
{
releasePendingSends();
}
boolean popped = false;
IPersistentStack next = null;
while(!popped)
{
IPersistentStack prior = action.agent.q.get();
next = prior.pop();
popped = action.agent.q.compareAndSet(prior, next);
}
if(next.count() > 0)
((Action) next.peek()).execute();
}
finally
{
nested.set(null);
Var.popThreadBindings();
}
}
public void run(){
doRun(this);
}
}
public Agent(Object state) throws Exception{
this(state,null);
}
public Agent(Object state, IPersistentMap meta) throws Exception {
super(meta);
setState(state);
}
boolean setState(Object newState) throws Exception{
validate(newState);
boolean ret = state != newState;
state = newState;
return ret;
}
public Object deref() throws Exception{
if(errors != null)
{
throw new Exception("Agent has errors", (Exception) RT.first(errors));
}
return state;
}
public ISeq getErrors(){
return errors;
}
public void clearErrors(){
errors = null;
}
public Object dispatch(IFn fn, ISeq args, boolean solo) {
if(errors != null)
{
throw new RuntimeException("Agent has errors", (Exception) RT.first(errors));
}
Action action = new Action(this, fn, args, solo);
dispatchAction(action);
return this;
}
static void dispatchAction(Action action){
LockingTransaction trans = LockingTransaction.getRunning();
if(trans != null)
trans.enqueue(action);
else if(nested.get() != null)
{
nested.set(nested.get().cons(action));
}
else
action.agent.enqueue(action);
}
void enqueue(Action action){
boolean queued = false;
IPersistentStack prior = null;
while(!queued)
{
prior = q.get();
queued = q.compareAndSet(prior, (IPersistentStack) prior.cons(action));
}
if(prior.count() == 0)
action.execute();
}
public int getQueueCount(){
return q.get().count();
}
static public int releasePendingSends(){
IPersistentVector sends = nested.get();
if(sends == null)
return 0;
for(int i=0;i<sends.count();i++)
{
Action a = (Action) sends.valAt(i);
a.agent.enqueue(a);
}
nested.set(PersistentVector.EMPTY);
return sends.count();
}
}