/*
* 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.common.util.DelegatingEquals;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.strands.Timeout;
import co.paralleluniverse.strands.channels.Channels.OverflowPolicy;
import co.paralleluniverse.strands.channels.SendPort;
import co.paralleluniverse.strands.queues.QueueCapacityExceededException;
import java.lang.reflect.Field;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* An actor's external API (for use by code not part of the actor).
*
* @author pron
*/
public class ActorRef<Message> implements SendPort<Message>, java.io.Serializable {
private volatile ActorImpl<Message> impl;
protected ActorRef(ActorImpl<Message> impl) {
setImpl(impl);
}
protected ActorRef() {
}
public String getName() {
return getImpl().getName();
}
protected ActorImpl<Message> getImpl() {
return impl;
}
void setImpl(ActorImpl<Message> impl) {
this.impl = impl;
ActorRef<Message> r = impl.ref;
if (r != null && ActorRefDelegate.stripDelegates(r) != this)
throw new IllegalStateException("Actor " + impl + " already has a ref: " + ActorRefDelegate.stripDelegates(r));
}
/**
* Sends a message to the actor, possibly blocking until there's room available in the mailbox.
*
* If the mailbox is full, this method may block or silently drop the message.
* The behavior is determined by the mailbox's {@link co.paralleluniverse.strands.channels.Channels.OverflowPolicy OverflowPolicy}, set at construction time.
* However, unlike regular channels, this method never throws {@link co.paralleluniverse.strands.queues.QueueCapacityExceededException QueueCapacityExceededException}.
* If the mailbox overflows, and has been configured with the {@link co.paralleluniverse.strands.channels.Channels.OverflowPolicy#THROW THROW} policy,
* the exception will be thrown <i>into</i> the actor.
*
* @param message
* @throws SuspendExecution
*/
@Override
public void send(Message message) throws SuspendExecution {
try {
MutabilityTester.testMutability(message);
ActorImpl<Message> x = getImpl();
try {
x.internalSend(message);
} catch (QueueCapacityExceededException e) {
x.throwIn(e);
}
} catch (RuntimeException e) {
e.printStackTrace();
LostActor.instance.ref().send(message);
LostActor.instance.throwIn(e);
}
}
/**
* Sends a message to the actor, and attempts to schedule the actor's strand for immediate execution.
* This method may be called when a response message is expected from this actor; in this case, this method might provide
* better latency than {@link #send(java.lang.Object)}.
*
* @param message
* @throws SuspendExecution
*/
public void sendSync(Message message) throws SuspendExecution {
try {
MutabilityTester.testMutability(message);
getImpl().sendSync(message);
} catch (RuntimeException e) {
LostActor.instance.ref().sendSync(message);
LostActor.instance.throwIn(e);
}
}
/**
* Sends a message to the actor, possibly blocking until there's room available in the mailbox, but never longer than the
* specified timeout.
*
* If the mailbox is full, this method may block, throw an exception, silently drop the message, or displace an old message from
* the channel. The behavior is determined by the mailbox's {@link OverflowPolicy OverflowPolicy}, set at construction time.
* <p/>
* <b/>Currently, this behavior is not yet supported. The message will be sent using {@link #send(Object)} and the timeout argument
* will be disregarded</b>
*
* @param msg the message
* @param timeout the maximum duration this method is allowed to wait.
* @param unit the timeout's time unit
* @return {@code true} if the message has been sent successfully; {@code false} if the timeout has elapsed.
* @throws SuspendExecution
*/
@Override
public boolean send(Message message, long timeout, TimeUnit unit) throws SuspendExecution, InterruptedException {
send(message);
return true;
}
/**
* Sends a message to the actor, possibly blocking until there's room available in the mailbox, but never longer than the
* specified timeout.
*
* If the channel is full, this method may block, throw an exception, silently drop the message, or displace an old message from
* the channel. The behavior is determined by the mailbox's {@link OverflowPolicy OverflowPolicy}, set at construction time.
* <p/>
* <b/>Currently, this behavior is not yet supported. The message will be sent using {@link #send(Object)} and the timeout argument
* will be disregarded</b>
*
* @param msg the message
* @param timeout the method will not block for longer than the amount remaining in the {@link Timeout}
* @return {@code true} if the message has been sent successfully; {@code false} if the timeout has elapsed.
* @throws SuspendExecution
*/
@Override
public boolean send(Message message, Timeout timeout) throws SuspendExecution, InterruptedException {
send(message);
return true;
}
/**
* Sends a message to the actor if the channel has mailbox available. This method never blocks.
*
* @param msg the message
* @return {@code true} if the message has been sent; {@code false} otherwise.
*/
@Override
public boolean trySend(Message msg) {
try {
return getImpl().trySend(msg);
} catch (RuntimeException e) {
LostActor.instance.ref().trySend(msg);
LostActor.instance.throwIn(e);
return false;
}
}
/**
* This implementation throws {@code UnsupportedOperationException}.
*/
@Override
public void close() {
throw new UnsupportedOperationException();
}
/**
* This implementation throws {@code UnsupportedOperationException}.
*/
@Override
public void close(Throwable t) {
throw new UnsupportedOperationException();
}
/**
* Interrupts the actor's strand
*/
protected void interrupt() {
getImpl().interrupt();
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
if (obj instanceof DelegatingEquals)
return obj.equals(this);
if (getImpl() == null)
return false;
if (!(obj instanceof ActorRef))
return false;
ActorRef other = (ActorRef) obj;
return getImpl().equals(other.getImpl());
}
@Override
public int hashCode() {
return 581 + Objects.hashCode(getImpl());
}
@Override
public String toString() {
return "ActorRef@" + Integer.toHexString(System.identityHashCode(this)) + "{" + getImpl() + '}';
}
Object readResolve() {
if (impl == null)
return null;
ActorRef<Message> ref = ActorRefCanonicalizerService.getRef(impl, this);
if (impl.ref == null)
setRef(impl, ref);
return ref;
}
private static final Field actorImplRefField;
static {
try {
actorImplRefField = ActorImpl.class.getDeclaredField("ref");
actorImplRefField.setAccessible(true);
} catch (Exception e) {
throw new AssertionError(e);
}
}
private static <T> void setRef(ActorImpl<T> impl, ActorRef<T> ref) {
try {
actorImplRefField.set(impl, ref);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
}