/* * 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.ReservedValueSupplier; import io.aeron.driver.status.SystemCounters; import org.junit.experimental.theories.DataPoint; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; import org.junit.runner.RunWith; import org.mockito.InOrder; import io.aeron.logbuffer.HeaderWriter; import io.aeron.logbuffer.FrameDescriptor; import io.aeron.logbuffer.LogBufferDescriptor; import io.aeron.logbuffer.TermAppender; import io.aeron.logbuffer.TermRebuilder; import io.aeron.protocol.DataHeaderFlyweight; import io.aeron.protocol.HeaderFlyweight; import org.agrona.concurrent.UnsafeBuffer; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.stream.IntStream; import static java.nio.ByteBuffer.allocateDirect; import static org.mockito.Mockito.*; import static org.agrona.BitUtil.align; @RunWith(Theories.class) public class RetransmitHandlerTest { private static final int MTU_LENGTH = 1024; private static final int TERM_BUFFER_LENGTH = LogBufferDescriptor.TERM_MIN_LENGTH; private static final byte[] DATA = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; private static final int MESSAGE_LENGTH = DataHeaderFlyweight.HEADER_LENGTH + DATA.length; private static final int ALIGNED_FRAME_LENGTH = align(MESSAGE_LENGTH, FrameDescriptor.FRAME_ALIGNMENT); private static final int SESSION_ID = 0x5E55101D; private static final int STREAM_ID = 0x5400E; private static final int TERM_ID = 0x7F003355; private static final FeedbackDelayGenerator DELAY_GENERATOR = () -> TimeUnit.MILLISECONDS.toNanos(20); private static final FeedbackDelayGenerator ZERO_DELAY_GENERATOR = () -> TimeUnit.MILLISECONDS.toNanos(0); private static final FeedbackDelayGenerator LINGER_GENERATOR = () -> TimeUnit.MILLISECONDS.toNanos(40); private static final ReservedValueSupplier RESERVED_VALUE_SUPPLIER = null; private final UnsafeBuffer termBuffer = new UnsafeBuffer(allocateDirect(TERM_BUFFER_LENGTH)); private final UnsafeBuffer metaDataBuffer = new UnsafeBuffer( allocateDirect(LogBufferDescriptor.LOG_META_DATA_LENGTH)); private final TermAppender termAppender = new TermAppender(termBuffer, metaDataBuffer, 0); private final UnsafeBuffer rcvBuffer = new UnsafeBuffer(new byte[MESSAGE_LENGTH]); private DataHeaderFlyweight dataHeader = new DataHeaderFlyweight(); private long currentTime = 0; private final RetransmitSender retransmitSender = mock(RetransmitSender.class); private final SystemCounters systemCounters = mock(SystemCounters.class); private final HeaderWriter headerWriter = new HeaderWriter(DataHeaderFlyweight.createDefaultHeader(0, 0, 0)); private RetransmitHandler handler = new RetransmitHandler( () -> currentTime, systemCounters, DELAY_GENERATOR, LINGER_GENERATOR); @DataPoint public static final BiConsumer<RetransmitHandlerTest, Integer> SENDER_ADD_DATA_FRAME = (h, i) -> h.addSentDataFrame(); @DataPoint public static final BiConsumer<RetransmitHandlerTest, Integer> RECEIVER_ADD_DATA_FRAME = RetransmitHandlerTest::addReceivedDataFrame; @Theory public void shouldRetransmitOnNak(final BiConsumer<RetransmitHandlerTest, Integer> creator) { createTermBuffer(creator, 5); handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); currentTime = TimeUnit.MILLISECONDS.toNanos(100); handler.processTimeouts(currentTime, retransmitSender); verify(retransmitSender).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH); } @Theory public void shouldNotRetransmitOnNakWhileInLinger(final BiConsumer<RetransmitHandlerTest, Integer> creator) { createTermBuffer(creator, 5); handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); currentTime = TimeUnit.MILLISECONDS.toNanos(40); handler.processTimeouts(currentTime, retransmitSender); handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); currentTime = TimeUnit.MILLISECONDS.toNanos(100); handler.processTimeouts(currentTime, retransmitSender); verify(retransmitSender).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH); } @Theory public void shouldRetransmitOnNakAfterLinger(final BiConsumer<RetransmitHandlerTest, Integer> creator) { createTermBuffer(creator, 5); handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); currentTime = TimeUnit.MILLISECONDS.toNanos(40); handler.processTimeouts(currentTime, retransmitSender); currentTime = TimeUnit.MILLISECONDS.toNanos(100); handler.processTimeouts(currentTime, retransmitSender); handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); currentTime = TimeUnit.MILLISECONDS.toNanos(200); handler.processTimeouts(currentTime, retransmitSender); verify(retransmitSender, times(2)).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH); } @Theory public void shouldRetransmitOnMultipleNaks(final BiConsumer<RetransmitHandlerTest, Integer> creator) { createTermBuffer(creator, 5); handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); handler.onNak(TERM_ID, offsetOfFrame(1), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); currentTime = TimeUnit.MILLISECONDS.toNanos(100); handler.processTimeouts(currentTime, retransmitSender); final InOrder inOrder = inOrder(retransmitSender); inOrder.verify(retransmitSender).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH); inOrder.verify(retransmitSender).resend(TERM_ID, offsetOfFrame(1), ALIGNED_FRAME_LENGTH); } @Theory public void shouldRetransmitOnNakOverMessageLength(final BiConsumer<RetransmitHandlerTest, Integer> creator) { createTermBuffer(creator, 10); handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH * 5, TERM_BUFFER_LENGTH, retransmitSender); currentTime = TimeUnit.MILLISECONDS.toNanos(100); handler.processTimeouts(currentTime, retransmitSender); verify(retransmitSender).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH * 5); } @Theory public void shouldRetransmitOnNakOverMtuLength(final BiConsumer<RetransmitHandlerTest, Integer> creator) { final int numFramesPerMtu = MTU_LENGTH / ALIGNED_FRAME_LENGTH; createTermBuffer(creator, numFramesPerMtu * 5); handler.onNak(TERM_ID, offsetOfFrame(0), MTU_LENGTH * 2, TERM_BUFFER_LENGTH, retransmitSender); currentTime = TimeUnit.MILLISECONDS.toNanos(100); handler.processTimeouts(currentTime, retransmitSender); verify(retransmitSender).resend(TERM_ID, offsetOfFrame(0), MTU_LENGTH * 2); } @Theory public void shouldStopRetransmitOnRetransmitReception(final BiConsumer<RetransmitHandlerTest, Integer> creator) { createTermBuffer(creator, 5); handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); handler.onRetransmitReceived(TERM_ID, offsetOfFrame(0)); currentTime = TimeUnit.MILLISECONDS.toNanos(100); handler.processTimeouts(currentTime, retransmitSender); verifyZeroInteractions(retransmitSender); } @Theory public void shouldStopOneRetransmitOnRetransmitReception(final BiConsumer<RetransmitHandlerTest, Integer> creator) { createTermBuffer(creator, 5); handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); handler.onNak(TERM_ID, offsetOfFrame(1), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); handler.onRetransmitReceived(TERM_ID, offsetOfFrame(0)); currentTime = TimeUnit.MILLISECONDS.toNanos(100); handler.processTimeouts(currentTime, retransmitSender); verify(retransmitSender).resend(TERM_ID, offsetOfFrame(1), ALIGNED_FRAME_LENGTH); } @Theory public void shouldImmediateRetransmitOnNak(final BiConsumer<RetransmitHandlerTest, Integer> creator) { createTermBuffer(creator, 5); handler = newZeroDelayRetransmitHandler(); handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); verify(retransmitSender).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH); } @Theory public void shouldGoIntoLingerOnImmediateRetransmit(final BiConsumer<RetransmitHandlerTest, Integer> creator) { createTermBuffer(creator, 5); handler = newZeroDelayRetransmitHandler(); handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); currentTime = TimeUnit.MILLISECONDS.toNanos(40); handler.processTimeouts(currentTime, retransmitSender); handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); verify(retransmitSender).resend(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH); } @Theory public void shouldOnlyRetransmitOnNakWhenConfiguredTo(final BiConsumer<RetransmitHandlerTest, Integer> creator) { createTermBuffer(creator, 5); handler.onNak(TERM_ID, offsetOfFrame(0), ALIGNED_FRAME_LENGTH, TERM_BUFFER_LENGTH, retransmitSender); verifyZeroInteractions(retransmitSender); } private RetransmitHandler newZeroDelayRetransmitHandler() { return new RetransmitHandler(() -> currentTime, systemCounters, ZERO_DELAY_GENERATOR, LINGER_GENERATOR); } private void createTermBuffer(final BiConsumer<RetransmitHandlerTest, Integer> creator, final int num) { IntStream.range(0, num).forEach((i) -> creator.accept(this, i)); } private static int offsetOfFrame(final int index) { return index * ALIGNED_FRAME_LENGTH; } private void addSentDataFrame() { rcvBuffer.putBytes(0, DATA); termAppender.appendUnfragmentedMessage(headerWriter, rcvBuffer, 0, DATA.length, RESERVED_VALUE_SUPPLIER); } private void addReceivedDataFrame(final int msgNum) { dataHeader.wrap(rcvBuffer); dataHeader.termId(TERM_ID) .streamId(STREAM_ID) .sessionId(SESSION_ID) .termOffset(offsetOfFrame(msgNum)) .frameLength(MESSAGE_LENGTH) .headerType(HeaderFlyweight.HDR_TYPE_DATA) .flags(DataHeaderFlyweight.BEGIN_AND_END_FLAGS) .version(HeaderFlyweight.CURRENT_VERSION); rcvBuffer.putBytes(dataHeader.dataOffset(), DATA); TermRebuilder.insert(termBuffer, offsetOfFrame(msgNum), rcvBuffer, MESSAGE_LENGTH); } }