/* * 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.strands.channels; import static co.paralleluniverse.common.test.Matchers.*; import co.paralleluniverse.common.test.TestUtil; import co.paralleluniverse.common.util.Debug; import co.paralleluniverse.fibers.Fiber; import co.paralleluniverse.fibers.FiberForkJoinScheduler; import co.paralleluniverse.fibers.FiberScheduler; import co.paralleluniverse.fibers.SuspendExecution; import co.paralleluniverse.strands.SuspendableCallable; import co.paralleluniverse.strands.SuspendableRunnable; import co.paralleluniverse.strands.channels.Channels.OverflowPolicy; import co.paralleluniverse.strands.queues.QueueCapacityExceededException; import com.google.common.collect.ImmutableSet; import java.util.Arrays; import java.util.Collection; import java.util.concurrent.TimeUnit; import static org.hamcrest.CoreMatchers.*; import org.junit.After; import org.junit.AfterClass; import static org.junit.Assert.*; import static org.junit.Assume.*; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; /** * * @author pron */ @RunWith(Parameterized.class) public class ChannelTest { @Rule public TestName name = new TestName(); @Rule public TestRule watchman = TestUtil.WATCHMAN; final int mailboxSize; final OverflowPolicy policy; final boolean singleConsumer; final boolean singleProducer; final FiberScheduler scheduler; // public ChannelTest() { // scheduler = new FiberForkJoinScheduler("test", 4, null, false); // this.mailboxSize = 5; // this.policy = OverflowPolicy.THROW; // this.singleConsumer = true; // this.singleProducer = false; // // //Debug.dumpAfter(20000, "channels.log"); // } public ChannelTest(int mailboxSize, OverflowPolicy policy, boolean singleConsumer, boolean singleProducer) { scheduler = new FiberForkJoinScheduler("test", 4, null, false); this.mailboxSize = mailboxSize; this.policy = policy; this.singleConsumer = singleConsumer; this.singleProducer = singleProducer; } @Parameterized.Parameters(name = "{0} {1} {2} {3}") public static Collection<Object[]> data() { return Arrays.asList(new Object[][]{ {5, OverflowPolicy.THROW, true, false}, {5, OverflowPolicy.THROW, false, false}, {5, OverflowPolicy.BLOCK, true, false}, {5, OverflowPolicy.BLOCK, false, false}, {5, OverflowPolicy.DROP, true, false}, {1, OverflowPolicy.BLOCK, false, false}, {-1, OverflowPolicy.THROW, true, false}, {5, OverflowPolicy.DISPLACE, true, false}, {0, OverflowPolicy.BLOCK, false, false},}); } private <Message> Channel<Message> newChannel() { return Channels.newChannel(mailboxSize, policy, singleProducer, singleConsumer); } @Before public void setUp() { } @After public void tearDown() { } @Test public void sendMessageFromFiberToFiber() throws Exception { final Channel<String> ch = newChannel(); Fiber fib1 = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { String m = ch.receive(); assertThat(m, equalTo("a message")); } }).start(); Fiber fib2 = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { Fiber.sleep(50); ch.send("a message"); } }).start(); fib1.join(); fib2.join(); } @Test public void sendMessageFromThreadToFiber() throws Exception { final Channel<String> ch = newChannel(); Fiber fib = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { String m = ch.receive(); assertThat(m, equalTo("a message")); } }).start(); Thread.sleep(50); ch.send("a message"); fib.join(); } @Test public void sendMessageFromFiberToThread() throws Exception { final Channel<String> ch = newChannel(); Fiber fib = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { Fiber.sleep(100); ch.send("a message"); } }).start(); String m = ch.receive(); assertThat(m, equalTo("a message")); fib.join(); } @Test public void sendMessageFromThreadToThread() throws Exception { final Channel<String> ch = newChannel(); Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(100); ch.send("a message"); } catch (InterruptedException | SuspendExecution ex) { throw new AssertionError(ex); } } }); thread.start(); String m = ch.receive(); assertThat(m, equalTo("a message")); thread.join(); } @Test public void sendMessageThreadToThreadTimeoutOK() throws Exception { final Channel<String> ch = newChannel(); assumeTrue(mailboxSize == 1); assumeTrue(OverflowPolicy.BLOCK.equals(policy)); Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(100); ch.send("message 1"); ch.send("message 2", 100, TimeUnit.MILLISECONDS); } catch (InterruptedException | SuspendExecution ex) { throw new AssertionError(ex); } } }); thread.start(); Thread.sleep(150); String m = ch.receive(); assertThat(m, equalTo("message 1")); m = ch.receive(100, TimeUnit.MILLISECONDS); assertThat(m, equalTo("message 2")); thread.join(); } @Test public void sendMessageThreadToThreadTimeoutKO() throws Exception { final Channel<String> ch = newChannel(); assumeTrue(mailboxSize == 1); assumeTrue(OverflowPolicy.BLOCK.equals(policy)); Thread thread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(100); ch.send("message 1"); ch.send("message 2", 100, TimeUnit.MILLISECONDS); } catch (InterruptedException | SuspendExecution ex) { throw new AssertionError(ex); } } }); thread.start(); Thread.sleep(250); String m = ch.receive(); assertThat(m, equalTo("message 1")); m = ch.receive(100, TimeUnit.MILLISECONDS); assertNull(m); thread.join(); } @Ignore @Test public void whenReceiveNotCalledFromOwnerThenThrowException1() throws Exception { assumeTrue(Debug.isAssertionsEnabled()); final Channel<String> ch = newChannel(); Fiber fib1 = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { String m = ch.receive(); assertThat(m, equalTo("a message")); } }).start(); Fiber fib2 = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { Fiber.sleep(50); ch.send("a message"); boolean thrown = false; try { ch.receive(); } catch (Throwable e) { thrown = true; } assertTrue(thrown); } }).start(); fib1.join(); fib2.join(); } @Ignore @Test public void whenReceiveNotCalledFromOwnerThenThrowException2() throws Exception { assumeTrue(Debug.isAssertionsEnabled()); final Channel<String> ch = newChannel(); Fiber fib = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { String m = ch.receive(); assertThat(m, equalTo("a message")); } }).start(); Thread.sleep(50); ch.send("a message"); boolean thrown = false; try { ch.receive(); } catch (Throwable e) { thrown = true; } assertTrue(thrown); fib.join(); } @Ignore @Test public void whenReceiveNotCalledFromOwnerThenThrowException3() throws Exception { assumeTrue(Debug.isAssertionsEnabled()); final Channel<String> ch = newChannel(); Fiber fib = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { Fiber.sleep(100); ch.send("a message"); boolean thrown = false; try { ch.receive(); } catch (Throwable e) { thrown = true; } assertTrue(thrown); } }).start(); String m = ch.receive(); assertThat(m, equalTo("a message")); fib.join(); } @Ignore @Test public void whenReceiveNotCalledFromOwnerThenThrowException4() throws Exception { assumeTrue(Debug.isAssertionsEnabled()); final Channel<String> ch = newChannel(); Thread thread = new Thread(new Runnable() { @Override public void run() { try { ch.receive(); } catch (InterruptedException ex) { throw new AssertionError(ex); } catch (SuspendExecution e) { throw new AssertionError(e); } } }); thread.start(); Thread.sleep(100); ch.send("a message"); boolean thrown = false; try { ch.receive(); } catch (Throwable e) { thrown = true; } assertTrue(thrown); thread.join(); } @Test public void whenChannelOverflowsAndPolicyThrowThenThrowException() throws Exception { assumeThat(policy, is(OverflowPolicy.THROW)); assumeThat(mailboxSize, greaterThan(0)); final Channel<Integer> ch = newChannel(); int i = 0; try { for (i = 0; i < 10; i++) ch.send(i); fail(); } catch (QueueCapacityExceededException e) { System.out.println("i = " + i); } } @Test public void whenChannelOverflowsAndPolicyDropThenDrop() throws Exception { assumeThat(policy, is(OverflowPolicy.DROP)); assumeThat(mailboxSize, greaterThan(0)); final Channel<Integer> ch = newChannel(); for (int i = 0; i < 10; i++) ch.send(i); } @Test public void testBlockingChannelSendingFiber() throws Exception { assumeThat(policy, is(OverflowPolicy.BLOCK)); final Channel<Integer> ch = newChannel(); Fiber<Integer> receiver = new Fiber<>(scheduler, new SuspendableCallable<Integer>() { @Override public Integer run() throws SuspendExecution, InterruptedException { int i = 0; while (ch.receive() != null) { i++; Fiber.sleep(50); } return i; } }).start(); Fiber<Void> sender = new Fiber<Void>(scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { for (int i = 0; i < 10; i++) ch.send(i); ch.close(); } }).start(); try { assertThat(receiver.get(), is(10)); sender.join(); } catch (Throwable t) { Debug.dumpRecorder("channels.log"); throw t; } } @Test public void testBlockingChannelSendingThread() throws Exception { assumeThat(policy, is(OverflowPolicy.BLOCK)); final Channel<Integer> ch = newChannel(); Fiber<Integer> fib = new Fiber<>(scheduler, new SuspendableCallable<Integer>() { @Override public Integer run() throws SuspendExecution, InterruptedException { int i = 0; while (ch.receive() != null) { i++; Fiber.sleep(50); } return i; } }).start(); for (int i = 0; i < 10; i++) ch.send(i); ch.close(); assertThat(fib.get(), is(10)); } @Test public void testChannelClose() throws Exception { final Channel<Integer> ch = newChannel(); Fiber fib = new Fiber(scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { for (int i = 1; i <= 5; i++) { Integer m = ch.receive(); assertThat(m, equalTo(i)); } Integer m = ch.receive(); assertThat(m, nullValue()); assertTrue(ch.isClosed()); } }).start(); Thread.sleep(50); ch.send(1); ch.send(2); ch.send(3); ch.send(4); ch.send(5); ch.close(); ch.send(6); ch.send(7); fib.join(); } @Test public void testChannelCloseException() throws Exception { final Channel<Integer> ch = newChannel(); Fiber fib = new Fiber(scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { for (int i = 1; i <= 5; i++) { Integer m = ch.receive(); assertThat(m, equalTo(i)); } try { ch.receive(); fail(); } catch (ProducerException e) { assertThat(e.getCause().getMessage(), equalTo("foo")); } assertTrue(ch.isClosed()); } }).start(); Thread.sleep(50); ch.send(1); ch.send(2); ch.send(3); ch.send(4); ch.send(5); ch.close(new Exception("foo")); ch.send(6); ch.send(7); fib.join(); } @Test public void testChannelCloseWithSleep() throws Exception { final Channel<Integer> ch = newChannel(); Fiber fib = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { for (int i = 1; i <= 5; i++) { Integer m = ch.receive(); assertThat(m, equalTo(i)); } Integer m = ch.receive(); assertThat(m, nullValue()); assertTrue(ch.isClosed()); } }).start(); Thread.sleep(50); ch.send(1); ch.send(2); ch.send(3); ch.send(4); ch.send(5); Thread.sleep(50); ch.close(); ch.send(6); ch.send(7); fib.join(); } @Test public void testChannelCloseExceptionWithSleep() throws Exception { final Channel<Integer> ch = newChannel(); Fiber fib = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { for (int i = 1; i <= 5; i++) { Integer m = ch.receive(); assertThat(m, equalTo(i)); } try { Integer m = ch.receive(); fail("m = " + m); } catch (ProducerException e) { assertThat(e.getCause().getMessage(), equalTo("foo")); } assertTrue(ch.isClosed()); } }).start(); Thread.sleep(50); ch.send(1); ch.send(2); ch.send(3); ch.send(4); ch.send(5); Thread.sleep(50); ch.close(new Exception("foo")); ch.send(6); ch.send(7); fib.join(); } @Test public void whenChannelClosedThenBlockedSendsComplete() throws Exception { assumeThat(policy, is(OverflowPolicy.BLOCK)); final Channel<Integer> ch = newChannel(); final SuspendableRunnable r = new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { for (int i = 1; i <= 100; i++) { ch.send(i); } } }; Fiber fib1 = new Fiber("fiber", scheduler, r).start(); Fiber fib2 = new Fiber("fiber", scheduler, r).start(); Thread.sleep(500); ch.close(); fib1.join(); fib2.join(); } @Test public void whenChannelClosedExceptionThenBlockedSendsComplete() throws Exception { assumeThat(policy, is(OverflowPolicy.BLOCK)); final Channel<Integer> ch = newChannel(); final SuspendableRunnable r = new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { for (int i = 1; i <= 100; i++) { ch.send(i); } } }; Fiber fib1 = new Fiber("fiber", scheduler, r).start(); Fiber fib2 = new Fiber("fiber", scheduler, r).start(); Thread.sleep(500); ch.close(new Exception("foo")); fib1.join(); fib2.join(); } @Test public void testPrimitiveChannelClose() throws Exception { assumeThat(mailboxSize, not(equalTo(0))); final IntChannel ch = Channels.newIntChannel(mailboxSize, policy); Fiber fib = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { try { for (int i = 1; i <= 5; i++) { int m = ch.receiveInt(); assertThat(m, is(i)); } } catch (QueueChannel.EOFException e) { fail(); } try { int m = ch.receiveInt(); fail("m = " + m); } catch (QueueChannel.EOFException e) { } assertTrue(ch.isClosed()); } }).start(); Thread.sleep(50); ch.send(1); ch.send(2); ch.send(3); ch.send(4); ch.send(5); ch.close(); ch.send(6); ch.send(7); fib.join(); } @Test public void testPrimitiveChannelCloseException() throws Exception { assumeThat(mailboxSize, not(equalTo(0))); final IntChannel ch = Channels.newIntChannel(mailboxSize, policy); Fiber fib = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { try { for (int i = 1; i <= 5; i++) { int m = ch.receiveInt(); assertThat(m, is(i)); } } catch (QueueChannel.EOFException e) { fail(); } try { int m = ch.receiveInt(); fail("m = " + m); } catch (ProducerException e) { assertThat(e.getCause().getMessage(), equalTo("foo")); } catch (ReceivePort.EOFException e) { fail(); } assertTrue(ch.isClosed()); } }).start(); Thread.sleep(50); ch.send(1); ch.send(2); ch.send(3); ch.send(4); ch.send(5); ch.close(new Exception("foo")); ch.send(6); ch.send(7); fib.join(); } @Test public void testTopic() throws Exception { final Channel<String> channel1 = newChannel(); final Channel<String> channel2 = newChannel(); final Channel<String> channel3 = newChannel(); final Topic<String> topic = new Topic<>(); topic.subscribe(channel1); topic.subscribe(channel2); topic.subscribe(channel3); Fiber f1 = new Fiber(scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { assertThat(channel1.receive(), equalTo("hello")); assertThat(channel1.receive(), equalTo("world!")); } }).start(); Fiber f2 = new Fiber(scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { assertThat(channel2.receive(), equalTo("hello")); assertThat(channel2.receive(), equalTo("world!")); } }).start(); Fiber f3 = new Fiber(scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { assertThat(channel3.receive(), equalTo("hello")); assertThat(channel3.receive(), equalTo("world!")); } }).start(); Thread.sleep(100); topic.send("hello"); Thread.sleep(100); topic.send("world!"); f1.join(); f2.join(); f3.join(); } @Test public void testChannelGroupReceive() throws Exception { final Channel<String> channel1 = newChannel(); final Channel<String> channel2 = newChannel(); final Channel<String> channel3 = newChannel(); final ReceivePortGroup<String> group = new ReceivePortGroup<>(channel1, channel2, channel3); Fiber fib = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { String m1 = group.receive(); String m2 = channel2.receive(); assertThat(m1, equalTo("hello")); assertThat(m2, equalTo("world!")); } }).start(); Thread.sleep(100); channel3.send("hello"); Thread.sleep(100); if (policy != OverflowPolicy.BLOCK) { channel1.send("goodbye"); // TransferChannel will block here Thread.sleep(100); } channel2.send("world!"); fib.join(); } @Test public void testChannelGroupReceiveWithTimeout() throws Exception { final Channel<String> channel1 = newChannel(); final Channel<String> channel2 = newChannel(); final Channel<String> channel3 = newChannel(); final ReceivePortGroup<String> group = new ReceivePortGroup<>(channel1, channel2, channel3); Fiber fib = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { String m1 = group.receive(); String m2 = channel2.receive(); String m3 = group.receive(10, TimeUnit.MILLISECONDS); String m4 = group.receive(200, TimeUnit.MILLISECONDS); assertThat(m1, equalTo("hello")); assertThat(m2, equalTo("world!")); assertThat(m3, nullValue()); assertThat(m4, equalTo("foo")); } }).start(); Thread.sleep(100); channel3.send("hello"); Thread.sleep(100); channel2.send("world!"); Thread.sleep(100); channel1.send("foo"); fib.join(); } @Test public void testChannelGroupMix() throws Exception { assumeThat(mailboxSize, greaterThan(1)); final Channel<String> channel1 = newChannel(); final Channel<String> channel2 = newChannel(); final Channel<String> channel3 = newChannel(); final Channel<Object> sync = Channels.newChannel(0); final ReceivePortGroup<String> group = new ReceivePortGroup<>(); group.add(channel3); final Fiber fib = new Fiber("fiber", scheduler, new SuspendableRunnable() { @Override public void run() throws SuspendExecution, InterruptedException { final String m1 = group.receive(); final String m2 = channel2.receive(); sync.receive(); // 1 sync.receive(); // 2 final String m3 = group.receive(10, TimeUnit.MILLISECONDS); final String m4 = group.receive(200, TimeUnit.MILLISECONDS); sync.receive(); // 3 sync.receive(); // 4 final String m5 = group.receive(10, TimeUnit.MILLISECONDS); final String m6 = group.receive(200, TimeUnit.MILLISECONDS); assertThat(m1, equalTo("hello")); assertThat(m2, equalTo("world!")); assertThat(m3, nullValue()); assertThat(m4, equalTo("foo")); assertThat(m5, nullValue()); assertThat(m6, equalTo("bar")); sync.receive(); // 5 sync.receive(); // 6 final String m7 = group.receive(); assertThat(m7, equalTo("2-solo-sings")); sync.receive(); // 7 sync.receive(); // 8 String m8 = group.receive(); while (m8 != null && m8.contains("leaks")) m8 = group.receive(); String m9 = group.receive(); while (m9 != null && m9.contains("leaks")) m9 = group.receive(); String m10 = group.receive(); while (m10 != null && m10.contains("leaks")) m10 = group.receive(); assertThat(ImmutableSet.of(m8, m9, m10), equalTo(ImmutableSet.of("1-paused-by-2-solo-waits", "3-paused-by-2-solo-waits", "1-normal"))); sync.receive(); // 9 sync.receive(); // 10 String m11 = group.receive(); while (m11 != null && m11.contains("leaks")) m11 = group.receive(); assertThat(m11, equalTo("2-solo-sings-again")); sync.receive(); // 11 sync.receive(); // 12 String m12 = group.receive(); while (m12 != null && m12.contains("leaks")) m12 = group.receive(); assertThat(m12, nullValue()); } }).start(); final Object ping = new Object(); Thread.sleep(100); channel3.send("hello", 1, TimeUnit.SECONDS); Thread.sleep(100); channel2.send("world!", 1, TimeUnit.SECONDS); sync.send(ping, 1, TimeUnit.SECONDS); // 1 group.remove(channel3); group.add(channel1); sync.send(ping, 1, TimeUnit.SECONDS); // 2 Thread.sleep(150); channel1.send("foo", 1, TimeUnit.SECONDS); sync.send(ping, 1, TimeUnit.SECONDS); // 3 group.remove(channel1); group.add(channel2); sync.send(ping, 1, TimeUnit.SECONDS); // 4 Thread.sleep(100); channel2.send("bar", 1, TimeUnit.SECONDS); sync.send(ping, 1, TimeUnit.SECONDS); // 5 // Solo and mute, solo wins and others are paused (default) group.add(channel1); group.add(channel3); group.setState(new Mix.State(Mix.Mode.MUTE, true), channel2); sync.send(ping, 1, TimeUnit.SECONDS); // 6 channel1.send("1-paused-by-2-solo-waits", 1, TimeUnit.SECONDS); channel3.send("3-paused-by-2-solo-waits", 1, TimeUnit.SECONDS); channel2.send("2-solo-sings", 1, TimeUnit.SECONDS); sync.send(ping, 1, TimeUnit.SECONDS); // 7 // Remove solo group.setState(new Mix.State(false), channel2); sync.send(ping, 1, TimeUnit.SECONDS); // 8 channel2.send("2-muted-leaks", 1, TimeUnit.SECONDS); channel1.send("1-normal", 1, TimeUnit.SECONDS); sync.send(ping, 1, TimeUnit.SECONDS); // 9 // Restore normal state and solo and change solo effect on other channels to MUTE group.setState(new Mix.State(Mix.Mode.NORMAL, true), channel2); group.setSoloEffect(Mix.SoloEffect.MUTE_OTHERS); sync.send(ping, 1, TimeUnit.SECONDS); // 10 channel1.send("1-muted-by-2-solo-leaks", 1, TimeUnit.SECONDS); channel3.send("3-muted-by-2-solo-leaks", 1, TimeUnit.SECONDS); channel2.send("2-solo-sings-again", 1, TimeUnit.SECONDS); sync.send(ping, 1, TimeUnit.SECONDS); // 11 channel1.close(); channel2.close(); channel3.close(); sync.send(ping, 1, TimeUnit.SECONDS); // 12 fib.join(); } }