/* * 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 io.aeron.driver.MediaDriver; import io.aeron.logbuffer.FragmentHandler; import io.aeron.logbuffer.Header; import org.agrona.DirectBuffer; import org.agrona.concurrent.*; import org.junit.Test; import java.nio.ByteBuffer; import static org.junit.Assert.fail; public class MemoryOrderingTest { private static final String CHANNEL = "aeron:udp?endpoint=localhost:54325"; private static final int STREAM_ID = 1; private static final int FRAGMENT_COUNT_LIMIT = 10; private static final int MESSAGE_LENGTH = 2000; private static final int TERM_BUFFER_LENGTH = 1024 * 64; private static final int NUM_MESSAGES = 15_000; private static final int BURST_LENGTH = 7; private static final int INTER_BURST_DURATION_NS = 100_000; private static volatile String failedMessage = null; @Test(timeout = 10000) public void shouldReceiveMessagesInOrderWithFirstLongWordIntact() throws Exception { final UnsafeBuffer srcBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(MESSAGE_LENGTH)); srcBuffer.setMemory(0, MESSAGE_LENGTH, (byte)7); final MediaDriver.Context ctx = new MediaDriver.Context() .publicationTermBufferLength(TERM_BUFFER_LENGTH); try (MediaDriver ignore = MediaDriver.launch(ctx); Aeron aeron = Aeron.connect(); Publication publication = aeron.addPublication(CHANNEL, STREAM_ID); Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID)) { final BusySpinIdleStrategy idleStrategy = new BusySpinIdleStrategy(); final Thread subscriberThread = new Thread(new Subscriber(subscription)); subscriberThread.start(); for (int i = 0; i < NUM_MESSAGES; i++) { if (null != failedMessage) { fail(failedMessage); } srcBuffer.putLong(0, i); while (publication.offer(srcBuffer) < 0L) { if (null != failedMessage) { fail(failedMessage); } idleStrategy.idle(); } if (i % BURST_LENGTH == 0) { final long timeout = System.nanoTime() + INTER_BURST_DURATION_NS; long now; do { now = System.nanoTime(); } while (now < timeout); } } subscriberThread.join(); } finally { ctx.deleteAeronDirectory(); } } @Test(timeout = 10000) public void shouldReceiveMessagesInOrderWithFirstLongWordIntactFromExclusivePublication() throws Exception { final UnsafeBuffer srcBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(MESSAGE_LENGTH)); srcBuffer.setMemory(0, MESSAGE_LENGTH, (byte)7); final MediaDriver.Context ctx = new MediaDriver.Context() .publicationTermBufferLength(TERM_BUFFER_LENGTH); try (MediaDriver ignore = MediaDriver.launch(ctx); Aeron aeron = Aeron.connect(); ExclusivePublication publication = aeron.addExclusivePublication(CHANNEL, STREAM_ID); Subscription subscription = aeron.addSubscription(CHANNEL, STREAM_ID)) { final BusySpinIdleStrategy idleStrategy = new BusySpinIdleStrategy(); final Thread subscriberThread = new Thread(new Subscriber(subscription)); subscriberThread.start(); for (int i = 0; i < NUM_MESSAGES; i++) { if (null != failedMessage) { fail(failedMessage); } srcBuffer.putLong(0, i); while (publication.offer(srcBuffer) < 0L) { if (null != failedMessage) { fail(failedMessage); } idleStrategy.idle(); } if (i % BURST_LENGTH == 0) { final long timeout = System.nanoTime() + INTER_BURST_DURATION_NS; long now; do { now = System.nanoTime(); } while (now < timeout); } } subscriberThread.join(); } finally { ctx.deleteAeronDirectory(); } } static class Subscriber implements Runnable, FragmentHandler { private final Subscription subscription; long previousValue = -1; int messageNum = 0; Subscriber(final Subscription subscription) { this.subscription = subscription; } public void run() { final BusySpinIdleStrategy idleStrategy = new BusySpinIdleStrategy(); while (messageNum < NUM_MESSAGES && null == failedMessage) { idleStrategy.idle(subscription.poll(this, FRAGMENT_COUNT_LIMIT)); } } public void onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header) { final long messageValue = buffer.getLong(offset); final long expectedValue = previousValue + 1; if (messageValue != expectedValue) { final long messageValueSecondRead = buffer.getLong(offset); final String msg = "Issue at message number transition: " + previousValue + " -> " + messageValue; System.out.println(msg + "\n" + "offset: " + offset + "\n" + "length: " + length + "\n" + "expected bytes: " + byteString(expectedValue) + "\n" + "received bytes: " + byteString(messageValue) + "\n" + "expected bits: " + Long.toBinaryString(expectedValue) + "\n" + "received bits: " + Long.toBinaryString(messageValue) + "\n" + "messageValue on second read: " + messageValueSecondRead + "\n" + "messageValue on third read: " + buffer.getLong(offset)); failedMessage = msg; } previousValue = messageValue; messageNum++; } private String byteString(final long value) { return String.format("%x %x %x %x %x %x %x %x", (byte)(value >>> 56), (byte)(value >>> 48), (byte)(value >>> 40), (byte)(value >>> 32), (byte)(value >>> 24), (byte)(value >>> 18), (byte)(value >>> 8), (byte)value); } } }