/* * Quasar: lightweight threads and actors for the JVM. * Copyright (c) 2013-2014, 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.concurrent.util.MapUtil; import co.paralleluniverse.fibers.SuspendExecution; import co.paralleluniverse.strands.Strand; import co.paralleluniverse.strands.channels.SendPort; import co.paralleluniverse.strands.queues.QueueCapacityExceededException; import java.util.Collections; import java.util.Iterator; import java.util.Set; /** * An {@link ActorRef} which is not backed by any actual {@link Actor}. * Instead, this "fake actor" only has a channel that serves as a mailbox, but not no {@link Actor#doRun() doRun} method, or a private strand. * * @author pron */ public abstract class FakeActor<Message> extends ActorImpl<Message> { private static final Throwable NATURAL = new Throwable(); private final Set<LifecycleListener> lifecycleListeners = Collections.newSetFromMap(MapUtil.<LifecycleListener, Boolean>newConcurrentHashMap()); private final Set<ActorImpl> observed = Collections.newSetFromMap(MapUtil.<ActorImpl, Boolean>newConcurrentHashMap()); private volatile Throwable deathCause; public FakeActor(String name, SendPort<Message> mailbox) { super(name, (SendPort<Object>) mailbox, null); } /** * All messages sent to the mailbox are passed to this method. If this method returns a non-null value, this value will be returned * from the {@code receive} methods. If it returns {@code null}, then {@code receive} will keep waiting. * <p/> * By default, this message passes all {@link LifecycleMessage} messages to {@link #handleLifecycleMessage(LifecycleMessage) handleLifecycleMessage}, while * other messages are returned (and will be returned by {@code receive}. * * @param m the message */ protected Message filterMessage(Object m) { if (m instanceof LifecycleMessage) { return handleLifecycleMessage((LifecycleMessage) m); } return (Message) m; } @Override public boolean trySend(Message message) { record(1, "ActorRef", "trySend", "Sending %s -> %s", message, this); Message msg = filterMessage(message); if (msg == null) return true; if (mailbox().trySend(msg)) return true; record(1, "ActorRef", "trySend", "Message not sent. Mailbox is not ready."); return false; } /** * For internal use * * @param message */ @Override protected void internalSend(Object message) throws SuspendExecution { record(1, "ActorRef", "send", "Sending %s -> %s", message, this); Message msg = filterMessage(message); if (msg == null) return; try { mailbox().send(message); } catch (InterruptedException e) { Strand.currentStrand().interrupt(); } } @Override protected void internalSendNonSuspendable(Object message) { record(1, "ActorRef", "internalSendNonSuspendable", "Sending %s -> %s", message, this); Message msg = filterMessage(message); if (msg == null) return; if (!mailbox().trySend(msg)) throw new QueueCapacityExceededException(); } @Override protected void addLifecycleListener(LifecycleListener listener) { final Throwable cause = getDeathCause(); if (isDone()) { listener.dead(ref(), cause); return; } lifecycleListeners.add(listener); if (isDone()) listener.dead(ref(), cause); } /** * Returns this actor's cause of death * * @return the {@link Throwable} that caused this actor's death, or {@code null} if it died by natural causes, or if it not dead. */ protected final Throwable getDeathCause() { return deathCause == NATURAL ? null : deathCause; } /** * Tests whether this fake actor has terminated. */ protected final boolean isDone() { return deathCause != null; } @Override protected void removeLifecycleListener(LifecycleListener listener) { lifecycleListeners.remove(listener); } @Override protected void removeObserverListeners(ActorRef actor) { for (Iterator<LifecycleListener> it = lifecycleListeners.iterator(); it.hasNext();) { LifecycleListener lifecycleListener = it.next(); if (lifecycleListener instanceof ActorLifecycleListener) if (((ActorLifecycleListener) lifecycleListener).getObserver().equals(actor)) it.remove(); } } /** * Makes this fake actor watch another actor. * * When the other actor dies, this actor receives an {@link ExitMessage}, that is * handled by {@link #handleLifecycleMessage(LifecycleMessage) handleLifecycleMessage}. This message does not cause an exception to be thrown, * unlike the case where it is received as a result of a linked actor's death. * <p/> * Unlike a link, a watch is asymmetric, and it is also composable, namely, calling this method twice with the same argument would result in two different values * returned, and in an {@link ExitMessage} to be received twice. * * @param other the other actor * @return a {@code watchId} object that identifies this watch in messages, and used to remove the watch by the {@link #unwatch(ActorRef, Object) unwatch} method. * @see #unwatch(ActorRef, Object) */ public final Object watch(ActorRef other) { final Object id = ActorUtil.randtag(); final ActorImpl other1 = getActorRefImpl(other); final LifecycleListener listener = new ActorLifecycleListener(ref(), id); record(1, "Actor", "watch", "Actor %s to watch %s (listener: %s)", this, other1, listener); other1.addLifecycleListener(listener); observed.add(other1); return id; } /** * Un-watches another actor. * * @param other the other actor * @param watchId the object returned from the call to {@link #watch(ActorRef) watch(other)} * @see #watch(ActorRef) */ public final void unwatch(ActorRef other, Object watchId) { final ActorImpl other1 = getActorRefImpl(other); final LifecycleListener listener = new ActorLifecycleListener(ref(), watchId); record(1, "Actor", "unwatch", "Actor %s to stop watching %s (listener: %s)", this, other1, listener); other1.removeLifecycleListener(listener); observed.remove(getActorRefImpl(other)); } protected abstract Message handleLifecycleMessage(LifecycleMessage m); protected void die(Throwable cause) { record(1, "Actor", "die", "Actor %s is dying of cause %s", this, cause); this.deathCause = (cause == null ? NATURAL : cause); 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().getImpl().removeObserverListeners(ref()); } } // avoid memory leaks: lifecycleListeners.clear(); for (ActorImpl a : observed) a.removeObserverListeners(ref()); observed.clear(); } /////////// Serialization /////////////////////////////////// protected final Object writeReplace() throws java.io.ObjectStreamException { return RemoteActorProxyFactoryService.create(ref(), null); } }