/*
* JBoss, Home of Professional Open Source
* Copyright 2005-2008, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.messaging.tests.integration.asyncio;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.CountDownLatch;
import org.jboss.messaging.core.asyncio.AIOCallback;
import org.jboss.messaging.core.asyncio.BufferCallback;
import org.jboss.messaging.core.asyncio.impl.AsynchronousFileImpl;
import org.jboss.messaging.core.logging.Logger;
/**
*
* you need to define -Djava.library.path=${project-root}/native/src/.libs when calling the JVM
* If you are running this test in eclipse you should do:
* I - Run->Open Run Dialog
* II - Find the class on the list (you will find it if you already tried running this testcase before)
* III - Add -Djava.library.path=<your project place>/native/src/.libs
* @author <a href="mailto:clebert.suconic@jboss.com">Clebert Suconic</a>.
* */
public class SingleThreadWriteNativeTest extends AIOTestBase
{
private static final Logger log = Logger.getLogger(SingleThreadWriteNativeTest.class);
private static CharsetEncoder UTF_8_ENCODER = Charset.forName("UTF-8").newEncoder();
byte commonBuffer[] = null;
private static void debug(final String msg)
{
log.debug(msg);
}
@Override
protected void setUp() throws Exception
{
super.setUp();
}
@Override
protected void tearDown() throws Exception
{
super.tearDown();
}
/**
* Opening and closing a file immediately can lead to races on the native layer,
* creating crash conditions.
* */
public void testOpenClose() throws Exception
{
final AsynchronousFileImpl controller = new AsynchronousFileImpl();
for (int i = 0; i < 1000; i++)
{
controller.open(FILE_NAME, 10000);
controller.close();
}
}
/**
* This test is validating if the AIO layer can open two different
* simultaneous files without loose any callbacks. This test made the native
* layer to crash at some point during development
*/
public void testTwoFiles() throws Exception
{
final AsynchronousFileImpl controller = new AsynchronousFileImpl();
final AsynchronousFileImpl controller2 = new AsynchronousFileImpl();
controller.open(FILE_NAME + ".1", 10000);
controller2.open(FILE_NAME + ".2", 10000);
int numberOfLines = 1000;
int size = 1024;
try
{
CountDownLatch latchDone = new CountDownLatch(numberOfLines);
CountDownLatch latchDone2 = new CountDownLatch(numberOfLines);
ByteBuffer block = controller.newBuffer(size);
encodeBufer(block);
preAlloc(controller, numberOfLines * size);
preAlloc(controller2, numberOfLines * size);
ArrayList<CountDownCallback> list = new ArrayList<CountDownCallback>();
ArrayList<CountDownCallback> list2 = new ArrayList<CountDownCallback>();
for (int i = 0; i < numberOfLines; i++)
{
list.add(new CountDownCallback(latchDone));
list2.add(new CountDownCallback(latchDone2));
}
long valueInitial = System.currentTimeMillis();
long lastTime = System.currentTimeMillis();
int counter = 0;
Iterator<CountDownCallback> iter2 = list2.iterator();
for (CountDownCallback tmp : list)
{
CountDownCallback tmp2 = iter2.next();
controller.write(counter * size, size, block, tmp);
controller.write(counter * size, size, block, tmp2);
if (++counter % 5000 == 0)
{
debug(5000 * 1000 / (System.currentTimeMillis() - lastTime) + " rec/sec (Async)");
lastTime = System.currentTimeMillis();
}
}
long timeTotal = System.currentTimeMillis() - valueInitial;
debug("Asynchronous time = " + timeTotal +
" for " +
numberOfLines +
" registers " +
" size each line = " +
size +
" Records/Sec=" +
numberOfLines *
1000 /
timeTotal +
" (Assynchronous)");
latchDone.await();
latchDone2.await();
timeTotal = System.currentTimeMillis() - valueInitial;
debug("After completions time = " + timeTotal +
" for " +
numberOfLines +
" registers " +
" size each line = " +
size +
" Records/Sec=" +
numberOfLines *
1000 /
timeTotal +
" (Assynchronous)");
for (CountDownCallback callback : list)
{
assertEquals(1, callback.timesDoneCalled.get());
assertTrue(callback.doneCalled);
assertFalse(callback.errorCalled);
}
for (CountDownCallback callback : list2)
{
assertEquals(1, callback.timesDoneCalled.get());
assertTrue(callback.doneCalled);
assertFalse(callback.errorCalled);
}
controller.close();
}
finally
{
try
{
controller.close();
}
catch (Exception ignored)
{
}
try
{
controller2.close();
}
catch (Exception ignored)
{
}
}
}
public void testAddBeyongSimultaneousLimit() throws Exception
{
asyncData(3000, 1024, 10);
}
public void testAddAsyncData() throws Exception
{
asyncData(10000, 1024, 30000);
}
public void testInvalidReads() throws Exception
{
class LocalCallback implements AIOCallback
{
private final CountDownLatch latch = new CountDownLatch(1);
volatile boolean error;
public void done()
{
latch.countDown();
}
public void onError(final int errorCode, final String errorMessage)
{
error = true;
latch.countDown();
}
}
AsynchronousFileImpl controller = new AsynchronousFileImpl();
try
{
final int SIZE = 512;
controller.open(FILE_NAME, 10);
controller.close();
controller = new AsynchronousFileImpl();
controller.open(FILE_NAME, 10);
controller.fill(0, 1, 512, (byte)'j');
ByteBuffer buffer = controller.newBuffer(SIZE);
buffer.clear();
for (int i = 0; i < SIZE; i++)
{
buffer.put((byte)(i % 100));
}
LocalCallback callbackLocal = new LocalCallback();
controller.write(0, 512, buffer, callbackLocal);
callbackLocal.latch.await();
ByteBuffer newBuffer = ByteBuffer.allocateDirect(50);
callbackLocal = new LocalCallback();
controller.read(0, 50, newBuffer, callbackLocal);
callbackLocal.latch.await();
// assertTrue(callbackLocal.error);
callbackLocal = new LocalCallback();
byte bytes[] = new byte[512];
try
{
newBuffer = ByteBuffer.wrap(bytes);
controller.read(0, 512, newBuffer, callbackLocal);
fail("An exception was supposed to be thrown");
}
catch (Exception ignored)
{
}
// newBuffer = ByteBuffer.allocateDirect(512);
newBuffer = controller.newBuffer(512);
callbackLocal = new LocalCallback();
controller.read(0, 512, newBuffer, callbackLocal);
callbackLocal.latch.await();
assertFalse(callbackLocal.error);
newBuffer.rewind();
byte[] bytesRead = new byte[SIZE];
newBuffer.get(bytesRead);
for (int i = 0; i < SIZE; i++)
{
assertEquals((byte)(i % 100), bytesRead[i]);
}
}
finally
{
try
{
controller.close();
}
catch (Throwable ignored)
{
}
}
}
public void testBufferCallbackUniqueBuffers() throws Exception
{
boolean closed = false;
final AsynchronousFileImpl controller = new AsynchronousFileImpl();
try
{
final int NUMBER_LINES = 1000;
final int SIZE = 512;
controller.open(FILE_NAME, 1000);
controller.fill(0, 1, NUMBER_LINES * SIZE, (byte)'j');
final ArrayList<ByteBuffer> buffers = new ArrayList<ByteBuffer>();
BufferCallback bufferCallback = new BufferCallback()
{
public void bufferDone(ByteBuffer buffer)
{
buffers.add(buffer);
}
};
controller.setBufferCallback(bufferCallback);
CountDownLatch latch = new CountDownLatch(NUMBER_LINES);
CountDownCallback aio = new CountDownCallback(latch);
for (int i = 0; i < NUMBER_LINES; i++)
{
ByteBuffer buffer = controller.newBuffer(SIZE);
buffer.rewind();
for (int j = 0; j < SIZE; j++)
{
buffer.put((byte)(j % Byte.MAX_VALUE));
}
controller.write(i * SIZE, SIZE, buffer, aio);
}
// The buffer callback is only called after the complete callback was
// called.
// Because of that a race could happen on the assertions to
// buffers.size what would invalidate the test
// We close the file and that would guarantee the buffer callback was
// called for all the elements
controller.close();
closed = true;
assertEquals(NUMBER_LINES, buffers.size());
// Make sure all the buffers are unique
ByteBuffer lineOne = null;
for (ByteBuffer bufferTmp : buffers)
{
if (lineOne == null)
{
lineOne = bufferTmp;
}
else
{
assertTrue(lineOne != bufferTmp);
}
}
buffers.clear();
}
finally
{
if (!closed)
{
controller.close();
}
}
}
public void testBufferCallbackAwaysSameBuffer() throws Exception
{
boolean closed = false;
final AsynchronousFileImpl controller = new AsynchronousFileImpl();
try
{
final int NUMBER_LINES = 1000;
final int SIZE = 512;
controller.open(FILE_NAME, 1000);
controller.fill(0, 1, NUMBER_LINES * SIZE, (byte)'j');
final ArrayList<ByteBuffer> buffers = new ArrayList<ByteBuffer>();
BufferCallback bufferCallback = new BufferCallback()
{
public void bufferDone(ByteBuffer buffer)
{
buffers.add(buffer);
}
};
controller.setBufferCallback(bufferCallback);
CountDownLatch latch = new CountDownLatch(NUMBER_LINES);
CountDownCallback aio = new CountDownCallback(latch);
ByteBuffer buffer = controller.newBuffer(SIZE);
buffer.rewind();
for (int j = 0; j < SIZE; j++)
{
buffer.put((byte)(j % Byte.MAX_VALUE));
}
for (int i = 0; i < NUMBER_LINES; i++)
{
controller.write(i * SIZE, SIZE, buffer, aio);
}
// The buffer callback is only called after the complete callback was
// called.
// Because of that a race could happen on the assertions to
// buffers.size what would invalidate the test
// We close the file and that would guarantee the buffer callback was
// called for all the elements
controller.close();
closed = true;
assertEquals(NUMBER_LINES, buffers.size());
// Make sure all the buffers are unique
ByteBuffer lineOne = null;
for (ByteBuffer bufferTmp : buffers)
{
if (lineOne == null)
{
lineOne = bufferTmp;
}
else
{
assertTrue(lineOne == bufferTmp);
}
}
buffers.clear();
}
finally
{
if (!closed)
{
controller.close();
}
}
}
public void testRead() throws Exception
{
final AsynchronousFileImpl controller = new AsynchronousFileImpl();
try
{
final int NUMBER_LINES = 5000;
final int SIZE = 1024;
controller.open(FILE_NAME, 1000);
controller.fill(0, 1, NUMBER_LINES * SIZE, (byte)'j');
{
CountDownLatch latch = new CountDownLatch(NUMBER_LINES);
CountDownCallback aio = new CountDownCallback(latch);
for (int i = 0; i < NUMBER_LINES; i++)
{
ByteBuffer buffer = ByteBuffer.allocateDirect(SIZE);
addString("Str value " + i + "\n", buffer);
for (int j = buffer.position(); j < buffer.capacity() - 1; j++)
{
buffer.put((byte)' ');
}
buffer.put((byte)'\n');
controller.write(i * SIZE, SIZE, buffer, aio);
}
latch.await();
assertFalse(aio.errorCalled);
assertEquals(NUMBER_LINES, aio.timesDoneCalled.get());
}
// If you call close you're supposed to wait events to finish before
// closing it
controller.close();
controller.open(FILE_NAME, 10);
ByteBuffer newBuffer = ByteBuffer.allocateDirect(SIZE);
for (int i = 0; i < NUMBER_LINES; i++)
{
newBuffer.clear();
addString("Str value " + i + "\n", newBuffer);
for (int j = newBuffer.position(); j < newBuffer.capacity() - 1; j++)
{
newBuffer.put((byte)' ');
}
newBuffer.put((byte)'\n');
CountDownLatch latch = new CountDownLatch(1);
CountDownCallback aio = new CountDownCallback(latch);
ByteBuffer buffer = ByteBuffer.allocateDirect(SIZE);
controller.read(i * SIZE, SIZE, buffer, aio);
latch.await();
assertFalse(aio.errorCalled);
assertTrue(aio.doneCalled);
byte bytesRead[] = new byte[SIZE];
byte bytesCompare[] = new byte[SIZE];
newBuffer.rewind();
newBuffer.get(bytesCompare);
buffer.rewind();
buffer.get(bytesRead);
for (int count = 0; count < SIZE; count++)
{
assertEquals("byte position " + count + " differs on line " + i, bytesCompare[count], bytesRead[count]);
}
assertTrue(buffer.equals(newBuffer));
}
}
finally
{
try
{
controller.close();
}
catch (Throwable ignored)
{
}
}
}
/**
* This test will call file.close() when there are still callbacks being processed.
* This could cause a crash or callbacks missing and this test is validating both situations.
* The file is also read after being written to validate its correctness */
public void testConcurrentClose() throws Exception
{
final AsynchronousFileImpl controller = new AsynchronousFileImpl();
try
{
final int NUMBER_LINES = 1000;
CountDownLatch readLatch = new CountDownLatch(NUMBER_LINES);
final int SIZE = 1024;
controller.open(FILE_NAME, 10000);
controller.fill(0, 1, NUMBER_LINES * SIZE, (byte)'j');
for (int i = 0; i < NUMBER_LINES; i++)
{
ByteBuffer buffer = ByteBuffer.allocateDirect(SIZE);
buffer.clear();
addString("Str value " + i + "\n", buffer);
for (int j = buffer.position(); j < buffer.capacity() - 1; j++)
{
buffer.put((byte)' ');
}
buffer.put((byte)'\n');
CountDownCallback aio = new CountDownCallback(readLatch);
controller.write(i * SIZE, SIZE, buffer, aio);
}
// If you call close you're supposed to wait events to finish before
// closing it
controller.close();
assertEquals(0, readLatch.getCount());
readLatch.await();
controller.open(FILE_NAME, 10);
ByteBuffer newBuffer = ByteBuffer.allocateDirect(SIZE);
ByteBuffer buffer = ByteBuffer.allocateDirect(SIZE);
for (int i = 0; i < NUMBER_LINES; i++)
{
newBuffer.clear();
addString("Str value " + i + "\n", newBuffer);
for (int j = newBuffer.position(); j < newBuffer.capacity() - 1; j++)
{
newBuffer.put((byte)' ');
}
newBuffer.put((byte)'\n');
CountDownLatch latch = new CountDownLatch(1);
CountDownCallback aio = new CountDownCallback(latch);
controller.read(i * SIZE, SIZE, buffer, aio);
latch.await();
assertFalse(aio.errorCalled);
assertTrue(aio.doneCalled);
byte bytesRead[] = new byte[SIZE];
byte bytesCompare[] = new byte[SIZE];
newBuffer.rewind();
newBuffer.get(bytesCompare);
buffer.rewind();
buffer.get(bytesRead);
for (int count = 0; count < SIZE; count++)
{
assertEquals("byte position " + count + " differs on line " + i, bytesCompare[count], bytesRead[count]);
}
assertTrue(buffer.equals(newBuffer));
}
}
finally
{
try
{
controller.close();
}
catch (Throwable ignored)
{
}
}
}
private void asyncData(final int numberOfLines, final int size, final int aioLimit) throws Exception
{
final AsynchronousFileImpl controller = new AsynchronousFileImpl();
controller.open(FILE_NAME, aioLimit);
try
{
CountDownLatch latchDone = new CountDownLatch(numberOfLines);
ByteBuffer block = controller.newBuffer(size);
encodeBufer(block);
preAlloc(controller, numberOfLines * size);
ArrayList<CountDownCallback> list = new ArrayList<CountDownCallback>();
for (int i = 0; i < numberOfLines; i++)
{
list.add(new CountDownCallback(latchDone));
}
long valueInitial = System.currentTimeMillis();
long lastTime = System.currentTimeMillis();
int counter = 0;
for (CountDownCallback tmp : list)
{
controller.write(counter * size, size, block, tmp);
if (++counter % 20000 == 0)
{
debug(20000 * 1000 / (System.currentTimeMillis() - lastTime) + " rec/sec (Async)");
lastTime = System.currentTimeMillis();
}
}
latchDone.await();
long timeTotal = System.currentTimeMillis() - valueInitial;
debug("After completions time = " + timeTotal +
" for " +
numberOfLines +
" registers " +
" size each line = " +
size +
", Records/Sec=" +
numberOfLines *
1000 /
timeTotal +
" (Assynchronous)");
for (CountDownCallback tmp : list)
{
assertEquals(1, tmp.timesDoneCalled.get());
assertTrue(tmp.doneCalled);
assertFalse(tmp.errorCalled);
}
controller.close();
}
finally
{
try
{
controller.close();
}
catch (Exception ignored)
{
}
}
}
public void testDirectSynchronous() throws Exception
{
try
{
final int NUMBER_LINES = 3000;
final int SIZE = 1024;
final AsynchronousFileImpl controller = new AsynchronousFileImpl();
controller.open(FILE_NAME, 2000);
ByteBuffer block = ByteBuffer.allocateDirect(SIZE);
encodeBufer(block);
preAlloc(controller, NUMBER_LINES * SIZE);
long startTime = System.currentTimeMillis();
for (int i = 0; i < NUMBER_LINES; i++)
{
CountDownLatch latchDone = new CountDownLatch(1);
CountDownCallback aioBlock = new CountDownCallback(latchDone);
controller.write(i * 512, 512, block, aioBlock);
latchDone.await();
assertTrue(aioBlock.doneCalled);
assertFalse(aioBlock.errorCalled);
}
long timeTotal = System.currentTimeMillis() - startTime;
debug("time = " + timeTotal +
" for " +
NUMBER_LINES +
" registers " +
" size each line = " +
SIZE +
" Records/Sec=" +
NUMBER_LINES *
1000 /
timeTotal +
" Synchronous");
controller.close();
}
catch (Exception e)
{
throw e;
}
}
public void testInvalidWrite() throws Exception
{
final AsynchronousFileImpl controller = new AsynchronousFileImpl();
controller.open(FILE_NAME, 2000);
try
{
final int SIZE = 512;
ByteBuffer block = controller.newBuffer(SIZE);
encodeBufer(block);
preAlloc(controller, 10 * 512);
CountDownLatch latchDone = new CountDownLatch(1);
CountDownCallback aioBlock = new CountDownCallback(latchDone);
controller.write(11, 512, block, aioBlock);
latchDone.await();
assertTrue(aioBlock.errorCalled);
assertFalse(aioBlock.doneCalled);
}
catch (Exception e)
{
throw e;
}
finally
{
controller.close();
}
}
public void testInvalidAlloc() throws Exception
{
AsynchronousFileImpl controller = new AsynchronousFileImpl();
try
{
ByteBuffer buffer = controller.newBuffer(300);
fail("Exception expected");
}
catch (Exception ignored)
{
}
}
public void testSize() throws Exception
{
final AsynchronousFileImpl controller = new AsynchronousFileImpl();
final int NUMBER_LINES = 10;
final int SIZE = 1024;
controller.open(FILE_NAME, 1);
controller.fill(0, 1, NUMBER_LINES * SIZE, (byte)'j');
assertEquals(NUMBER_LINES * SIZE, controller.size());
controller.close();
}
private void addString(final String str, final ByteBuffer buffer)
{
CharBuffer charBuffer = CharBuffer.wrap(str);
UTF_8_ENCODER.encode(charBuffer, buffer, true);
}
}