/*
* Quasar: lightweight threads and actors for the JVM.
* Copyright (c) 2013-2015, 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.behaviors;
import co.paralleluniverse.actors.Actor;
import co.paralleluniverse.actors.ActorRef;
import co.paralleluniverse.actors.ActorRegistry;
import co.paralleluniverse.actors.ActorSpec;
import co.paralleluniverse.actors.BasicActor;
import co.paralleluniverse.actors.LifecycleMessage;
import co.paralleluniverse.actors.LocalActor;
import co.paralleluniverse.actors.ShutdownMessage;
import co.paralleluniverse.actors.behaviors.Supervisor.ChildMode;
import co.paralleluniverse.actors.behaviors.Supervisor.ChildSpec;
import co.paralleluniverse.actors.behaviors.SupervisorActor.RestartStrategy;
import co.paralleluniverse.common.test.TestUtil;
import co.paralleluniverse.common.util.Debug;
import co.paralleluniverse.fibers.FiberFactory;
import co.paralleluniverse.fibers.FiberForkJoinScheduler;
import co.paralleluniverse.fibers.FiberScheduler;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.strands.Strand;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.hamcrest.CoreMatchers.*;
import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.rules.TestRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* These tests are also good tests for sendSync, as they test sendSync (and receive) from both fibers and threads.
*
* @author pron
*/
public class SupervisorTest {
@Rule
public TestName name = new TestName();
@Rule
public TestRule watchman = TestUtil.WATCHMAN;
@After
public void tearDown() {
ActorRegistry.clear();
}
private static final Logger LOG = LoggerFactory.getLogger(SupervisorActor.class);
static final int mailboxSize = 10;
private static FiberScheduler scheduler;
private static FiberFactory factory;
public SupervisorTest() throws Exception {
factory = scheduler = new FiberForkJoinScheduler("test", 4, null, false);
java.util.logging.LogManager.getLogManager().readConfiguration(); // gradle messes with the configurations
}
private static class Actor1 extends BasicActor<Object, Integer> {
public Actor1(String name) {
super(name);
}
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
register();
int i = 0;
try {
for (;;) {
Object m = receive();
i++;
}
} catch (InterruptedException e) {
return i;
}
}
@Override
protected Object handleLifecycleMessage(LifecycleMessage m) {
if (m instanceof ShutdownMessage)
Strand.currentStrand().interrupt();
else
super.handleLifecycleMessage(m);
return null;
}
}
private static class BadActor1 extends BasicActor<Object, Integer> {
public BadActor1(String name) {
super(name);
}
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
register();
int i = 0;
try {
for (;;) {
Object m = receive();
i++;
throw new RuntimeException("Ha!");
}
} catch (InterruptedException e) {
return i;
}
}
}
// private <Message, V> Actor<Message, V> getRegisteredActor(String name, long timeout) throws InterruptedException {
// Actor<Message, V> a;
// final long start = System.nanoTime();
// while ((a = (Actor)Actor.getActor(name)) == null || a.isDone()) {
// if (System.nanoTime() > start + TimeUnit.MILLISECONDS.toNanos(timeout))
// return null;
// Thread.sleep(10);
// }
// return a;
// }
private <Message> ActorRef<Message> getChild(Supervisor sup, String name, long timeout) throws InterruptedException, SuspendExecution {
return (ActorRef<Message>) sup.getChild(name);
// Actor<Message, V> a;
// final long start = System.nanoTime();
// while ((a = sup.getChild(name)) == null || a.isDone()) {
// if (System.nanoTime() > start + TimeUnit.MILLISECONDS.toNanos(timeout))
// return null;
// Thread.sleep(10);
// }
// return a;
}
private <Message> List<ActorRef<Message>> getChildren(final Supervisor sup) throws InterruptedException, SuspendExecution {
return (List<ActorRef<Message>>) sup.getChildren();
}
@Test
public void startChild() throws Exception {
final Supervisor sup = new SupervisorActor(RestartStrategy.ONE_FOR_ONE,
new ChildSpec("actor1", ChildMode.PERMANENT, 5, 1, TimeUnit.SECONDS, 3, ActorSpec.of(Actor1.class, "actor1"))).spawn(factory);
ActorRef<Object> a;
a = getChild(sup, "actor1", 1000);
for (int i = 0; i < 3; i++)
a.send(1);
a.send(new ShutdownMessage(null));
assertThat(LocalActor.<Integer>get(a), is(3));
sup.shutdown();
LocalActor.join(sup);
}
@Test
public void whenChildDiesThenRestart() throws Exception {
final Supervisor sup = new SupervisorActor(RestartStrategy.ONE_FOR_ONE,
new ChildSpec("actor1", ChildMode.PERMANENT, 5, 1, TimeUnit.SECONDS, 3, ActorSpec.of(Actor1.class, "actor1"))).spawn(factory);
ActorRef<Object> a;
a = getChild(sup, "actor1", 1000);
for (int i = 0; i < 3; i++)
a.send(1);
a.send(new ShutdownMessage(null));
assertThat(LocalActor.<Integer>get(a), is(3));
a = getChild(sup, "actor1", 1000);
for (int i = 0; i < 5; i++)
a.send(1);
a.send(new ShutdownMessage(null));
assertThat(LocalActor.<Integer>get(a), is(5));
sup.shutdown();
LocalActor.join(sup);
}
@Test
public void whenChildDiesTooManyTimesThenGiveUpAndDie() throws Exception {
final Supervisor sup = new SupervisorActor(RestartStrategy.ONE_FOR_ONE,
new ChildSpec("actor1", ChildMode.PERMANENT, 3, 1, TimeUnit.SECONDS, 3, ActorSpec.of(BadActor1.class, "actor1"))).spawn();
ActorRef<Object> a, prevA = null;
for (int k = 0; k < 4; k++) {
a = getChildren(sup).get(0);
assertThat(a, not(prevA));
a.send(1);
try {
LocalActor.join(a);
fail();
} catch (ExecutionException e) {
}
prevA = a;
}
LocalActor.join(sup, 20, TimeUnit.MILLISECONDS);
}
@Test
public void dontRestartTemporaryChildDeadOfNaturalCause() throws Exception {
final Supervisor sup = new SupervisorActor(RestartStrategy.ONE_FOR_ONE,
new ChildSpec("actor1", ChildMode.TEMPORARY, 5, 1, TimeUnit.SECONDS, 3, ActorSpec.of(Actor1.class, "actor1"))).spawn();
ActorRef<Object> a;
a = getChild(sup, "actor1", 1000);
for (int i = 0; i < 3; i++)
a.send(1);
a.send(new ShutdownMessage(null));
assertThat(LocalActor.<Integer>get(a), is(3));
a = getChild(sup, "actor1", 200);
assertThat(a, nullValue());
List<ActorRef<Object>> cs = getChildren(sup);
assertEquals(cs.size(), 0);
sup.shutdown();
LocalActor.join(sup);
}
@Test
public void dontRestartTemporaryChildDeadOfUnnaturalCause() throws Exception {
final Supervisor sup = new SupervisorActor(RestartStrategy.ONE_FOR_ONE,
new ChildSpec("actor1", ChildMode.TEMPORARY, 5, 1, TimeUnit.SECONDS, 3, ActorSpec.of(BadActor1.class, "actor1"))).spawn(factory);
ActorRef<Object> a;
a = getChild(sup, "actor1", 1000);
for (int i = 0; i < 3; i++)
a.send(1);
a.send(new ShutdownMessage(null));
try {
LocalActor.join(a);
fail();
} catch (ExecutionException e) {
}
a = getChild(sup, "actor1", 200);
assertThat(a, nullValue());
sup.shutdown();
LocalActor.join(sup);
}
@Test
public void dontRestartTransientChildDeadOfNaturalCause() throws Exception {
final Supervisor sup = new SupervisorActor(RestartStrategy.ONE_FOR_ONE,
new ChildSpec("actor1", ChildMode.TRANSIENT, 5, 1, TimeUnit.SECONDS, 3, ActorSpec.of(Actor1.class, "actor1"))).spawn();
ActorRef<Object> a;
a = getChild(sup, "actor1", 1000);
for (int i = 0; i < 3; i++)
a.send(1);
a.send(new ShutdownMessage(null));
assertThat(LocalActor.<Integer>get(a), is(3));
a = getChild(sup, "actor1", 200);
assertThat(a, nullValue());
sup.shutdown();
LocalActor.join(sup);
}
@Test
public void restartTransientChildDeadOfUnnaturalCause() throws Exception {
final Supervisor sup = new SupervisorActor(RestartStrategy.ONE_FOR_ONE,
new ChildSpec("actor1", ChildMode.TRANSIENT, 5, 1, TimeUnit.SECONDS, 3, ActorSpec.of(BadActor1.class, "actor1"))).spawn();
ActorRef<Object> a;
a = getChild(sup, "actor1", 1000);
for (int i = 0; i < 3; i++)
a.send(1);
a.send(new ShutdownMessage(null));
try {
LocalActor.join(a);
fail();
} catch (ExecutionException e) {
}
ActorRef<Object> b = getChild(sup, "actor1", 200);
assertThat(b, not(nullValue()));
assertThat(b, not(equalTo(a)));
List<ActorRef<Object>> bcs = getChildren(sup);
assertThat(bcs.get(0), not(nullValue()));
assertThat(bcs.get(0), not(equalTo(a)));
assertEquals(bcs.size(), 1);
sup.shutdown();
LocalActor.join(sup);
}
private static class Actor2 extends BasicActor<Object, Integer> {
public Actor2(String name) {
super(name);
}
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
register();
int i = 0;
try {
for (;;) {
Object m = receive();
i++;
}
} catch (InterruptedException e) {
return i;
}
}
@Override
protected Object handleLifecycleMessage(LifecycleMessage m) {
if (m instanceof ShutdownMessage)
Strand.currentStrand().interrupt();
else
super.handleLifecycleMessage(m);
return null;
}
@Override
protected Actor<Object, Integer> reinstantiate() {
return new Actor2(getName());
}
}
@Test
public void restartPreInstantiatedChild() throws Exception {
final Supervisor sup = new SupervisorActor(RestartStrategy.ONE_FOR_ONE).spawn();
final ActorRef<Object> a1 = new Actor2("actor1").spawn();
sup.addChild(new ChildSpec("actor1", ChildMode.PERMANENT, 5, 1, TimeUnit.SECONDS, 3, a1));
ActorRef<Object> a;
a = getChild(sup, "actor1", 1);
assertThat(a, equalTo(a1));
for (int i = 0; i < 3; i++)
a.send(1);
a.send(new ShutdownMessage(null));
assertThat(LocalActor.<Integer>get(a), is(3));
a = getChild(sup, "actor1", 1000);
assertThat(a, is(not(equalTo(a1))));
for (int i = 0; i < 5; i++)
a.send(1);
a.send(new ShutdownMessage(null));
assertThat(LocalActor.<Integer>get(a), is(5));
sup.shutdown();
LocalActor.join(sup);
}
@Test
public void testRegistration() throws Exception {
Supervisor s = new SupervisorActor(RestartStrategy.ONE_FOR_ONE) {
@Override
protected void init() throws SuspendExecution, InterruptedException {
// Strand.sleep(1000);
register("test1");
}
}.spawn();
assertTrue(s == (Supervisor) ActorRegistry.getActor("test1"));
}
///////////////// Complex example ///////////////////////////////////////////
private static class Actor3 extends BasicActor<Integer, Integer> {
private final Supervisor mySup;
private final AtomicInteger started;
private final AtomicInteger terminated;
public Actor3(String name, AtomicInteger started, AtomicInteger terminated) {
super(name);
mySup = LocalActor.self();
this.started = started;
this.terminated = terminated;
}
@Override
protected Integer doRun() throws SuspendExecution, InterruptedException {
final Server<Message1, Integer, Void> adder = new ServerActor<Message1, Integer, Void>() {
@Override
protected void init() throws SuspendExecution {
started.incrementAndGet();
}
@Override
protected void terminate(Throwable cause) throws SuspendExecution {
terminated.incrementAndGet();
}
@Override
protected Integer handleCall(ActorRef<?> from, Object id, Message1 m) throws Exception, SuspendExecution {
int res = m.a + m.b;
if (res > 100)
throw new RuntimeException("oops!");
return res;
}
}.spawn();
//link(adder);
mySup.addChild(new ChildSpec(null, ChildMode.TEMPORARY, 10, 1, TimeUnit.SECONDS, 1, adder));
int a = receive();
int b = receive();
int res = adder.call(new Message1(a, b));
return res;
}
@Override
protected Actor<Integer, Integer> reinstantiate() {
return new Actor3(getName(), started, terminated);
}
}
@Test
public void testComplex1() throws Exception {
AtomicInteger started = new AtomicInteger();
AtomicInteger terminated = new AtomicInteger();
final Supervisor sup = new SupervisorActor(RestartStrategy.ALL_FOR_ONE,
new ChildSpec("actor1", ChildMode.PERMANENT, 5, 1, TimeUnit.SECONDS, 3, ActorSpec.of(Actor3.class, "actor1", started, terminated))).spawn();
ActorRef<Integer> a;
a = getChild(sup, "actor1", 1000);
a.send(3);
a.send(4);
assertThat(LocalActor.<Integer>get(a), is(7));
a = getChild(sup, "actor1", 1000);
a.send(70);
a.send(80);
try {
LocalActor.<Integer>get(a);
fail();
} catch (ExecutionException e) {
}
a = getChild(sup, "actor1", 1000);
a.send(7);
a.send(8);
assertThat(LocalActor.<Integer>get(a), is(15));
Thread.sleep(100); // give the actor time to start the GenServer
sup.shutdown();
LocalActor.join(sup);
assertThat(started.get(), is(4));
assertThat(terminated.get(), is(4));
}
static class Message1 {
final int a;
final int b;
public Message1(int a, int b) {
this.a = a;
this.b = b;
}
}
}