/* * Copyright 2014-2017 Real Logic Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.aeron; import org.junit.After; import org.junit.Test; import io.aeron.driver.MediaDriver; import io.aeron.logbuffer.FragmentHandler; import org.agrona.BitUtil; import org.agrona.LangUtil; import org.agrona.concurrent.UnsafeBuffer; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Test that a second subscriber can be stopped and started again while data is being published. */ public class StopStartSecondSubscriberTest { public static final String CHANNEL1 = "aeron:udp?endpoint=localhost:54325"; public static final String CHANNEL2 = "aeron:udp?endpoint=localhost:54326"; private static final int STREAM_ID1 = 1; private static final int STREAM_ID2 = 2; private MediaDriver driver1; private MediaDriver driver2; private Aeron publishingClient1; private Aeron subscribingClient1; private Aeron publishingClient2; private Aeron subscribingClient2; private Subscription subscription1; private Publication publication1; private Subscription subscription2; private Publication publication2; private final UnsafeBuffer buffer = new UnsafeBuffer(new byte[8192]); private final AtomicInteger subscriber1Count = new AtomicInteger(); private final FragmentHandler fragmentHandler1 = (buffer, offset, length, header) -> subscriber1Count.getAndIncrement(); private final AtomicInteger subscriber2Count = new AtomicInteger(); private final FragmentHandler fragmentHandler2 = (buffer, offset, length, header) -> subscriber2Count.getAndIncrement(); final MediaDriver.Context mediaDriverContext1 = new MediaDriver.Context(); final MediaDriver.Context mediaDriverContext2 = new MediaDriver.Context(); private void launch(final String channel1, final int stream1, final String channel2, final int stream2) { driver1 = MediaDriver.launchEmbedded(mediaDriverContext1); driver2 = MediaDriver.launchEmbedded(mediaDriverContext2); final Aeron.Context publishingAeronContext1 = new Aeron.Context(); final Aeron.Context subscribingAeronContext1 = new Aeron.Context(); final Aeron.Context publishingAeronContext2 = new Aeron.Context(); final Aeron.Context subscribingAeronContext2 = new Aeron.Context(); publishingAeronContext1.aeronDirectoryName(driver1.aeronDirectoryName()); publishingAeronContext2.aeronDirectoryName(driver1.aeronDirectoryName()); subscribingAeronContext1.aeronDirectoryName(driver2.aeronDirectoryName()); subscribingAeronContext2.aeronDirectoryName(driver2.aeronDirectoryName()); publishingClient1 = Aeron.connect(publishingAeronContext1); subscribingClient1 = Aeron.connect(subscribingAeronContext1); publishingClient2 = Aeron.connect(publishingAeronContext2); subscribingClient2 = Aeron.connect(subscribingAeronContext2); publication1 = publishingClient1.addPublication(channel1, stream1); subscription1 = subscribingClient1.addSubscription(channel1, stream1); publication2 = publishingClient2.addPublication(channel2, stream2); subscription2 = subscribingClient2.addSubscription(channel2, stream2); } @After public void closeEverything() { subscribingClient1.close(); publishingClient1.close(); subscribingClient2.close(); publishingClient2.close(); driver1.close(); driver2.close(); mediaDriverContext1.deleteAeronDirectory(); mediaDriverContext2.deleteAeronDirectory(); } @Test(timeout = 10000) public void shouldSpinUpAndShutdown() { launch(CHANNEL1, STREAM_ID1, CHANNEL2, STREAM_ID2); } @Test(timeout = 10000) public void shouldReceivePublishedMessage() { launch(CHANNEL1, STREAM_ID1, CHANNEL2, STREAM_ID2); buffer.putInt(0, 1); final int numMessagesPerPublication = 1; while (publication1.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L) { Thread.yield(); } while (publication2.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L) { Thread.yield(); } final AtomicInteger fragmentsRead1 = new AtomicInteger(); final AtomicInteger fragmentsRead2 = new AtomicInteger(); SystemTestHelper.executeUntil( () -> fragmentsRead1.get() >= numMessagesPerPublication && fragmentsRead2.get() >= numMessagesPerPublication, (i) -> { fragmentsRead1.addAndGet(subscription1.poll(fragmentHandler1, 10)); fragmentsRead2.addAndGet(subscription2.poll(fragmentHandler2, 10)); Thread.yield(); }, Integer.MAX_VALUE, TimeUnit.MILLISECONDS.toNanos(9900)); assertEquals(numMessagesPerPublication, subscriber1Count.get()); assertEquals(numMessagesPerPublication, subscriber2Count.get()); } @Test(timeout = 10000) public void shouldReceiveMessagesAfterStopStartOnSameChannelSameStream() { shouldReceiveMessagesAfterStopStart(CHANNEL1, STREAM_ID1, CHANNEL1, STREAM_ID1); } @Test(timeout = 10000) public void shouldReceiveMessagesAfterStopStartOnSameChannelDifferentStreams() { shouldReceiveMessagesAfterStopStart(CHANNEL1, STREAM_ID1, CHANNEL1, STREAM_ID2); } @Test(timeout = 10000) public void shouldReceiveMessagesAfterStopStartOnDifferentChannelsSameStream() { shouldReceiveMessagesAfterStopStart(CHANNEL1, STREAM_ID1, CHANNEL2, STREAM_ID1); } @Test(timeout = 10000) public void shouldReceiveMessagesAfterStopStartOnDifferentChannelsDifferentStreams() { shouldReceiveMessagesAfterStopStart(CHANNEL1, STREAM_ID1, CHANNEL2, STREAM_ID2); } private void doPublisherWork(final Publication publication, final AtomicBoolean running) { while (running.get()) { while (running.get() && publication.offer(buffer, 0, BitUtil.SIZE_OF_INT) < 0L) { Thread.yield(); } } } private void shouldReceiveMessagesAfterStopStart( final String channel1, final int stream1, final String channel2, final int stream2) { final ExecutorService executor = Executors.newFixedThreadPool(2); final int numMessages = 1; final AtomicInteger subscriber2AfterRestartCount = new AtomicInteger(); final AtomicBoolean running = new AtomicBoolean(true); final FragmentHandler fragmentHandler2b = (buffer, offset, length, header) -> subscriber2AfterRestartCount.incrementAndGet(); launch(channel1, stream1, channel2, stream2); buffer.putInt(0, 1); executor.execute(() -> doPublisherWork(publication1, running)); executor.execute(() -> doPublisherWork(publication2, running)); final AtomicInteger fragmentsRead1 = new AtomicInteger(); final AtomicInteger fragmentsRead2 = new AtomicInteger(); SystemTestHelper.executeUntil( () -> fragmentsRead1.get() >= numMessages && fragmentsRead2.get() >= numMessages, (i) -> { fragmentsRead1.addAndGet(subscription1.poll(fragmentHandler1, 1)); fragmentsRead2.addAndGet(subscription2.poll(fragmentHandler2, 1)); Thread.yield(); }, Integer.MAX_VALUE, TimeUnit.MILLISECONDS.toNanos(4900)); assertTrue(subscriber1Count.get() >= numMessages); assertTrue(subscriber2Count.get() >= numMessages); // Stop the second subscriber subscription2.close(); // Zero out the counters fragmentsRead1.set(0); fragmentsRead2.set(0); // Start the second subscriber again subscription2 = subscribingClient2.addSubscription(channel2, stream2); SystemTestHelper.executeUntil( () -> fragmentsRead1.get() >= numMessages && fragmentsRead2.get() >= numMessages, (i) -> { fragmentsRead1.addAndGet(subscription1.poll(fragmentHandler1, 1)); fragmentsRead2.addAndGet(subscription2.poll(fragmentHandler2b, 1)); Thread.yield(); }, Integer.MAX_VALUE, TimeUnit.MILLISECONDS.toNanos(4900)); running.set(false); assertTrue("Expecting subscriber1 to receive messages the entire time", subscriber1Count.get() >= numMessages * 2); assertTrue("Expecting subscriber2 to receive messages before being stopped and started", subscriber2Count.get() >= numMessages); assertTrue("Expecting subscriber2 to receive messages after being stopped and started", subscriber2AfterRestartCount.get() >= numMessages); executor.shutdown(); try { while (!executor.awaitTermination(1, TimeUnit.SECONDS)) { System.err.println("Still awaiting termination"); } } catch (final InterruptedException ex) { LangUtil.rethrowUnchecked(ex); } } }