/* * 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.driver; import io.aeron.CommonContext; import io.aeron.DriverProxy; import io.aeron.ErrorCode; import io.aeron.driver.buffer.RawLog; import io.aeron.driver.buffer.RawLogFactory; import io.aeron.driver.media.ReceiveChannelEndpoint; import io.aeron.driver.media.ReceiveChannelEndpointThreadLocals; import io.aeron.driver.media.UdpChannel; import io.aeron.driver.status.SystemCounters; import io.aeron.logbuffer.HeaderWriter; import io.aeron.logbuffer.LogBufferDescriptor; import io.aeron.logbuffer.TermAppender; import io.aeron.protocol.StatusMessageFlyweight; import org.agrona.concurrent.*; import org.agrona.concurrent.errors.DistinctErrorLog; import org.agrona.concurrent.ringbuffer.ManyToOneRingBuffer; import org.agrona.concurrent.ringbuffer.RingBuffer; import org.agrona.concurrent.status.AtomicCounter; import org.agrona.concurrent.status.CountersManager; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.stubbing.Answer; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; import static io.aeron.ErrorCode.*; import static io.aeron.driver.Configuration.*; import static io.aeron.protocol.DataHeaderFlyweight.createDefaultHeader; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.mockito.Mockito.any; import static org.mockito.Mockito.*; public class DriverConductorTest { private static final String CHANNEL_4000 = "aeron:udp?endpoint=localhost:4000"; private static final String CHANNEL_4001 = "aeron:udp?endpoint=localhost:4001"; private static final String CHANNEL_4002 = "aeron:udp?endpoint=localhost:4002"; private static final String CHANNEL_4003 = "aeron:udp?endpoint=localhost:4003"; private static final String CHANNEL_4004 = "aeron:udp?endpoint=localhost:4004"; private static final String CHANNEL_IPC = "aeron:ipc"; private static final String INVALID_URI = "aeron:udp://"; private static final int SESSION_ID = 100; private static final int STREAM_ID_1 = 10; private static final int STREAM_ID_2 = 20; private static final int STREAM_ID_3 = 30; private static final int STREAM_ID_4 = 40; private static final int TERM_BUFFER_LENGTH = Configuration.TERM_BUFFER_LENGTH_DEFAULT; private static final int BUFFER_LENGTH = 16 * 1024; private final ByteBuffer toDriverBuffer = ByteBuffer.allocateDirect(Configuration.CONDUCTOR_BUFFER_LENGTH); private final RawLogFactory mockRawLogFactory = mock(RawLogFactory.class); private final RingBuffer fromClientCommands = new ManyToOneRingBuffer(new UnsafeBuffer(toDriverBuffer)); private final ClientProxy mockClientProxy = mock(ClientProxy.class); private final DistinctErrorLog mockErrorLog = mock(DistinctErrorLog.class); private final AtomicCounter mockErrorCounter = mock(AtomicCounter.class); private final SenderProxy senderProxy = mock(SenderProxy.class); private final ReceiverProxy receiverProxy = mock(ReceiverProxy.class); private final DriverConductorProxy driverConductorProxy = mock(DriverConductorProxy.class); private long currentTimeNs; private NanoClock nanoClock = () -> currentTimeNs; private DriverProxy driverProxy; private DriverConductor driverConductor; private final Answer<Void> closeChannelEndpointAnswer = (invocation) -> { final Object args[] = invocation.getArguments(); final ReceiveChannelEndpoint channelEndpoint = (ReceiveChannelEndpoint)args[0]; channelEndpoint.close(); return null; }; @Before public void setUp() throws Exception { // System GC required in order to ensure that the direct byte buffers get cleaned and avoid OOM. System.gc(); when(mockRawLogFactory.newNetworkPublication(anyString(), anyInt(), anyInt(), anyLong(), anyInt())) .thenReturn(LogBufferHelper.newTestLogBuffers(TERM_BUFFER_LENGTH)); when(mockRawLogFactory.newNetworkedImage(anyString(), anyInt(), anyInt(), anyLong(), eq(TERM_BUFFER_LENGTH))) .thenReturn(LogBufferHelper.newTestLogBuffers(TERM_BUFFER_LENGTH)); when(mockRawLogFactory.newIpcPublication(anyInt(), anyInt(), anyLong(), anyInt())) .thenReturn(LogBufferHelper.newTestLogBuffers(TERM_BUFFER_LENGTH)); currentTimeNs = 0; final UnsafeBuffer counterBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(BUFFER_LENGTH)); final CountersManager countersManager = new CountersManager( new UnsafeBuffer(ByteBuffer.allocateDirect(BUFFER_LENGTH * 2)), counterBuffer); final MediaDriver.Context ctx = new MediaDriver.Context() .unicastFlowControlSupplier(Configuration.unicastFlowControlSupplier()) .multicastFlowControlSupplier(Configuration.multicastFlowControlSupplier()) // TODO: remove .driverCommandQueue(new ManyToOneConcurrentArrayQueue<>(Configuration.CMD_QUEUE_CAPACITY)) .errorLog(mockErrorLog) .rawLogBuffersFactory(mockRawLogFactory) .countersManager(countersManager) .nanoClock(nanoClock) .sendChannelEndpointSupplier(Configuration.sendChannelEndpointSupplier()) .receiveChannelEndpointSupplier(Configuration.receiveChannelEndpointSupplier()) .congestControlSupplier(Configuration.congestionControlSupplier()); ctx.toDriverCommands(fromClientCommands); ctx.clientProxy(mockClientProxy); ctx.countersValuesBuffer(counterBuffer); final SystemCounters mockSystemCounters = mock(SystemCounters.class); ctx.systemCounters(mockSystemCounters); when(mockSystemCounters.get(any())).thenReturn(mockErrorCounter); ctx.epochClock(new SystemEpochClock()); ctx.receiverProxy(receiverProxy); ctx.senderProxy(senderProxy); ctx.driverConductorProxy(driverConductorProxy); ctx.clientLivenessTimeoutNs(CLIENT_LIVENESS_TIMEOUT_NS); ctx.receiveChannelEndpointThreadLocals(new ReceiveChannelEndpointThreadLocals(ctx)); driverProxy = new DriverProxy(fromClientCommands); driverConductor = new DriverConductor(ctx); doAnswer(closeChannelEndpointAnswer).when(receiverProxy).closeReceiveChannelEndpoint(any()); } @After public void tearDown() throws Exception { driverConductor.onClose(); } @Test public void shouldBeAbleToAddSinglePublication() throws Exception { driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); verify(senderProxy).registerSendChannelEndpoint(any()); final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class); verify(senderProxy, times(1)).newNetworkPublication(captor.capture()); final NetworkPublication publication = captor.getValue(); assertThat(publication.streamId(), is(STREAM_ID_1)); verify(mockClientProxy).onPublicationReady(anyLong(), eq(STREAM_ID_1), anyInt(), any(), anyInt(), eq(false)); } @Test public void shouldBeAbleToAddPublicationForReplay() throws Exception { final int mtu = 1024 * 8; final int termLength = 128 * 1024; final int initialTermId = 7; final int termId = 11; final int termOffset = 64; final String params = "|mtu=" + mtu + "|term-length=" + termLength + "|init-term-id=" + initialTermId + "|term-id=" + termId + "|term-offset=" + termOffset; when(mockRawLogFactory.newNetworkPublication(anyString(), anyInt(), anyInt(), anyLong(), eq(termLength))) .thenReturn(LogBufferHelper.newTestLogBuffers(termLength)); driverProxy.addExclusivePublication(CHANNEL_4000 + params, STREAM_ID_1); driverConductor.doWork(); verify(senderProxy).registerSendChannelEndpoint(any()); final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class); verify(senderProxy, times(1)).newNetworkPublication(captor.capture()); final NetworkPublication publication = captor.getValue(); assertThat(publication.streamId(), is(STREAM_ID_1)); assertThat(publication.mtuLength(), is(mtu)); final long expectedPosition = termLength * (termId - initialTermId) + termOffset; assertThat(publication.producerPosition(), is(expectedPosition)); assertThat(publication.consumerPosition(), is(expectedPosition)); verify(mockClientProxy).onPublicationReady(anyLong(), eq(STREAM_ID_1), anyInt(), any(), anyInt(), eq(true)); } @Test public void shouldBeAbleToAddIpcPublicationForReplay() throws Exception { final int termLength = 128 * 1024; final int initialTermId = 7; final int termId = 11; final int termOffset = 64; final String params = "?term-length=" + termLength + "|init-term-id=" + initialTermId + "|term-id=" + termId + "|term-offset=" + termOffset; when(mockRawLogFactory.newIpcPublication(anyInt(), anyInt(), anyLong(), eq(termLength))) .thenReturn(LogBufferHelper.newTestLogBuffers(termLength)); driverProxy.addExclusivePublication(CHANNEL_IPC + params, STREAM_ID_1); driverConductor.doWork(); final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class); verify(mockClientProxy).onPublicationReady( captor.capture(), eq(STREAM_ID_1), anyInt(), any(), anyInt(), eq(true)); final long registrationId = captor.getValue(); final IpcPublication publication = driverConductor.getIpcPublication(registrationId); assertThat(publication.streamId(), is(STREAM_ID_1)); final long expectedPosition = termLength * (termId - initialTermId) + termOffset; assertThat(publication.producerPosition(), is(expectedPosition)); assertThat(publication.consumerPosition(), is(expectedPosition)); } @Test public void shouldBeAbleToAddSingleSubscription() throws Exception { final long id = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); verify(receiverProxy).registerReceiveChannelEndpoint(any()); verify(receiverProxy).addSubscription(any(), eq(STREAM_ID_1)); verify(mockClientProxy).operationSucceeded(id); assertNotNull(driverConductor.receiverChannelEndpoint(UdpChannel.parse(CHANNEL_4000))); } @Test public void shouldBeAbleToAddAndRemoveSingleSubscription() throws Exception { final long id = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverProxy.removeSubscription(id); driverConductor.doWork(); assertNull(driverConductor.receiverChannelEndpoint(UdpChannel.parse(CHANNEL_4000))); } @Test public void shouldBeAbleToAddMultipleStreams() throws Exception { driverProxy.addPublication(CHANNEL_4001, STREAM_ID_1); driverProxy.addPublication(CHANNEL_4002, STREAM_ID_2); driverProxy.addPublication(CHANNEL_4003, STREAM_ID_3); driverProxy.addPublication(CHANNEL_4004, STREAM_ID_4); driverConductor.doWork(); verify(senderProxy, times(4)).newNetworkPublication(any()); } @Test public void shouldBeAbleToRemoveSingleStream() throws Exception { final long id = driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1); driverProxy.removePublication(id); driverConductor.doWork(); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS + PUBLICATION_LINGER_NS * 2); verify(senderProxy).removeNetworkPublication(any()); assertNull(driverConductor.senderChannelEndpoint(UdpChannel.parse(CHANNEL_4000))); } @Test public void shouldBeAbleToRemoveMultipleStreams() throws Exception { final long id1 = driverProxy.addPublication(CHANNEL_4001, STREAM_ID_1); final long id2 = driverProxy.addPublication(CHANNEL_4002, STREAM_ID_2); final long id3 = driverProxy.addPublication(CHANNEL_4003, STREAM_ID_3); final long id4 = driverProxy.addPublication(CHANNEL_4004, STREAM_ID_4); driverProxy.removePublication(id1); driverProxy.removePublication(id2); driverProxy.removePublication(id3); driverProxy.removePublication(id4); driverConductor.doWork(); doWorkUntil(() -> nanoClock.nanoTime() >= PUBLICATION_LINGER_NS * 2 + CLIENT_LIVENESS_TIMEOUT_NS * 2); verify(senderProxy, times(4)).removeNetworkPublication(any()); } @Test public void shouldKeepSubscriptionMediaEndpointUponRemovalOfAllButOneSubscriber() throws Exception { final UdpChannel udpChannel = UdpChannel.parse(CHANNEL_4000); final long id1 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); final long id2 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_2); driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_3); driverConductor.doWork(); final ReceiveChannelEndpoint channelEndpoint = driverConductor.receiverChannelEndpoint(udpChannel); assertNotNull(channelEndpoint); assertThat(channelEndpoint.streamCount(), is(3)); driverProxy.removeSubscription(id1); driverProxy.removeSubscription(id2); driverConductor.doWork(); assertNotNull(driverConductor.receiverChannelEndpoint(udpChannel)); assertThat(channelEndpoint.streamCount(), is(1)); } @Test public void shouldOnlyRemoveSubscriptionMediaEndpointUponRemovalOfAllSubscribers() throws Exception { final UdpChannel udpChannel = UdpChannel.parse(CHANNEL_4000); final long id1 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); final long id2 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_2); final long id3 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_3); driverConductor.doWork(); final ReceiveChannelEndpoint channelEndpoint = driverConductor.receiverChannelEndpoint(udpChannel); assertNotNull(channelEndpoint); assertThat(channelEndpoint.streamCount(), is(3)); driverProxy.removeSubscription(id2); driverProxy.removeSubscription(id3); driverConductor.doWork(); assertNotNull(driverConductor.receiverChannelEndpoint(udpChannel)); assertThat(channelEndpoint.streamCount(), is(1)); driverProxy.removeSubscription(id1); driverConductor.doWork(); assertNull(driverConductor.receiverChannelEndpoint(udpChannel)); } @Test public void shouldErrorOnRemovePublicationOnUnknownRegistrationId() throws Exception { final long id = driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1); driverProxy.removePublication(id + 1); driverConductor.doWork(); final InOrder inOrder = inOrder(senderProxy, mockClientProxy); inOrder.verify(senderProxy).newNetworkPublication(any()); inOrder.verify(mockClientProxy).onPublicationReady( eq(id), eq(STREAM_ID_1), anyInt(), any(), anyInt(), eq(false)); inOrder.verify(mockClientProxy).onError(eq(UNKNOWN_PUBLICATION), anyString(), anyLong()); inOrder.verifyNoMoreInteractions(); verify(mockErrorCounter).increment(); verify(mockErrorLog).record(any(Throwable.class)); } @Test public void shouldAddPublicationWithMtu() throws Exception { final int mtuLength = 4096; final String mtuParam = "|" + CommonContext.MTU_LENGTH_URI_PARAM_NAME + "=" + mtuLength; driverProxy.addPublication(CHANNEL_4000 + mtuParam, STREAM_ID_1); driverConductor.doWork(); final ArgumentCaptor<NetworkPublication> argumentCaptor = ArgumentCaptor.forClass(NetworkPublication.class); verify(senderProxy).newNetworkPublication(argumentCaptor.capture()); assertThat(argumentCaptor.getValue().mtuLength(), is(mtuLength)); } @Test public void shouldErrorOnRemoveSubscriptionOnUnknownRegistrationId() throws Exception { final long id1 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverProxy.removeSubscription(id1 + 100); driverConductor.doWork(); final InOrder inOrder = inOrder(receiverProxy, mockClientProxy); inOrder.verify(receiverProxy).addSubscription(any(), anyInt()); inOrder.verify(mockClientProxy).operationSucceeded(id1); inOrder.verify(mockClientProxy).onError(eq(UNKNOWN_SUBSCRIPTION), anyString(), anyLong()); inOrder.verifyNoMoreInteractions(); verify(mockErrorLog).record(any(Throwable.class)); } @Test public void shouldErrorOnAddSubscriptionWithInvalidUri() throws Exception { driverProxy.addSubscription(INVALID_URI, STREAM_ID_1); driverConductor.doWork(); driverConductor.doWork(); verify(senderProxy, never()).newNetworkPublication(any()); verify(mockClientProxy).onError(eq(INVALID_CHANNEL), anyString(), anyLong()); verify(mockClientProxy, never()).operationSucceeded(anyLong()); verify(mockErrorCounter).increment(); verify(mockErrorLog).record(any(Throwable.class)); } @Test public void shouldTimeoutPublication() throws Exception { driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class); verify(senderProxy, times(1)).newNetworkPublication(captor.capture()); final NetworkPublication publication = captor.getValue(); doWorkUntil(() -> nanoClock.nanoTime() >= PUBLICATION_LINGER_NS + CLIENT_LIVENESS_TIMEOUT_NS * 2); verify(senderProxy).removeNetworkPublication(eq(publication)); assertNull(driverConductor.senderChannelEndpoint(UdpChannel.parse(CHANNEL_4000))); } @Test public void shouldNotTimeoutPublicationOnKeepAlive() throws Exception { driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class); verify(senderProxy, times(1)).newNetworkPublication(captor.capture()); final NetworkPublication publication = captor.getValue(); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS / 2); driverProxy.sendClientKeepalive(); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS + 1000); driverProxy.sendClientKeepalive(); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS * 2); verify(senderProxy, never()).removeNetworkPublication(eq(publication)); } @Test public void shouldTimeoutPublicationWithNoKeepaliveButNotFlushed() throws Exception { driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class); verify(senderProxy, times(1)).newNetworkPublication(captor.capture()); final NetworkPublication publication = captor.getValue(); final int termId = 101; final int index = LogBufferDescriptor.indexByTerm(termId, termId); final RawLog rawLog = publication.rawLog(); final TermAppender appender = new TermAppender(rawLog.termBuffers()[index], rawLog.metaData(), index); final UnsafeBuffer srcBuffer = new UnsafeBuffer(new byte[256]); final HeaderWriter headerWriter = new HeaderWriter( createDefaultHeader(publication.sessionId(), STREAM_ID_1, termId)); final StatusMessageFlyweight msg = mock(StatusMessageFlyweight.class); when(msg.consumptionTermId()).thenReturn(termId); when(msg.consumptionTermOffset()).thenReturn(0); when(msg.receiverWindowLength()).thenReturn(10); publication.onStatusMessage(msg, new InetSocketAddress("localhost", 4059)); appender.appendUnfragmentedMessage(headerWriter, srcBuffer, 0, 256, null); doWorkUntil(() -> nanoClock.nanoTime() >= PUBLICATION_LINGER_NS + CLIENT_LIVENESS_TIMEOUT_NS * 2); assertThat(publication.status(), is(NetworkPublication.Status.CLOSING)); verify(senderProxy).removeNetworkPublication(eq(publication)); assertNull(driverConductor.senderChannelEndpoint(UdpChannel.parse(CHANNEL_4000))); } @Test public void shouldTimeoutSubscription() throws Exception { driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); final ReceiveChannelEndpoint receiveChannelEndpoint = driverConductor.receiverChannelEndpoint(UdpChannel.parse(CHANNEL_4000)); assertNotNull(receiveChannelEndpoint); verify(receiverProxy).addSubscription(eq(receiveChannelEndpoint), eq(STREAM_ID_1)); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS * 2); verify(receiverProxy, times(1)) .removeSubscription(eq(receiveChannelEndpoint), eq(STREAM_ID_1)); assertNull(driverConductor.receiverChannelEndpoint(UdpChannel.parse(CHANNEL_4000))); } @Test public void shouldNotTimeoutSubscriptionOnKeepAlive() throws Exception { driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); final ReceiveChannelEndpoint receiveChannelEndpoint = driverConductor.receiverChannelEndpoint(UdpChannel.parse(CHANNEL_4000)); assertNotNull(receiveChannelEndpoint); verify(receiverProxy).addSubscription(eq(receiveChannelEndpoint), eq(STREAM_ID_1)); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS); driverProxy.sendClientKeepalive(); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS + 1000); driverProxy.sendClientKeepalive(); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS * 2); verify(receiverProxy, never()).removeSubscription(any(), anyInt()); assertNotNull(driverConductor.receiverChannelEndpoint(UdpChannel.parse(CHANNEL_4000))); } @Test public void shouldCreateImageOnSubscription() throws Exception { final InetSocketAddress sourceAddress = new InetSocketAddress("localhost", 4400); final int initialTermId = 1; final int activeTermId = 2; final int termOffset = 100; driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); final ReceiveChannelEndpoint receiveChannelEndpoint = driverConductor.receiverChannelEndpoint(UdpChannel.parse(CHANNEL_4000)); assertNotNull(receiveChannelEndpoint); receiveChannelEndpoint.openChannel(); driverConductor.onCreatePublicationImage( SESSION_ID, STREAM_ID_1, initialTermId, activeTermId, termOffset, TERM_BUFFER_LENGTH, MTU_LENGTH, mock(InetSocketAddress.class), sourceAddress, receiveChannelEndpoint); final ArgumentCaptor<PublicationImage> captor = ArgumentCaptor.forClass(PublicationImage.class); verify(receiverProxy).newPublicationImage(eq(receiveChannelEndpoint), captor.capture()); final PublicationImage publicationImage = captor.getValue(); assertThat(publicationImage.sessionId(), is(SESSION_ID)); assertThat(publicationImage.streamId(), is(STREAM_ID_1)); verify(mockClientProxy).onAvailableImage( anyLong(), eq(STREAM_ID_1), eq(SESSION_ID), any(), any(), anyString()); } @Test public void shouldNotCreateImageOnUnknownSubscription() throws Exception { final InetSocketAddress sourceAddress = new InetSocketAddress("localhost", 4400); driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); final ReceiveChannelEndpoint receiveChannelEndpoint = driverConductor.receiverChannelEndpoint(UdpChannel.parse(CHANNEL_4000)); assertNotNull(receiveChannelEndpoint); receiveChannelEndpoint.openChannel(); driverConductor.onCreatePublicationImage( SESSION_ID, STREAM_ID_2, 1, 1, 0, TERM_BUFFER_LENGTH, MTU_LENGTH, mock(InetSocketAddress.class), sourceAddress, receiveChannelEndpoint); verify(receiverProxy, never()).newPublicationImage(any(), any()); verify(mockClientProxy, never()).onAvailableImage( anyLong(), anyInt(), anyInt(), any(), any(), anyString()); } @Test public void shouldSignalInactiveImageWhenImageTimesOut() throws Exception { final InetSocketAddress sourceAddress = new InetSocketAddress("localhost", 4400); driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); final ReceiveChannelEndpoint receiveChannelEndpoint = driverConductor.receiverChannelEndpoint(UdpChannel.parse(CHANNEL_4000)); assertNotNull(receiveChannelEndpoint); receiveChannelEndpoint.openChannel(); driverConductor.onCreatePublicationImage( SESSION_ID, STREAM_ID_1, 1, 1, 0, TERM_BUFFER_LENGTH, MTU_LENGTH, mock(InetSocketAddress.class), sourceAddress, receiveChannelEndpoint); final ArgumentCaptor<PublicationImage> captor = ArgumentCaptor.forClass(PublicationImage.class); verify(receiverProxy).newPublicationImage(eq(receiveChannelEndpoint), captor.capture()); final PublicationImage publicationImage = captor.getValue(); publicationImage.status(PublicationImage.Status.INACTIVE); doWorkUntil(() -> nanoClock.nanoTime() >= IMAGE_LIVENESS_TIMEOUT_NS + 1000); verify(mockClientProxy).onUnavailableImage(eq(publicationImage.correlationId()), eq(STREAM_ID_1), anyString()); } @Test public void shouldAlwaysGiveNetworkPublicationCorrelationIdToClientCallbacks() throws Exception { final InetSocketAddress sourceAddress = new InetSocketAddress("localhost", 4400); driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); final ReceiveChannelEndpoint receiveChannelEndpoint = driverConductor.receiverChannelEndpoint(UdpChannel.parse(CHANNEL_4000)); assertNotNull(receiveChannelEndpoint); receiveChannelEndpoint.openChannel(); driverConductor.onCreatePublicationImage( SESSION_ID, STREAM_ID_1, 1, 1, 0, TERM_BUFFER_LENGTH, MTU_LENGTH, mock(InetSocketAddress.class), sourceAddress, receiveChannelEndpoint); final ArgumentCaptor<PublicationImage> captor = ArgumentCaptor.forClass(PublicationImage.class); verify(receiverProxy).newPublicationImage(eq(receiveChannelEndpoint), captor.capture()); final PublicationImage publicationImage = captor.getValue(); publicationImage.status(PublicationImage.Status.ACTIVE); driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); publicationImage.status(PublicationImage.Status.INACTIVE); doWorkUntil(() -> nanoClock.nanoTime() >= IMAGE_LIVENESS_TIMEOUT_NS + 1000); final InOrder inOrder = inOrder(mockClientProxy); inOrder.verify(mockClientProxy, times(2)).onAvailableImage( eq(publicationImage.correlationId()), eq(STREAM_ID_1), eq(SESSION_ID), any(), any(), anyString()); inOrder.verify(mockClientProxy, times(1)).onUnavailableImage( eq(publicationImage.correlationId()), eq(STREAM_ID_1), anyString()); } @Test public void shouldNotSendAvailableImageWhileImageNotActiveOnAddSubscription() throws Exception { final InetSocketAddress sourceAddress = new InetSocketAddress("localhost", 4400); final long subOneId = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); final ReceiveChannelEndpoint receiveChannelEndpoint = driverConductor.receiverChannelEndpoint(UdpChannel.parse(CHANNEL_4000)); assertNotNull(receiveChannelEndpoint); receiveChannelEndpoint.openChannel(); driverConductor.onCreatePublicationImage( SESSION_ID, STREAM_ID_1, 1, 1, 0, TERM_BUFFER_LENGTH, MTU_LENGTH, mock(InetSocketAddress.class), sourceAddress, receiveChannelEndpoint); final ArgumentCaptor<PublicationImage> captor = ArgumentCaptor.forClass(PublicationImage.class); verify(receiverProxy).newPublicationImage(eq(receiveChannelEndpoint), captor.capture()); final PublicationImage publicationImage = captor.getValue(); publicationImage.status(PublicationImage.Status.INACTIVE); doWorkUntil(() -> nanoClock.nanoTime() >= IMAGE_LIVENESS_TIMEOUT_NS / 2); driverProxy.sendClientKeepalive(); doWorkUntil(() -> nanoClock.nanoTime() >= IMAGE_LIVENESS_TIMEOUT_NS + 1000); final long subTwoId = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); final InOrder inOrder = inOrder(mockClientProxy); inOrder.verify(mockClientProxy, times(1)).operationSucceeded(subOneId); inOrder.verify(mockClientProxy, times(1)).onAvailableImage( eq(publicationImage.correlationId()), eq(STREAM_ID_1), eq(SESSION_ID), any(), any(), anyString()); inOrder.verify(mockClientProxy, times(1)).onUnavailableImage( eq(publicationImage.correlationId()), eq(STREAM_ID_1), anyString()); inOrder.verify(mockClientProxy, times(1)).operationSucceeded(subTwoId); inOrder.verifyNoMoreInteractions(); } @Test public void shouldBeAbleToAddSingleIpcPublication() throws Exception { final long id = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1); driverConductor.doWork(); assertNotNull(driverConductor.getSharedIpcPublication(STREAM_ID_1)); verify(mockClientProxy).onPublicationReady(eq(id), eq(STREAM_ID_1), anyInt(), any(), anyInt(), eq(false)); } @Test public void shouldBeAbleToAddIpcPublicationThenSubscription() throws Exception { final long idPub = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1); final long idSub = driverProxy.addSubscription(CHANNEL_IPC, STREAM_ID_1); driverConductor.doWork(); final IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNotNull(ipcPublication); final InOrder inOrder = inOrder(mockClientProxy); inOrder.verify(mockClientProxy).onPublicationReady( eq(idPub), eq(STREAM_ID_1), anyInt(), any(), anyInt(), eq(false)); inOrder.verify(mockClientProxy).operationSucceeded(eq(idSub)); inOrder.verify(mockClientProxy).onAvailableImage( eq(ipcPublication.registrationId()), eq(STREAM_ID_1), eq(ipcPublication.sessionId()), eq(ipcPublication.rawLog().fileName()), any(), anyString()); } @Test public void shouldBeAbleToAddThenRemoveTheAddIpcPublicationWithExistingSubscription() throws Exception { final long idSub = driverProxy.addSubscription(CHANNEL_IPC, STREAM_ID_1); final long idPubOne = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1); driverConductor.doWork(); final IpcPublication ipcPublicationOne = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNotNull(ipcPublicationOne); final long idPubOneRemove = driverProxy.removePublication(idPubOne); driverConductor.doWork(); final long idPubTwo = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1); driverConductor.doWork(); final IpcPublication ipcPublicationTwo = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNotNull(ipcPublicationTwo); final InOrder inOrder = inOrder(mockClientProxy); inOrder.verify(mockClientProxy).operationSucceeded(eq(idSub)); inOrder.verify(mockClientProxy).onPublicationReady( eq(idPubOne), eq(STREAM_ID_1), anyInt(), any(), anyInt(), eq(false)); inOrder.verify(mockClientProxy).onAvailableImage( eq(ipcPublicationOne.registrationId()), eq(STREAM_ID_1), eq(ipcPublicationOne.sessionId()), eq(ipcPublicationOne.rawLog().fileName()), any(), anyString()); inOrder.verify(mockClientProxy).operationSucceeded(eq(idPubOneRemove)); inOrder.verify(mockClientProxy).onPublicationReady( eq(idPubTwo), eq(STREAM_ID_1), anyInt(), any(), anyInt(), eq(false)); inOrder.verify(mockClientProxy).onAvailableImage( eq(ipcPublicationTwo.registrationId()), eq(STREAM_ID_1), eq(ipcPublicationTwo.sessionId()), eq(ipcPublicationTwo.rawLog().fileName()), any(), anyString()); } @Test public void shouldBeAbleToAddSubscriptionThenIpcPublication() throws Exception { final long idSub = driverProxy.addSubscription(CHANNEL_IPC, STREAM_ID_1); final long idPub = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1); driverConductor.doWork(); final IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNotNull(ipcPublication); final InOrder inOrder = inOrder(mockClientProxy); inOrder.verify(mockClientProxy).operationSucceeded(eq(idSub)); inOrder.verify(mockClientProxy).onPublicationReady( eq(idPub), eq(STREAM_ID_1), anyInt(), any(), anyInt(), eq(false)); inOrder.verify(mockClientProxy).onAvailableImage( eq(ipcPublication.registrationId()), eq(STREAM_ID_1), eq(ipcPublication.sessionId()), eq(ipcPublication.rawLog().fileName()), any(), anyString()); } @Test public void shouldBeAbleToAddAndRemoveIpcPublication() throws Exception { final long idAdd = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1); driverProxy.removePublication(idAdd); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS); final IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNull(ipcPublication); } @Test public void shouldBeAbleToAddAndRemoveSubscriptionToIpcPublication() throws Exception { final long idAdd = driverProxy.addSubscription(CHANNEL_IPC, STREAM_ID_1); driverProxy.removeSubscription(idAdd); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS); final IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNull(ipcPublication); } @Test public void shouldBeAbleToAddAndRemoveTwoIpcPublications() throws Exception { final long idAdd1 = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1); final long idAdd2 = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1); driverProxy.removePublication(idAdd1); driverConductor.doWork(); IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNotNull(ipcPublication); driverProxy.removePublication(idAdd2); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS); ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNull(ipcPublication); } @Test public void shouldBeAbleToAddAndRemoveIpcPublicationAndSubscription() throws Exception { final long idAdd1 = driverProxy.addSubscription(CHANNEL_IPC, STREAM_ID_1); final long idAdd2 = driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1); driverProxy.removeSubscription(idAdd1); driverConductor.doWork(); IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNotNull(ipcPublication); driverProxy.removePublication(idAdd2); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS); ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNull(ipcPublication); } @Test public void shouldTimeoutIpcPublication() throws Exception { driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1); driverConductor.doWork(); IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNotNull(ipcPublication); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS * 2); ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNull(ipcPublication); } @Test public void shouldNotTimeoutIpcPublicationWithKeepalive() throws Exception { driverProxy.addPublication(CHANNEL_IPC, STREAM_ID_1); driverConductor.doWork(); IpcPublication ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNotNull(ipcPublication); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS); driverProxy.sendClientKeepalive(); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS); ipcPublication = driverConductor.getSharedIpcPublication(STREAM_ID_1); assertNotNull(ipcPublication); } @Test public void shouldBeAbleToAddSingleSpy() throws Exception { final long id = driverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1); driverConductor.doWork(); verify(receiverProxy, never()).registerReceiveChannelEndpoint(any()); verify(receiverProxy, never()).addSubscription(any(), eq(STREAM_ID_1)); verify(mockClientProxy).operationSucceeded(id); assertNull(driverConductor.receiverChannelEndpoint(UdpChannel.parse(CHANNEL_4000))); } @Test public void shouldBeAbleToAddNetworkPublicationThenSingleSpy() throws Exception { driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1); final long idSpy = driverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1); driverConductor.doWork(); final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class); verify(senderProxy, times(1)).newNetworkPublication(captor.capture()); final NetworkPublication publication = captor.getValue(); assertTrue(publication.hasSpies()); final InOrder inOrder = inOrder(mockClientProxy); inOrder.verify(mockClientProxy).operationSucceeded(eq(idSpy)); inOrder.verify(mockClientProxy).onAvailableImage( eq(networkPublicationCorrelationId(publication)), eq(STREAM_ID_1), eq(publication.sessionId()), eq(publication.rawLog().fileName()), any(), anyString()); } @Test public void shouldBeAbleToAddSingleSpyThenNetworkPublication() throws Exception { final long idSpy = driverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1); driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class); verify(senderProxy, times(1)).newNetworkPublication(captor.capture()); final NetworkPublication publication = captor.getValue(); assertTrue(publication.hasSpies()); final InOrder inOrder = inOrder(mockClientProxy); inOrder.verify(mockClientProxy).operationSucceeded(eq(idSpy)); inOrder.verify(mockClientProxy).onAvailableImage( eq(networkPublicationCorrelationId(publication)), eq(STREAM_ID_1), eq(publication.sessionId()), eq(publication.rawLog().fileName()), any(), anyString()); } @Test public void shouldBeAbleToAddNetworkPublicationThenSingleSpyThenRemoveSpy() throws Exception { driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1); final long idSpy = driverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1); driverProxy.removeSubscription(idSpy); driverConductor.doWork(); final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class); verify(senderProxy, times(1)).newNetworkPublication(captor.capture()); final NetworkPublication publication = captor.getValue(); assertFalse(publication.hasSpies()); } @Test public void shouldTimeoutSpy() throws Exception { driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1); driverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1); driverConductor.doWork(); final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class); verify(senderProxy, times(1)).newNetworkPublication(captor.capture()); final NetworkPublication publication = captor.getValue(); assertTrue(publication.hasSpies()); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS * 2); assertFalse(publication.hasSpies()); } @Test public void shouldNotTimeoutSpyWithKeepalive() throws Exception { driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1); driverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1); driverConductor.doWork(); final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class); verify(senderProxy, times(1)).newNetworkPublication(captor.capture()); final NetworkPublication publication = captor.getValue(); assertTrue(publication.hasSpies()); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS); driverProxy.sendClientKeepalive(); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS); assertTrue(publication.hasSpies()); } @Test public void shouldTimeoutNetworkPublicationWithSpy() throws Exception { final DriverProxy spyDriverProxy = new DriverProxy(fromClientCommands); driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1); spyDriverProxy.addSubscription(spyForChannel(CHANNEL_4000), STREAM_ID_1); driverConductor.doWork(); final ArgumentCaptor<NetworkPublication> captor = ArgumentCaptor.forClass(NetworkPublication.class); verify(senderProxy, times(1)).newNetworkPublication(captor.capture()); final NetworkPublication publication = captor.getValue(); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS / 2); spyDriverProxy.sendClientKeepalive(); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS + 1000); spyDriverProxy.sendClientKeepalive(); doWorkUntil(() -> nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS * 2); verify(mockClientProxy).onUnavailableImage( eq(networkPublicationCorrelationId(publication)), eq(STREAM_ID_1), anyString()); } @Test public void shouldOnlyCloseSendChannelEndpointOnceWithMultiplePublications() throws Exception { final long id1 = driverProxy.addPublication(CHANNEL_4000, STREAM_ID_1); final long id2 = driverProxy.addPublication(CHANNEL_4000, STREAM_ID_2); driverProxy.removePublication(id1); driverProxy.removePublication(id2); driverConductor.doWork(); doWorkUntil( () -> { driverProxy.sendClientKeepalive(); return nanoClock.nanoTime() >= PUBLICATION_LINGER_NS * 2; }); verify(senderProxy, times(1)).closeSendChannelEndpoint(any()); } @Test public void shouldOnlyCloseReceiveChannelEndpointOnceWithMultipleSubscriptions() throws Exception { final long id1 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); final long id2 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_2); driverProxy.removeSubscription(id1); driverProxy.removeSubscription(id2); driverConductor.doWork(); doWorkUntil( () -> { driverProxy.sendClientKeepalive(); return nanoClock.nanoTime() >= CLIENT_LIVENESS_TIMEOUT_NS * 2; }); verify(receiverProxy, times(1)).closeReceiveChannelEndpoint(any()); } @Test public void shouldErrorWhenConflictingUnreliableSubscriptionAdded() throws Exception { driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); final long id2 = driverProxy.addSubscription(CHANNEL_4000 + "|reliable=false", STREAM_ID_1); driverConductor.doWork(); verify(mockClientProxy).onError(any(ErrorCode.class), anyString(), eq(id2)); } @Test public void shouldErrorWhenConflictingDefaultReliableSubscriptionAdded() throws Exception { driverProxy.addSubscription(CHANNEL_4000 + "|reliable=false", STREAM_ID_1); driverConductor.doWork(); final long id2 = driverProxy.addSubscription(CHANNEL_4000, STREAM_ID_1); driverConductor.doWork(); verify(mockClientProxy).onError(any(ErrorCode.class), anyString(), eq(id2)); } @Test public void shouldErrorWhenConflictingReliableSubscriptionAdded() throws Exception { driverProxy.addSubscription(CHANNEL_4000 + "|reliable=false", STREAM_ID_1); driverConductor.doWork(); final long id2 = driverProxy.addSubscription(CHANNEL_4000 + "|reliable=true", STREAM_ID_1); driverConductor.doWork(); verify(mockClientProxy).onError(any(ErrorCode.class), anyString(), eq(id2)); } private long doWorkUntil(final BooleanSupplier condition) throws Exception { final long startTime = currentTimeNs; while (!condition.getAsBoolean()) { currentTimeNs += TimeUnit.MILLISECONDS.toNanos(10); driverConductor.doWork(); } return currentTimeNs - startTime; } private static String spyForChannel(final String channel) { return CommonContext.SPY_PREFIX + channel; } private static long networkPublicationCorrelationId(final NetworkPublication publication) { return LogBufferDescriptor.correlationId(publication.rawLog().metaData()); } }