/*
* Quasar: lightweight threads and actors for the JVM.
* 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.actors.behaviors.MessageSelector;
import co.paralleluniverse.common.test.TestUtil;
import co.paralleluniverse.common.util.Debug;
import co.paralleluniverse.common.util.Exceptions;
import co.paralleluniverse.fibers.Fiber;
import co.paralleluniverse.fibers.FiberForkJoinScheduler;
import co.paralleluniverse.fibers.FiberScheduler;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.strands.Strand;
import co.paralleluniverse.strands.StrandFactoryBuilder;
import co.paralleluniverse.strands.channels.Channels;
import co.paralleluniverse.strands.channels.SendPort;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.rules.TestRule;
/**
*
* @author pron
*/
public class ActorTest {
@Rule
public TestName name = new TestName();
@Rule
public TestRule watchman = TestUtil.WATCHMAN;
@BeforeClass
public static void beforeClass() {
Debug.dumpAfter(10000);
}
static final MailboxConfig mailboxConfig = new MailboxConfig(10, Channels.OverflowPolicy.THROW);
private FiberScheduler scheduler;
public ActorTest() {
scheduler = new FiberForkJoinScheduler("test", 4, null, false);
}
private <Message, V> Actor<Message, V> spawnActor(Actor<Message, V> actor) {
Fiber fiber = new Fiber("actor", scheduler, actor);
fiber.setUncaughtExceptionHandler(new Strand.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Strand s, Throwable e) {
e.printStackTrace();
throw Exceptions.rethrow(e);
}
});
fiber.start();
return actor;
}
@Test
public void whenActorThrowsExceptionThenGetThrowsIt() throws Exception {
Actor<Message, Integer> actor = spawnActor(new BasicActor<Message, Integer>(mailboxConfig) {
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
throw new RuntimeException("foo");
}
});
try {
actor.get();
fail();
} catch (ExecutionException e) {
assertThat(e.getCause(), instanceOf(RuntimeException.class));
assertThat(e.getCause().getMessage(), is("foo"));
}
}
@Test
public void whenActorThrowsExceptionThenGetThrowsItThreadActor() throws Exception {
Actor<Message, Integer> actor = new BasicActor<Message, Integer>(mailboxConfig) {
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
throw new RuntimeException("foo");
}
};
actor.spawnThread();
try {
actor.get();
fail();
} catch (ExecutionException e) {
assertThat(e.getCause(), instanceOf(RuntimeException.class));
assertThat(e.getCause().getMessage(), is("foo"));
}
}
@Test
public void whenActorReturnsValueThenGetReturnsIt() throws Exception {
Actor<Message, Integer> actor = spawnActor(new BasicActor<Message, Integer>(mailboxConfig) {
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
return 42;
}
});
assertThat(actor.get(), is(42));
}
@Test
public void whenActorReturnsValueThenGetReturnsItThreadActor() throws Exception {
Actor<Message, Integer> actor = new BasicActor<Message, Integer>(mailboxConfig) {
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
return 42;
}
};
actor.spawnThread();
assertThat(actor.get(), is(42));
}
@Test
public void testReceive() throws Exception {
ActorRef<Message> actor = new BasicActor<Message, Integer>(mailboxConfig) {
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
Message m = receive();
return m.num;
}
}.spawn();
actor.send(new Message(15));
assertThat(LocalActor.<Integer>get(actor), is(15));
}
@Test
public void testReceiveThreadActor() throws Exception {
ActorRef<Message> actor = new BasicActor<Message, Integer>(mailboxConfig) {
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
Message m = receive();
return m.num;
}
}.spawnThread();
actor.send(new Message(15));
assertThat(LocalActor.<Integer>get(actor), is(15));
}
@Test
public void testReceiveAfterSleep() throws Exception {
ActorRef<Message> actor = new BasicActor<Message, Integer>(mailboxConfig) {
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
Message m1 = receive();
Message m2 = receive();
return m1.num + m2.num;
}
}.spawn();
actor.send(new Message(25));
Thread.sleep(200);
actor.send(new Message(17));
assertThat(LocalActor.<Integer>get(actor), is(42));
}
@Test
public void testReceiveAfterSleepThreadActor() throws Exception {
ActorRef<Message> actor = new BasicActor<Message, Integer>(mailboxConfig) {
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
Message m1 = receive();
Message m2 = receive();
return m1.num + m2.num;
}
}.spawnThread();
actor.send(new Message(25));
Thread.sleep(200);
actor.send(new Message(17));
assertThat(LocalActor.<Integer>get(actor), is(42));
}
private class TypedReceiveA {
};
private class TypedReceiveB {
};
@Test
public void testTypedReceive() throws Exception {
Actor<Object, List<Object>> actor = spawnActor(new BasicActor<Object, List<Object>>(mailboxConfig) {
@Override
protected List<Object> doRun() throws InterruptedException, SuspendExecution {
List<Object> list = new ArrayList<>();
list.add(receive(TypedReceiveA.class));
list.add(receive(TypedReceiveB.class));
return list;
}
});
final TypedReceiveB typedReceiveB = new TypedReceiveB();
final TypedReceiveA typedReceiveA = new TypedReceiveA();
actor.ref().send(typedReceiveB);
Thread.sleep(2);
actor.ref().send(typedReceiveA);
assertThat(actor.get(500, TimeUnit.MILLISECONDS), equalTo(Arrays.asList(typedReceiveA, typedReceiveB)));
}
@Test
public void testSelectiveReceive() throws Exception {
Actor<ComplexMessage, List<Integer>> actor = spawnActor(new BasicActor<ComplexMessage, List<Integer>>(mailboxConfig) {
@Override
protected List<Integer> doRun() throws SuspendExecution, InterruptedException {
final List<Integer> list = new ArrayList<>();
for (int i = 0; i < 2; i++) {
receive(new MessageProcessor<ComplexMessage, ComplexMessage>() {
public ComplexMessage process(ComplexMessage m) throws SuspendExecution, InterruptedException {
switch (m.type) {
case FOO:
list.add(m.num);
receive(new MessageProcessor<ComplexMessage, ComplexMessage>() {
public ComplexMessage process(ComplexMessage m) throws SuspendExecution, InterruptedException {
switch (m.type) {
case BAZ:
list.add(m.num);
return m;
default:
return null;
}
}
});
return m;
case BAR:
list.add(m.num);
return m;
case BAZ:
fail();
default:
return null;
}
}
});
}
return list;
}
});
actor.ref().send(new ComplexMessage(ComplexMessage.Type.FOO, 1));
actor.ref().send(new ComplexMessage(ComplexMessage.Type.BAR, 2));
actor.ref().send(new ComplexMessage(ComplexMessage.Type.BAZ, 3));
assertThat(actor.get(), equalTo(Arrays.asList(1, 3, 2)));
}
@Test
public void testSelectiveReceiveMsgSelector() throws Exception {
Actor<Object, String> actor = spawnActor(new BasicActor<Object, String>(mailboxConfig) {
@Override
protected String doRun() throws SuspendExecution, InterruptedException {
return receive(MessageSelector.select().ofType(String.class));
}
});
actor.ref().send(1);
actor.ref().send("hello");
assertThat(actor.get(), equalTo("hello"));
}
@Test
public void testNestedSelectiveWithEqualMessage() throws Exception {
Actor<String, String> actor = spawnActor(new BasicActor<String, String>(mailboxConfig) {
@Override
protected String doRun() throws SuspendExecution, InterruptedException {
// return receive(a -> a + receive(b -> b));
return receive(new MessageProcessor<String, String>() {
@Override
public String process(String m1) throws SuspendExecution, InterruptedException {
return m1 + receive(new MessageProcessor<String, String>() {
@Override
public String process(String m2) throws SuspendExecution, InterruptedException {
return m2;
}
});
}
});
}
});
String msg = "a";
actor.ref().send(msg);
actor.ref().send(msg);
assertThat(actor.get(), equalTo("aa"));
}
@Test
public void whenLinkedActorDiesDuringSelectiveReceiveThenReceiverDies() throws Exception {
final Actor<Message, Void> a = spawnActor(new BasicActor<Message, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
//noinspection InfiniteLoopStatement
for (;;)
System.out.println(receive());
}
});
final Actor<Object, Void> m = spawnActor(new BasicActor<Object, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
link(a.ref());
receive(MessageSelector.select().ofType(String.class));
//noinspection StatementWithEmptyBody
for (; receive(100, TimeUnit.MILLISECONDS) instanceof Integer;);
return null;
}
});
try {
a.getStrand().interrupt();
m.ref().send(1);
m.ref().send("hello");
m.join();
fail();
} catch (final ExecutionException e) {
final Throwable cause = e.getCause();
assertEquals(cause.getClass(), LifecycleException.class);
final LifecycleException lce = (LifecycleException) cause;
assertEquals(lce.message().getClass(), ExitMessage.class);
final ExitMessage em = (ExitMessage) lce.message();
assertEquals(em.getCause().getClass(), InterruptedException.class);
assertNull(em.watch);
assertEquals(em.actor, a.ref());
}
}
@Test
public void whenWatchedActorDiesDuringSelectiveReceiveThenExitMessageDeferred() throws Exception {
final Actor<Message, Void> a = spawnActor(new BasicActor<Message, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
//noinspection InfiniteLoopStatement
for (;;)
System.out.println(receive());
}
});
final AtomicReference<ExitMessage> emr = new AtomicReference<>();
final Actor<Object, Object[]> m = spawnActor(new BasicActor<Object, Object[]>(mailboxConfig) {
private Object watch;
@Override
protected Object[] doRun() throws SuspendExecution, InterruptedException {
watch = watch(a.ref());
final String msg = receive(MessageSelector.select().ofType(String.class));
Object o;
//noinspection StatementWithEmptyBody
for (; (o = receive(100, TimeUnit.MILLISECONDS)) instanceof Integer;);
return new Object[]{watch, msg, o};
}
@Override
protected Object handleLifecycleMessage(LifecycleMessage m) {
if (m instanceof ExitMessage) {
final ExitMessage em = (ExitMessage) m;
if (watch.equals(em.watch) && em.actor.equals(a.ref()))
emr.set(em);
}
return super.handleLifecycleMessage(m);
}
});
try {
a.getStrand().interrupt();
m.ref().send(1);
m.ref().send("hello");
final Object[] res = m.get();
assertNotNull(res[0]);
assertNotNull(res[1]);
assertNull(res[2]);
assertNotNull(emr.get());
assertEquals(res[1], "hello");
assertEquals(res[0], emr.get().watch);
assertEquals(a.ref(), emr.get().actor);
assertNotNull(emr.get().cause);
assertEquals(emr.get().cause.getClass(), InterruptedException.class);
} catch (final Throwable t) {
fail();
}
}
@Test
public void whenSimpleReceiveAndTimeoutThenReturnNull() throws Exception {
Actor<Message, Void> actor = spawnActor(new BasicActor<Message, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
Message m;
m = receive(100, TimeUnit.MILLISECONDS);
assertThat(m.num, is(1));
m = receive(100, TimeUnit.MILLISECONDS);
assertThat(m.num, is(2));
m = receive(100, TimeUnit.MILLISECONDS);
assertThat(m, is(nullValue()));
return null;
}
});
actor.ref().send(new Message(1));
Thread.sleep(20);
actor.ref().send(new Message(2));
Thread.sleep(200);
actor.ref().send(new Message(3));
actor.join();
}
@Test
public void testTimeoutException() throws Exception {
Actor<Message, Void> actor = spawnActor(new BasicActor<Message, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
try {
receive(100, TimeUnit.MILLISECONDS, new MessageProcessor<Message, Message>() {
public Message process(Message m) throws SuspendExecution, InterruptedException {
fail();
return m;
}
});
fail();
} catch (TimeoutException e) {
}
return null;
}
});
Thread.sleep(150);
actor.ref().send(new Message(1));
actor.join();
}
@Test
public void testSendSync() throws Exception {
final Actor<Message, Void> actor1 = spawnActor(new BasicActor<Message, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
Message m;
m = receive();
assertThat(m.num, is(1));
m = receive();
assertThat(m.num, is(2));
m = receive();
assertThat(m.num, is(3));
return null;
}
});
final Actor<Message, Void> actor2 = spawnActor(new BasicActor<Message, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
Fiber.sleep(20);
actor1.ref().send(new Message(1));
Fiber.sleep(10);
actor1.sendSync(new Message(2));
actor1.ref().send(new Message(3));
return null;
}
});
actor1.join();
actor2.join();
}
@Test
public void testLink() throws Exception {
Actor<Message, Void> actor1 = spawnActor(new BasicActor<Message, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
Fiber.sleep(100);
return null;
}
});
Actor<Message, Void> actor2 = spawnActor(new BasicActor<Message, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
try {
for (;;) {
receive();
}
} catch (LifecycleException e) {
}
return null;
}
});
actor1.link(actor2.ref());
actor1.join();
actor2.join();
}
@Test
public void testWatch() throws Exception {
Actor<Message, Void> actor1 = spawnActor(new BasicActor<Message, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
Fiber.sleep(100);
return null;
}
});
final AtomicBoolean handlerCalled = new AtomicBoolean(false);
Actor<Message, Void> actor2 = spawnActor(new BasicActor<Message, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
Message m = receive(200, TimeUnit.MILLISECONDS);
assertThat(m, is(nullValue()));
return null;
}
@Override
protected Message handleLifecycleMessage(LifecycleMessage m) {
super.handleLifecycleMessage(m);
handlerCalled.set(true);
return null;
}
});
actor2.watch(actor1.ref());
actor1.join();
actor2.join();
assertThat(handlerCalled.get(), is(true));
}
@Test
public void testWatchGC() throws Exception {
Assume.assumeFalse(Debug.isDebug());
final Actor<Message, Void> actor = spawnActor(new BasicActor<Message, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
Fiber.sleep(120000);
return null;
}
});
System.out.println("actor1 is " + actor);
WeakReference wrActor2 = new WeakReference(spawnActor(new BasicActor<Message, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
Fiber.sleep(10);
final Object watch = watch(actor.ref());
// unwatch(actor, watch);
return null;
}
}));
System.out.println("actor2 is " + wrActor2.get());
for (int i = 0; i < 10; i++) {
Thread.sleep(10);
System.gc();
}
Thread.sleep(2000);
assertEquals(null, wrActor2.get());
}
@Test
public void transformingSendChannelIsEqualToActor() throws Exception {
final ActorRef<Integer> actor = new BasicActor<Integer, Void>(mailboxConfig) {
@Override
protected Void doRun() throws SuspendExecution, InterruptedException {
return null;
}
}.spawn();
SendPort<Integer> ch1 = Channels.filterSend(actor, new Predicate<Integer>() {
@Override
public boolean apply(Integer input) {
return input % 2 == 0;
}
});
SendPort<Integer> ch2 = Channels.mapSend(actor, new Function<Integer, Integer>() {
@Override
public Integer apply(Integer input) {
return input + 10;
}
});
assertTrue(ch1.equals(actor));
assertTrue(actor.equals(ch1));
assertTrue(ch2.equals(actor));
assertTrue(actor.equals(ch2));
}
@Test
public void testSpawnWithStrandFactory() throws Exception {
final AtomicBoolean run = new AtomicBoolean(false);
Actor<Message, Integer> actor = new BasicActor<Message, Integer>(mailboxConfig) {
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
run.set(true);
return 3;
}
};
ActorRef a = actor.spawn(new StrandFactoryBuilder().setFiber(null).setNameFormat("my-fiber-%d").build());
Strand s = LocalActor.getStrand(a);
assertTrue(s.isFiber());
assertThat(s.getName(), equalTo("my-fiber-0"));
assertThat((Integer) LocalActor.get(a), equalTo(3));
assertThat(run.get(), is(true));
run.set(false);
actor = new BasicActor<Message, Integer>(mailboxConfig) {
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
run.set(true);
return 3;
}
};
a = actor.spawn(new StrandFactoryBuilder().setThread(false).setNameFormat("my-thread-%d").build());
s = LocalActor.getStrand(a);
assertTrue(!s.isFiber());
assertThat(s.getName(), equalTo("my-thread-0"));
LocalActor.join(a);
assertThat(run.get(), is(true));
run.set(false);
Actor<Message, Integer> actor2 = new BasicActor<Message, Integer>("coolactor", mailboxConfig) {
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
run.set(true);
return 3;
}
};
a = actor2.spawn(new StrandFactoryBuilder().setFiber(null).setNameFormat("my-fiber-%d").build());
s = LocalActor.getStrand(a);
assertTrue(s.isFiber());
assertThat(s.getName(), equalTo("coolactor"));
assertThat((Integer) LocalActor.get(a), equalTo(3));
assertThat(run.get(), is(true));
run.set(false);
actor2 = new BasicActor<Message, Integer>("coolactor", mailboxConfig) {
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
run.set(true);
return 3;
}
};
a = actor2.spawn(new StrandFactoryBuilder().setThread(false).setNameFormat("my-thread-%d").build());
s = LocalActor.getStrand(a);
assertTrue(!s.isFiber());
assertThat(s.getName(), equalTo("coolactor"));
LocalActor.join(a);
assertThat(run.get(), is(true));
run.set(false);
}
static class Message {
final int num;
public Message(int num) {
this.num = num;
}
}
static class ComplexMessage {
enum Type {
FOO, BAR, BAZ, WAT
}
final Type type;
final int num;
public ComplexMessage(Type type, int num) {
this.type = type;
this.num = num;
}
}
}