/*
* Copyright 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 org.agrona.agent;
import static java.nio.ByteOrder.BIG_ENDIAN;
import static org.agrona.BitUtil.*;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.nio.ByteBuffer;
import java.util.function.IntConsumer;
import org.agrona.DirectBuffer;
import org.agrona.ExpandableArrayBuffer;
import org.agrona.MutableDirectBuffer;
import org.agrona.concurrent.AtomicBuffer;
import org.agrona.concurrent.UnsafeBuffer;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import net.bytebuddy.agent.ByteBuddyAgent;
import sun.misc.Unsafe;
public class BufferAlignmentAgentTest
{
private static final String TEST_STRING = "BufferAlignmentTest";
//on 32-bits JVMs, array content is not 8-byte aligned => need to add 4 bytes offset
private static final int HEAP_BUFFER_ALIGNMENT_OFFSET = Unsafe.ARRAY_BYTE_BASE_OFFSET % 8;
@BeforeClass
public static void installAgent()
{
BufferAlignmentAgent.agentmain("", ByteBuddyAgent.install());
}
@AfterClass
public static void removeAgent()
{
BufferAlignmentAgent.removeTransformer();
}
@Test
public void testUnsafeBufferFromByteArray()
{
testUnsafeBuffer(new UnsafeBuffer(new byte[256]), HEAP_BUFFER_ALIGNMENT_OFFSET);
}
@Test
public void testUnsafeBufferFromByteArrayWithOffset()
{
final UnsafeBuffer buffer = new UnsafeBuffer(new byte[256], 1, 128);
assertTrue(buffer.addressOffset() % 4 != 0);
testUnsafeBuffer(buffer, 7 + HEAP_BUFFER_ALIGNMENT_OFFSET);
}
@Test
public void testUnsafeBufferFromHeapByteBuffer()
{
testUnsafeBuffer(new UnsafeBuffer(ByteBuffer.allocate(256)), HEAP_BUFFER_ALIGNMENT_OFFSET);
}
@Test
public void testUnsafeBufferFromSlicedHeapByteBuffer()
{
final ByteBuffer nioBuffer = ByteBuffer.allocateDirect(256);
nioBuffer.position(1);
final UnsafeBuffer buffer = new UnsafeBuffer(nioBuffer.slice());
assertTrue(buffer.addressOffset() % 4 != 0);
testUnsafeBuffer(buffer, 7 + HEAP_BUFFER_ALIGNMENT_OFFSET);
}
@Test
public void testUnsafeBufferFromSlicedHeapByteBufferWithOffset()
{
final ByteBuffer nioBuffer = ByteBuffer.allocate(256);
nioBuffer.position(1);
final UnsafeBuffer buffer = new UnsafeBuffer(nioBuffer.slice(), 2, 128);
assertTrue(buffer.addressOffset() % 4 != 0);
testUnsafeBuffer(buffer, 5 + HEAP_BUFFER_ALIGNMENT_OFFSET);
}
@Test
public void testUnsafeBufferFromDirectByteBuffer()
{
testUnsafeBuffer(new UnsafeBuffer(ByteBuffer.allocateDirect(256)), 0);
}
@Test
public void testUnsafeBufferFromSlicedDirectByteBuffer()
{
final ByteBuffer nioBuffer = ByteBuffer.allocateDirect(256);
nioBuffer.position(1);
final UnsafeBuffer buffer = new UnsafeBuffer(nioBuffer.slice());
assertTrue(buffer.addressOffset() % 4 != 0);
testUnsafeBuffer(buffer, 7);
}
@Test
public void testUnsafeBufferFromSlicedDirectByteBufferWithOffset()
{
final ByteBuffer nioBuffer = ByteBuffer.allocateDirect(256);
nioBuffer.position(1);
final UnsafeBuffer buffer = new UnsafeBuffer(nioBuffer.slice(), 2, 128);
assertTrue(buffer.addressOffset() % 4 != 0);
testUnsafeBuffer(buffer, 5);
}
@Test
public void testExpandableBuffer()
{
final ExpandableArrayBuffer buffer = new ExpandableArrayBuffer(256);
testAlignedReadMethods(buffer, HEAP_BUFFER_ALIGNMENT_OFFSET);
testUnAlignedReadMethods(buffer, HEAP_BUFFER_ALIGNMENT_OFFSET);
testAlignedWriteMethods(buffer, HEAP_BUFFER_ALIGNMENT_OFFSET);
testUnAlignedWriteMethods(buffer, HEAP_BUFFER_ALIGNMENT_OFFSET);
}
private void testUnsafeBuffer(final UnsafeBuffer buffer, final int offset)
{
testAlignedReadMethods(buffer, offset);
testUnAlignedReadMethods(buffer, offset);
testAlignedWriteMethods(buffer, offset);
testUnAlignedWriteMethods(buffer, offset);
testAlignedAtomicMethods(buffer, offset);
testUnAlignedAtomicMethods(buffer, offset);
}
private void testAlignedReadMethods(final DirectBuffer buffer, final int offset)
{
buffer.getLong(offset + SIZE_OF_LONG);
buffer.getLong(offset + SIZE_OF_LONG, BIG_ENDIAN);
buffer.getDouble(offset + SIZE_OF_DOUBLE);
buffer.getDouble(offset + SIZE_OF_DOUBLE, BIG_ENDIAN);
buffer.getInt(offset + SIZE_OF_INT);
buffer.getInt(offset + SIZE_OF_INT, BIG_ENDIAN);
buffer.getFloat(offset + SIZE_OF_FLOAT);
buffer.getFloat(offset + SIZE_OF_FLOAT, BIG_ENDIAN);
buffer.getShort(offset + SIZE_OF_SHORT);
buffer.getShort(offset + SIZE_OF_SHORT, BIG_ENDIAN);
buffer.getChar(offset + SIZE_OF_CHAR);
buffer.getChar(offset + SIZE_OF_CHAR, BIG_ENDIAN);
buffer.getByte(offset + SIZE_OF_BYTE);
buffer.getByte(offset + SIZE_OF_BYTE);
buffer.getStringUtf8(offset + SIZE_OF_INT);
buffer.getStringUtf8(offset + SIZE_OF_INT, BIG_ENDIAN);
buffer.getStringAscii(offset + SIZE_OF_INT);
buffer.getStringAscii(offset + SIZE_OF_INT, BIG_ENDIAN);
// string size is not read for these method => no need for 4-bytes
// alignment
buffer.getStringUtf8(offset + SIZE_OF_BYTE, 7);
buffer.getStringWithoutLengthUtf8(offset + SIZE_OF_BYTE, 7);
buffer.getStringAscii(offset + SIZE_OF_BYTE, 7);
buffer.getStringWithoutLengthAscii(offset + SIZE_OF_BYTE, 7);
}
private void testUnAlignedReadMethods(final DirectBuffer buffer, final int offset)
{
buffer.getLong(offset); // assert that buffer[offset] is 8-bytes aligned
assertUnaligned(offset + SIZE_OF_INT, buffer::getLong);
assertUnaligned(offset + SIZE_OF_INT, (i) -> buffer.getLong(i, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_FLOAT, buffer::getDouble);
assertUnaligned(offset + SIZE_OF_FLOAT, (i) -> buffer.getDouble(i, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_SHORT, buffer::getInt);
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.getInt(i, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_SHORT, buffer::getFloat);
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.getFloat(i, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_BYTE, buffer::getShort);
assertUnaligned(offset + SIZE_OF_BYTE, (i) -> buffer.getShort(i, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_BYTE, buffer::getChar);
assertUnaligned(offset + SIZE_OF_BYTE, (i) -> buffer.getChar(i, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_SHORT, buffer::getStringUtf8);
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.getStringUtf8(i, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_SHORT, buffer::getStringAscii);
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.getStringAscii(i, BIG_ENDIAN));
}
private void testAlignedWriteMethods(final MutableDirectBuffer buffer, final int offset)
{
buffer.putLong(offset + SIZE_OF_LONG, Long.MAX_VALUE);
buffer.putLong(offset + SIZE_OF_LONG, Long.MAX_VALUE, BIG_ENDIAN);
buffer.putDouble(offset + SIZE_OF_DOUBLE, Double.MAX_VALUE);
buffer.putDouble(offset + SIZE_OF_DOUBLE, Double.MAX_VALUE, BIG_ENDIAN);
buffer.putInt(offset + SIZE_OF_INT, Integer.MAX_VALUE);
buffer.putInt(offset + SIZE_OF_INT, Integer.MAX_VALUE, BIG_ENDIAN);
buffer.putFloat(offset + SIZE_OF_FLOAT, Float.MAX_VALUE);
buffer.putFloat(offset + SIZE_OF_FLOAT, Float.MAX_VALUE, BIG_ENDIAN);
buffer.putShort(offset + SIZE_OF_SHORT, Short.MAX_VALUE);
buffer.putShort(offset + SIZE_OF_SHORT, Short.MAX_VALUE, BIG_ENDIAN);
buffer.putChar(offset + SIZE_OF_CHAR, Character.MAX_VALUE);
buffer.putChar(offset + SIZE_OF_CHAR, Character.MAX_VALUE, BIG_ENDIAN);
buffer.putByte(offset + SIZE_OF_BYTE, Byte.MAX_VALUE);
buffer.putByte(offset + SIZE_OF_BYTE, Byte.MAX_VALUE);
buffer.putStringUtf8(offset + SIZE_OF_INT, TEST_STRING);
buffer.putStringUtf8(offset + SIZE_OF_INT, TEST_STRING, BIG_ENDIAN);
buffer.putStringUtf8(offset + SIZE_OF_INT, TEST_STRING, Integer.MAX_VALUE);
buffer.putStringUtf8(offset + SIZE_OF_INT, TEST_STRING, BIG_ENDIAN, Integer.MAX_VALUE);
buffer.putStringAscii(offset + SIZE_OF_INT, TEST_STRING);
buffer.putStringAscii(offset + SIZE_OF_INT, TEST_STRING, BIG_ENDIAN);
// string size is not read for these method => no need for 4-bytes
// alignment
buffer.putStringWithoutLengthUtf8(offset + SIZE_OF_BYTE, TEST_STRING);
buffer.putStringWithoutLengthAscii(offset + SIZE_OF_BYTE, TEST_STRING);
}
private void testUnAlignedWriteMethods(final MutableDirectBuffer buffer, final int offset)
{
buffer.putLong(offset, Long.MAX_VALUE); // assert that buffer[offset] is
// 8-bytes aligned
assertUnaligned(offset + SIZE_OF_INT, (i) -> buffer.putLong(i, Long.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_INT, (i) -> buffer.putLong(i, Long.MAX_VALUE, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_FLOAT, (i) -> buffer.putDouble(i, Double.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_FLOAT, (i) -> buffer.putDouble(i, Double.MAX_VALUE, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.putInt(i, Integer.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.putInt(i, Integer.MAX_VALUE, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.putFloat(i, Float.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.putFloat(i, Float.MAX_VALUE, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_BYTE, (i) -> buffer.putShort(i, Short.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_BYTE, (i) -> buffer.putShort(i, Short.MAX_VALUE, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_BYTE, (i) -> buffer.putChar(i, Character.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_BYTE, (i) -> buffer.putChar(i, Character.MAX_VALUE, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.putStringAscii(i, TEST_STRING));
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.putStringAscii(i, TEST_STRING, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.putStringUtf8(i, TEST_STRING));
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.putStringUtf8(i, TEST_STRING, BIG_ENDIAN));
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.putStringUtf8(i, TEST_STRING, Integer.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_SHORT,
(i) -> buffer.putStringUtf8(i, TEST_STRING, BIG_ENDIAN, Integer.MAX_VALUE));
}
private void testAlignedAtomicMethods(final AtomicBuffer buffer, final int offset)
{
buffer.getLongVolatile(offset + SIZE_OF_LONG);
buffer.putLongVolatile(offset + SIZE_OF_LONG, Long.MAX_VALUE);
buffer.compareAndSetLong(offset + SIZE_OF_LONG, Long.MAX_VALUE, Long.MAX_VALUE);
buffer.getAndAddLong(offset + SIZE_OF_LONG, Long.MAX_VALUE);
buffer.getAndSetLong(offset + SIZE_OF_LONG, Long.MAX_VALUE);
buffer.putLongOrdered(offset + SIZE_OF_LONG, Long.MAX_VALUE);
buffer.addLongOrdered(offset + SIZE_OF_LONG, Long.MAX_VALUE);
buffer.getIntVolatile(offset + SIZE_OF_INT);
buffer.putIntVolatile(offset + SIZE_OF_INT, Integer.MAX_VALUE);
buffer.compareAndSetInt(offset + SIZE_OF_INT, Integer.MAX_VALUE, Integer.MAX_VALUE);
buffer.getAndAddInt(offset + SIZE_OF_INT, Integer.MAX_VALUE);
buffer.getAndSetInt(offset + SIZE_OF_INT, Integer.MAX_VALUE);
buffer.putIntOrdered(offset + SIZE_OF_INT, Integer.MAX_VALUE);
buffer.addIntOrdered(offset + SIZE_OF_INT, Integer.MAX_VALUE);
buffer.getShortVolatile(offset + SIZE_OF_SHORT);
buffer.putShortVolatile(offset + SIZE_OF_SHORT, Short.MAX_VALUE);
buffer.getCharVolatile(offset + SIZE_OF_CHAR);
buffer.putCharVolatile(offset + SIZE_OF_CHAR, Character.MAX_VALUE);
buffer.getByteVolatile(offset + SIZE_OF_BYTE);
buffer.putByteVolatile(offset + SIZE_OF_BYTE, Byte.MAX_VALUE);
}
private void testUnAlignedAtomicMethods(final AtomicBuffer buffer, final int offset)
{
buffer.getLongVolatile(offset); // assert that buffer[offset] is 8-bytes
// aligned
assertUnaligned(offset + SIZE_OF_INT, buffer::getLongVolatile);
assertUnaligned(offset + SIZE_OF_INT, (i) -> buffer.putLongVolatile(i, Long.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_INT, (i) -> buffer.compareAndSetLong(i, Long.MAX_VALUE, Long.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_INT, (i) -> buffer.getAndAddLong(i, Long.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_INT, (i) -> buffer.getAndSetLong(i, Long.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_INT, (i) -> buffer.putLongOrdered(i, Long.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_INT, (i) -> buffer.addLongOrdered(i, Long.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_SHORT, buffer::getIntVolatile);
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.putIntVolatile(i, Integer.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_SHORT,
(i) -> buffer.compareAndSetInt(i, Integer.MAX_VALUE, Integer.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.getAndAddInt(i, Integer.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.getAndSetInt(i, Integer.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.putIntOrdered(i, Integer.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_SHORT, (i) -> buffer.addIntOrdered(i, Integer.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_BYTE, buffer::getShortVolatile);
assertUnaligned(offset + SIZE_OF_BYTE, (i) -> buffer.putShortVolatile(i, Short.MAX_VALUE));
assertUnaligned(offset + SIZE_OF_BYTE, buffer::getCharVolatile);
assertUnaligned(offset + SIZE_OF_BYTE, (i) -> buffer.putCharVolatile(i, Character.MAX_VALUE));
}
private void assertUnaligned(final int index, final IntConsumer methodUnderTest)
{
try
{
methodUnderTest.accept(index);
}
catch (final BufferAlignmentException ignore)
{
return;
}
fail("Should have thrown BufferAlignmentException");
}
}