/*
* Copyright (c) 2013-2016, 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.fibers.SuspendExecution;
import co.paralleluniverse.strands.Timeout;
import co.paralleluniverse.strands.queues.QueueIterator;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Performs selective receive on behalf of an actor.
*
* @author pron
*/
public class SelectiveReceiveHelper<Message> implements java.io.Serializable {
private transient Actor<Message, ?> actor;
private int currentMessageIndex = -1; // this works because channel is single-consumer
/**
* Creates a {@code SelectiveReceiveHelper} to add selective receive to an actor
*
* @param actor the actor
*/
public SelectiveReceiveHelper(Actor<Message, ?> actor) {
if (actor == null)
throw new NullPointerException("actor is null");
this.actor = actor;
}
/**
* used only during deserialization
*/
public void setActor(Actor<Message, ?> actor) {
this.actor = actor;
}
/**
* Performs a selective receive. This method blocks until a message that is {@link MessageProcessor#process(java.lang.Object) selected} by
* the given {@link MessageProcessor} is available in the mailbox, and returns the value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param <T> The type of the returned value
* @param proc performs the selection.
* @return The non-null value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}
* @throws InterruptedException
*/
public final <T> T receive(MessageProcessor<? super Message, T> proc) throws SuspendExecution, InterruptedException {
try {
return receive(0, null, proc);
} catch (TimeoutException e) {
throw new AssertionError(e);
}
}
/**
* Performs a selective receive. This method blocks (but for no longer than the given timeout) until a message that is
* {@link MessageProcessor#process(java.lang.Object) selected} by the given {@link MessageProcessor} is available in the mailbox,
* and returns the value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}.
* If the given timeout expires, this method returns {@code null}.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param <T> The type of the returned value
* @param timeout the duration to wait for a matching message to arrive.
* @param unit timeout's time unit.
* @param proc performs the selection.
* @return The non-null value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}, or {@code null} if the timeout expired.
* @throws InterruptedException
*/
public final <T> T receive(long timeout, TimeUnit unit, MessageProcessor<? super Message, T> proc) throws TimeoutException, SuspendExecution, InterruptedException {
assert Actor.currentActor() == null || Actor.currentActor() == actor;
final Mailbox<Object> mailbox = actor.mailbox();
actor.checkThrownIn0();
mailbox.maybeSetCurrentStrandAsOwner();
final long start = timeout > 0 ? System.nanoTime() : 0;
long now;
long left = unit != null ? unit.toNanos(timeout) : 0;
final long deadline = start + left;
actor.monitorResetSkippedMessages();
QueueIterator<Object> it = mailbox.queue().iterator();
for (int i = 0;; i++) {
if (actor.flightRecorder != null)
actor.record(1, "SelctiveReceiveHelper", "receive", "%s waiting for a message. %s", this, timeout > 0 ? "millis left: " + TimeUnit.MILLISECONDS.convert(left, TimeUnit.NANOSECONDS) : "");
mailbox.lock();
if (it.hasNext()) {
final Object m;
try {
m = it.next();
} finally {
mailbox.unlock();
}
if (i == currentMessageIndex) {
it.remove();
i--;
currentMessageIndex = -1;
continue;
}
actor.record(1, "SelctiveReceiveHelper", "receive", "Received %s <- %s", this, m);
actor.monitorAddMessage();
if (m instanceof ExitMessage) {
final ExitMessage em = (ExitMessage) m;
if (em.getWatch() == null) {
// Delay all lifecycle messages except link death signals
it.remove();
i--;
handleLifecycleMessage((LifecycleMessage) m);
}
} else {
final Message msg = (Message) m;
currentMessageIndex = i;
try {
T res = proc.process(msg);
if (res != null) {
if (currentMessageIndex == i) // another call to receive from within the processor may have deleted msg
it.remove();
return res;
}
} catch (Exception e) {
if (currentMessageIndex == i) // another call to receive from within the processor may have deleted msg
it.remove();
throw e;
} finally {
currentMessageIndex = -1;
}
actor.record(1, "SelctiveReceiveHelper", "receive", "%s skipped %s", this, m);
actor.monitorSkippedMessage();
}
} else {
try {
if (unit == null)
mailbox.await(i);
else if (timeout > 0) {
mailbox.await(i, left, TimeUnit.NANOSECONDS);
now = System.nanoTime();
left = deadline - now;
if (left <= 0) {
actor.record(1, "Actor", "receive", "%s timed out.", this);
throw new TimeoutException();
}
} else {
return null;
}
} finally {
mailbox.unlock();
}
}
}
}
/**
* Performs a selective receive. This method blocks (but for no longer than the given timeout) until a message that is
* {@link MessageProcessor#process(java.lang.Object) selected} by the given {@link MessageProcessor} is available in the mailbox,
* and returns the value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}.
* If the given timeout expires, this method returns {@code null}.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param <T> The type of the returned value
* @param timeout the method will not block for longer than the amount remaining in the {@link Timeout}
* @param proc performs the selection.
* @return The non-null value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}, or {@code null} if the timeout expired.
* @throws InterruptedException
*/
public final <T> T receive(Timeout timeout, MessageProcessor<? super Message, T> proc) throws TimeoutException, SuspendExecution, InterruptedException {
return receive(timeout.nanosLeft(), TimeUnit.NANOSECONDS, proc);
}
/**
* Tries to perform a selective receive. If a message {@link MessageProcessor#process(java.lang.Object) selected} by
* the given {@link MessageProcessor} is immediately available in the mailbox, returns the value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}.
* This method never blocks.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param <T> The type of the returned value
* @param proc performs the selection.
* @return The non-null value returned by {@link MessageProcessor#process(java.lang.Object) MessageProcessor.process}, or {@code null} if no message was slected.
* @throws InterruptedException
*/
public final <T> T tryReceive(MessageProcessor<? super Message, T> proc) {
try {
return receive(0, TimeUnit.NANOSECONDS, proc);
} catch (TimeoutException e) {
throw new AssertionError(e);
} catch (SuspendExecution | InterruptedException e) {
throw new AssertionError();
}
}
/**
* Creates a {@link MessageProcessor} that selects messages of the given class.
*
* @param <M>
* @param <Message>
* @param type The class of the messages to select.
* @return a new {@link MessageProcessor} that selects messages of the given class.
*/
public static <Message, M extends Message> MessageProcessor<Message, M> ofType(final Class<M> type) {
return new MessageProcessor<Message, M>() {
@Override
public M process(Message m) {
return type.isInstance(m) ? type.cast(m) : null;
}
};
}
/**
* Performs a selective receive based on type. This method blocks (but for no longer than the given timeout) until a message of the given type
* is available in the mailbox, and returns it. If the given timeout expires, this method returns {@code null}.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param <M> The type of the returned value
* @param timeout the duration to wait for a matching message to arrive.
* @param unit timeout's time unit.
* @param type the type of the messages to select
* @return The next message of the wanted type, or {@code null} if the timeout expires.
* @throws SuspendExecution
* @throws InterruptedException
*/
public final <M extends Message> M receive(long timeout, TimeUnit unit, final Class<M> type) throws SuspendExecution, InterruptedException, TimeoutException {
return receive(timeout, unit, ofType(type));
}
/**
* Performs a selective receive based on type. This method blocks (but for no longer than the given timeout) until a message of the given type
* is available in the mailbox, and returns it. If the given timeout expires, this method returns {@code null}.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param <M> The type of the returned value
* @param timeout the method will not block for longer than the amount remaining in the {@link Timeout}
* @param type the type of the messages to select
* @return The next message of the wanted type, or {@code null} if the timeout expires.
* @throws SuspendExecution
* @throws InterruptedException
*/
public final <M extends Message> M receive(Timeout timeout, final Class<M> type) throws SuspendExecution, InterruptedException, TimeoutException {
return receive(timeout.nanosLeft(), TimeUnit.NANOSECONDS, type);
}
/**
* Performs a selective receive based on type. This method blocks until a message of the given type is available in the mailbox,
* and returns it.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param <M> The type of the returned value
* @param type the type of the messages to select
* @return The next message of the wanted type.
* @throws InterruptedException
*/
public final <M extends Message> M receive(final Class<M> type) throws SuspendExecution, InterruptedException {
try {
return receive(0, null, type);
} catch (TimeoutException ex) {
throw new AssertionError();
}
}
/**
* Tries to performs a selective receive based on type. If a message of the given type is immediately found in the mailbox, it is returned.
* Otherwise this method returns {@code null}.
* This method never blocks.
* <p/>
* Messages that are not selected, are temporarily skipped. They will remain in the mailbox until another call to receive (selective or
* non-selective) retrieves them.
*
* @param <M> The type of the returned value
* @param type the type of the messages to select
* @return The next message of the wanted type if immediately found; {@code null} otherwise.
*/
public final <M extends Message> M tryReceive(final Class<M> type) {
return tryReceive(ofType(type));
}
protected void handleLifecycleMessage(LifecycleMessage m) {
actor.handleLifecycleMessage(m);
}
}