co.paralleluniverse.actors.Actor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of quasar-actors Show documentation
Show all versions of quasar-actors Show documentation
Fibers, Channels and Actors for the JVM
/*
* Quasar: lightweight threads and actors for the JVM.
* Copyright (C) 2013, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.actors;
import co.paralleluniverse.actors.ActorRefImpl.ActorLifecycleListener;
import co.paralleluniverse.common.monitoring.FlightRecorder;
import co.paralleluniverse.common.monitoring.FlightRecorderMessage;
import co.paralleluniverse.common.util.Debug;
import co.paralleluniverse.common.util.Objects;
import co.paralleluniverse.fibers.Fiber;
import co.paralleluniverse.fibers.FiberScheduler;
import co.paralleluniverse.fibers.Joinable;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.strands.Strand;
import co.paralleluniverse.strands.Stranded;
import co.paralleluniverse.strands.SuspendableCallable;
import co.paralleluniverse.strands.channels.ReceivePort;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import jsr166e.ConcurrentHashMapV8;
/**
*
* @author pron
*/
public abstract class Actor implements SuspendableCallable, Joinable, Stranded, ReceivePort {
private static final Throwable NATURAL = new Throwable();
private static final ThreadLocal currentActor = new ThreadLocal();
private Strand strand;
private final LocalActorRef ref;
private final ActorRef wrapperRef;
private final Set lifecycleListeners = Collections.newSetFromMap(new ConcurrentHashMapV8());
private final Set observed = Collections.newSetFromMap(new ConcurrentHashMapV8());
private volatile V result;
private volatile RuntimeException exception;
private volatile Throwable deathCause;
private volatile Object globalId;
private volatile ActorMonitor monitor;
private ActorSpec, Message, V> spec;
private Object aux;
protected transient final FlightRecorder flightRecorder;
@SuppressWarnings({"OverridableMethodCallInConstructor", "LeakingThisInConstructor"})
public Actor(String name, MailboxConfig mailboxConfig) {
this.ref = new LocalActorRef(this, name, new Mailbox(mailboxConfig));
mailbox().setActor(this);
this.flightRecorder = Debug.isDebug() ? Debug.getGlobalFlightRecorder() : null;
this.wrapperRef = makeRef(ref);
}
protected ActorRef makeRef(ActorRef ref) {
return ref;
}
public Actor(Strand strand, String name, MailboxConfig mailboxConfig) {
this(name, mailboxConfig);
if (strand != null)
setStrand(strand);
}
public String getName() {
return ref.getName();
}
public void setName(String name) {
myRef().setName(name);
}
private ActorRefImpl myRef() {
return ((ActorRefImpl) ref);
}
public ActorRef spawn(FiberScheduler scheduler) {
new Fiber(getName(), scheduler, this).start();
return ref();
}
public ActorRef spawn() {
new Fiber(getName(), this).start();
return ref();
}
public ActorRef spawnThread() {
new Thread(Strand.toRunnable(this), getName()).start();
return ref();
}
public static , Message, V> T newActor(Class clazz, Object... params) {
return newActor(ActorSpec.of(clazz, params));
}
public static , Message, V> T newActor(ActorSpec spec) {
return spec.build();
}
/**
* '
* Returns a "clone" of this actor, used by a {@link co.paralleluniverse.actors.behaviors.Supervisor supervisor} to restart this actor if it dies.
*
* If this actor is supervised by a {@link co.paralleluniverse.actors.behaviors.Supervisor supervisor} and was not created with the
* {@link #newActor(co.paralleluniverse.actors.ActorSpec) newActor} factory method, then this method should be overridden.
*
* @return A new LocalActor instance that's a clone of this.
*/
protected Actor reinstantiate() {
if (spec != null)
return newActor(spec);
else if (getClass().isAnonymousClass() && getClass().getSuperclass().equals(Actor.class))
return newActor(createSpecForAnonymousClass());
else
throw new RuntimeException("Actor " + this + " cannot be reinstantiated");
}
private ActorSpec, Message, V> createSpecForAnonymousClass() {
assert getClass().isAnonymousClass() && getClass().getSuperclass().equals(Actor.class);
Constructor> ctor = (Constructor>) getClass().getDeclaredConstructors()[0];
Object[] params = new Object[ctor.getParameterTypes().length];
for (int i = 0; i < params.length; i++) {
Class> type = ctor.getParameterTypes()[i];
if (String.class.equals(type))
params[i] = getName();
if (Integer.TYPE.equals(type))
params[i] = mailbox().capacity();
else
params[i] = type.isPrimitive() ? 0 : null;
}
return new ActorSpec, Message, V>(ctor, params);
}
void setSpec(ActorSpec, Message, V> spec) {
this.spec = spec;
}
Object getAux() {
return aux;
}
void setAux(Object aux) {
verifyInActor();
this.aux = aux;
}
@Override
public String toString() {
String className = getClass().getSimpleName();
if (className.isEmpty())
className = getClass().getName().substring(getClass().getPackage().getName().length() + 1);
return className + "@"
+ (getName() != null ? getName() : Integer.toHexString(System.identityHashCode(this)))
+ "[owner: " + systemToStringWithSimpleName(strand) + ']';
}
private static String systemToStringWithSimpleName(Object obj) {
return (obj == null ? "null" : obj.getClass().getSimpleName() + "@" + Objects.systemObjectId(obj));
}
final void interrupt() {
getStrand().interrupt();
}
public static Actor currentActor() {
final Fiber currentFiber = Fiber.currentFiber();
if (currentFiber == null)
return currentActor.get();
final SuspendableCallable target = currentFiber.getTarget();
if (target == null || !(target instanceof Actor))
return null;
return (Actor) target;
}
public ActorRef ref() {
if (!isStarted())
throw new IllegalStateException("Actor has not been started");
return wrapperRef;
}
protected ActorRef self() {
return ref();
}
@Override
public final void setStrand(Strand strand) {
if (strand == this.strand)
return;
if (this.strand != null)
throw new IllegalStateException("Strand already set to " + strand);
this.strand = strand;
if (strand != null && getName() == null)
setName(strand.getName());
mailbox().setStrand(strand);
}
@Override
public final Strand getStrand() {
return strand;
}
//
/////////// Mailbox methods ///////////////////////////////////
public final int getQueueLength() {
return mailbox().getQueueLength();
}
protected final Mailbox
//
/////////// Strand helpers ///////////////////////////////////
public final Actor start() {
record(1, "Actor", "start", "Starting actor %s", this);
strand.start();
return this;
}
@Override
public final V get() throws InterruptedException, ExecutionException {
if (strand instanceof Fiber)
return ((Fiber) strand).get();
else {
strand.join();
return result;
}
}
@Override
public final V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
if (strand instanceof Fiber)
return ((Fiber) strand).get(timeout, unit);
else {
strand.join(timeout, unit);
return result;
}
}
@Override
public final void join() throws ExecutionException, InterruptedException {
strand.join();
}
@Override
public final void join(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException {
strand.join(timeout, unit);
}
public final boolean isStarted() {
return strand != null && strand.getState().compareTo(Strand.State.STARTED) >= 0;
}
@Override
public final boolean isDone() {
return deathCause != null || strand.isTerminated();
}
protected final void verifyInActor() {
if (!isInActor())
throw new ConcurrencyException("Operation not called from within the actor (" + this + ", but called in " + currentActor() + ")");
}
protected final boolean isInActor() {
return (currentActor() == this);
}
//
//
/////////// Lifecycle ///////////////////////////////////
@Override
public final V run() throws InterruptedException, SuspendExecution {
if (strand == null)
setStrand(Strand.currentStrand());
JMXActorsMonitor.getInstance().actorStarted(ref);
if (!(strand instanceof Fiber))
currentActor.set(this);
try {
result = doRun();
die(null);
return result;
} catch (InterruptedException e) {
if (this.exception != null) {
die(exception);
throw exception;
}
die(e);
throw e;
} catch (Throwable t) {
if (t.getCause() instanceof InterruptedException) {
InterruptedException ie = (InterruptedException) t.getCause();
if (this.exception != null) {
die(exception);
throw exception;
}
die(ie);
throw ie;
}
die(t);
throw t;
} finally {
record(1, "Actor", "die", "Actor %s is now dead of %s", this, getDeathCause());
if (!(strand instanceof Fiber))
currentActor.set(null);
JMXActorsMonitor.getInstance().actorTerminated(ref);
}
}
protected abstract V doRun() throws InterruptedException, SuspendExecution;
protected void handleLifecycleMessage(LifecycleMessage m) {
record(1, "Actor", "handleLifecycleMessage", "%s got LifecycleMessage %s", this, m);
if (m instanceof ExitMessage) {
ExitMessage exit = (ExitMessage) m;
removeObserverListeners(getActorImpl(exit.getActor()));
if (exit.getWatch() == null)
throw new LifecycleException(m);
}
}
final void addLifecycleListener(LifecycleListener listener) {
final Throwable cause = getDeathCause();
if (isDone()) {
listener.dead(ref, cause);
return;
}
lifecycleListeners.add(listener);
if (isDone())
listener.dead(ref, cause);
}
void removeLifecycleListener(LifecycleListener listener) {
lifecycleListeners.remove(listener);
}
void removeObserverListeners(ActorRef actor) {
for (Iterator it = lifecycleListeners.iterator(); it.hasNext();) {
LifecycleListener lifecycleListener = it.next();
if (lifecycleListener instanceof ActorLifecycleListener)
if (((ActorLifecycleListener) lifecycleListener).getObserver().equals(actor))
it.remove();
}
}
protected final Throwable getDeathCause() {
return deathCause == NATURAL ? null : deathCause;
}
public final boolean isRegistered() {
return globalId != null;
}
Object getGlobalId() {
return globalId;
}
public final void throwIn(RuntimeException e) {
record(1, "Actor", "throwIn", "Exception %s thrown into actor %s", e, this);
this.exception = e; // last exception thrown in wins
strand.interrupt();
}
final void checkThrownIn() {
if (exception != null) {
record(1, "Actor", "checkThrownIn", "%s detected thrown in exception %s", this, exception);
exception.setStackTrace(new Throwable().getStackTrace());
throw exception;
}
}
private ActorRefImpl getActorImpl(ActorRef actor) {
while (actor instanceof ActorRefDelegate)
actor = ((ActorRefDelegate) actor).ref;
if (actor instanceof ActorRefImpl)
return (ActorRefImpl) actor;
else
throw new AssertionError("Actor " + actor + " is not an ActorImpl");
}
public final Actor link(ActorRef other1) {
final ActorRefImpl other = getActorImpl(other1);
record(1, "Actor", "link", "Linking actors %s, %s", this, other);
if (this.isDone()) {
other.getLifecycleListener().dead(ref, getDeathCause());
} else {
addLifecycleListener(other.getLifecycleListener());
other.addLifecycleListener(myRef().getLifecycleListener());
}
return this;
}
public final Actor unlink(ActorRef other1) {
final ActorRefImpl other = getActorImpl(other1);
record(1, "Actor", "unlink", "Uninking actors %s, %s", this, other);
removeLifecycleListener(other.getLifecycleListener());
other.removeLifecycleListener(myRef().getLifecycleListener());
return this;
}
public final Object watch(ActorRef other1) {
final Object id = ActorUtil.randtag();
final ActorRefImpl other = getActorImpl(other1);
final LifecycleListener listener = new ActorLifecycleListener(ref, id);
record(1, "Actor", "watch", "Actor %s to watch %s (listener: %s)", this, other, listener);
other.addLifecycleListener(listener);
observed.add(other);
return id;
}
public final void unwatch(ActorRef other1, Object watchId) {
final ActorRefImpl other = getActorImpl(other1);
final LifecycleListener listener = new ActorLifecycleListener(ref, watchId);
record(1, "Actor", "unwatch", "Actor %s to stop watching %s (listener: %s)", this, other, listener);
other.removeLifecycleListener(listener);
observed.remove(getActorImpl(other1));
}
public final Actor register(String name) {
if (getName() != null && !name.equals(name))
throw new RegistrationException("Cannot register actor named " + getName() + " under a different name (" + name + ")");
setName(name);
return register();
}
public final Actor register() {
record(1, "Actor", "register", "Registering actor %s as %s", this, getName());
this.globalId = ActorRegistry.register(this);
return this;
}
public final Actor unregister() {
if (!isRegistered())
return this;
record(1, "Actor", "unregister", "Unregistering actor %s (name: %s)", this, getName());
if (getName() == null)
throw new IllegalArgumentException("name is null");
ActorRegistry.unregister(getName());
if (monitor != null)
this.monitor.setActor(null);
this.globalId = null;
return this;
}
private void die(Throwable cause) {
record(1, "Actor", "die", "Actor %s is dying of cause %s", this, cause);
this.deathCause = (cause == null ? NATURAL : cause);
monitorAddDeath(cause);
if (isRegistered())
unregister();
for (LifecycleListener listener : lifecycleListeners) {
record(1, "Actor", "die", "Actor %s notifying listener %s of death.", this, listener);
try {
listener.dead(ref, cause);
} catch (Exception e) {
record(1, "Actor", "die", "Actor %s notifying listener %s of death failed with excetpion %s", this, listener, e);
}
// avoid memory leak in links:
if (listener instanceof ActorLifecycleListener) {
ActorLifecycleListener l = (ActorLifecycleListener) listener;
if (l.getId() == null) // link
l.getObserver().removeObserverListeners(myRef());
}
}
// avoid memory leaks:
lifecycleListeners.clear();
for (ActorRefImpl a : observed)
a.removeObserverListeners(myRef());
observed.clear();
}
//
//
/////////// Monitoring ///////////////////////////////////
public final ActorMonitor monitor() {
if (monitor != null)
return monitor;
final String name = getName().toString().replaceAll(":", "");
this.monitor = new JMXActorMonitor(name);
monitor.setActor(ref);
return monitor;
}
public final void setMonitor(ActorMonitor monitor) {
if (this.monitor == monitor)
return;
if (this.monitor != null)
throw new RuntimeException("actor already has a monitor");
this.monitor = monitor;
monitor.setActor(ref);
}
public final void stopMonitor() {
if (monitor != null) {
monitor.shutdown();
this.monitor = null;
}
}
public final ActorMonitor getMonitor() {
return monitor;
}
protected final void monitorAddDeath(Object reason) {
if (monitor != null)
monitor.addDeath(reason);
}
protected final void monitorAddMessage() {
if (monitor != null)
monitor.addMessage();
}
protected final void monitorSkippedMessage() {
if (monitor != null)
monitor.skippedMessage();
}
protected final void monitorResetSkippedMessages() {
if (monitor != null)
monitor.resetSkippedMessages();
}
List getMailboxSnapshot() {
return mailbox().getSnapshot();
}
StackTraceElement[] getStackTrace() {
return strand.getStackTrace();
}
//
//
/////////// Recording ///////////////////////////////////
protected final boolean isRecordingLevel(int level) {
if (flightRecorder == null)
return false;
final FlightRecorder.ThreadRecorder recorder = flightRecorder.get();
if (recorder == null)
return false;
return recorder.recordsLevel(level);
}
protected final void record(int level, String clazz, String method, String format) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format);
}
protected final void record(int level, String clazz, String method, String format, Object arg1) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, arg1);
}
protected final void record(int level, String clazz, String method, String format, Object arg1, Object arg2) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, arg1, arg2);
}
protected final void record(int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, arg1, arg2, arg3);
}
protected final void record(int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3, Object arg4) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, arg1, arg2, arg3, arg4);
}
protected final void record(int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, arg1, arg2, arg3, arg4, arg5);
}
protected final void record(int level, String clazz, String method, String format, Object... args) {
if (flightRecorder != null)
record(flightRecorder.get(), level, clazz, method, format, args);
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, null));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object arg1) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, new Object[]{arg1}));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object arg1, Object arg2) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, new Object[]{arg1, arg2}));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, new Object[]{arg1, arg2, arg3}));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3, Object arg4) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, new Object[]{arg1, arg2, arg3, arg4}));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, new Object[]{arg1, arg2, arg3, arg4, arg5}));
}
private static void record(FlightRecorder.ThreadRecorder recorder, int level, String clazz, String method, String format, Object... args) {
if (recorder != null)
recorder.record(level, makeFlightRecorderMessage(recorder, clazz, method, format, args));
}
private static FlightRecorderMessage makeFlightRecorderMessage(FlightRecorder.ThreadRecorder recorder, String clazz, String method, String format, Object[] args) {
return new FlightRecorderMessage(clazz, method, format, args);
//return ((FlightRecorderMessageFactory) recorder.getAux()).makeFlightRecorderMessage(clazz, method, format, args);
}
//
}