/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.tests.unit.core.asyncio;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.activemq.artemis.core.io.IOCallback;
import org.apache.activemq.artemis.core.io.aio.AIOSequentialFile;
import org.apache.activemq.artemis.core.io.aio.AIOSequentialFileFactory;
import org.apache.activemq.artemis.jlibaio.LibaioContext;
import org.apache.activemq.artemis.tests.unit.UnitTestLogger;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.ActiveMQThreadFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* 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
*/
public class MultiThreadAsynchronousFileTest extends AIOTestBase {
@BeforeClass
public static void hasAIO() {
org.junit.Assume.assumeTrue("Test case needs AIO to run", AIOSequentialFileFactory.isSupported());
}
AtomicInteger position = new AtomicInteger(0);
static final int SIZE = 1024;
static final int NUMBER_OF_THREADS = 1;
static final int NUMBER_OF_LINES = 1000;
ExecutorService executor;
ExecutorService pollerExecutor;
private static void debug(final String msg) {
UnitTestLogger.LOGGER.info(msg);
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
pollerExecutor = Executors.newCachedThreadPool(new ActiveMQThreadFactory("ActiveMQ-AIO-poller-pool" + System.identityHashCode(this), false, this.getClass().getClassLoader()));
executor = Executors.newSingleThreadExecutor(ActiveMQThreadFactory.defaultThreadFactory());
}
@Override
@After
public void tearDown() throws Exception {
executor.shutdown();
pollerExecutor.shutdown();
super.tearDown();
}
@Test
public void testMultipleASynchronousWrites() throws Throwable {
executeTest(false);
}
@Test
public void testMultipleSynchronousWrites() throws Throwable {
executeTest(true);
}
private void executeTest(final boolean sync) throws Throwable {
MultiThreadAsynchronousFileTest.debug(sync ? "Sync test:" : "Async test");
AIOSequentialFileFactory factory = new AIOSequentialFileFactory(getTestDirfile(), 21000);
factory.start();
factory.disableBufferReuse();
AIOSequentialFile file = (AIOSequentialFile) factory.createSequentialFile(fileName);
file.open();
try {
MultiThreadAsynchronousFileTest.debug("Preallocating file");
file.fill(MultiThreadAsynchronousFileTest.NUMBER_OF_THREADS *
MultiThreadAsynchronousFileTest.SIZE * MultiThreadAsynchronousFileTest.NUMBER_OF_LINES);
MultiThreadAsynchronousFileTest.debug("Done Preallocating file");
CountDownLatch latchStart = new CountDownLatch(MultiThreadAsynchronousFileTest.NUMBER_OF_THREADS + 1);
ArrayList<ThreadProducer> list = new ArrayList<>(MultiThreadAsynchronousFileTest.NUMBER_OF_THREADS);
for (int i = 0; i < MultiThreadAsynchronousFileTest.NUMBER_OF_THREADS; i++) {
ThreadProducer producer = new ThreadProducer("Thread " + i, latchStart, file, sync);
list.add(producer);
producer.start();
}
latchStart.countDown();
ActiveMQTestBase.waitForLatch(latchStart);
long startTime = System.currentTimeMillis();
for (ThreadProducer producer : list) {
producer.join();
if (producer.failed != null) {
throw producer.failed;
}
}
long endTime = System.currentTimeMillis();
MultiThreadAsynchronousFileTest.debug((sync ? "Sync result:" : "Async result:") + " Records/Second = " +
MultiThreadAsynchronousFileTest.NUMBER_OF_THREADS *
MultiThreadAsynchronousFileTest.NUMBER_OF_LINES *
1000 / (endTime - startTime) +
" total time = " +
(endTime - startTime) +
" total number of records = " +
MultiThreadAsynchronousFileTest.NUMBER_OF_THREADS * MultiThreadAsynchronousFileTest.NUMBER_OF_LINES);
} finally {
file.close();
factory.stop();
}
}
private int getNewPosition() {
return position.addAndGet(1);
}
class ThreadProducer extends Thread {
Throwable failed = null;
CountDownLatch latchStart;
boolean sync;
AIOSequentialFile libaio;
ThreadProducer(final String name,
final CountDownLatch latchStart,
final AIOSequentialFile libaio,
final boolean sync) {
super(name);
this.latchStart = latchStart;
this.libaio = libaio;
this.sync = sync;
}
@Override
public void run() {
super.run();
ByteBuffer buffer = null;
buffer = LibaioContext.newAlignedBuffer(MultiThreadAsynchronousFileTest.SIZE, 512);
try {
// I'm always reusing the same buffer, as I don't want any noise from
// malloc on the measurement
// Encoding buffer
MultiThreadAsynchronousFileTest.addString("Thread name=" + Thread.currentThread().getName() + ";" + "\n", buffer);
for (int local = buffer.position(); local < buffer.capacity() - 1; local++) {
buffer.put((byte) ' ');
}
buffer.put((byte) '\n');
latchStart.countDown();
waitForLatch(latchStart);
CountDownLatch latchFinishThread = null;
if (!sync) {
latchFinishThread = new CountDownLatch(MultiThreadAsynchronousFileTest.NUMBER_OF_LINES);
}
LinkedList<CountDownCallback> list = new LinkedList<>();
for (int i = 0; i < MultiThreadAsynchronousFileTest.NUMBER_OF_LINES; i++) {
if (sync) {
latchFinishThread = new CountDownLatch(1);
}
CountDownCallback callback = new CountDownCallback(latchFinishThread, null, null, 0);
if (!sync) {
list.add(callback);
}
addData(libaio, buffer, callback);
if (sync) {
waitForLatch(latchFinishThread);
assertTrue(callback.doneCalled);
assertFalse(callback.errorCalled != 0);
}
}
if (!sync) {
waitForLatch(latchFinishThread);
}
for (CountDownCallback callback : list) {
assertTrue(callback.doneCalled);
assertFalse(callback.errorCalled != 0);
}
for (CountDownCallback callback : list) {
assertTrue(callback.doneCalled);
assertFalse(callback.errorCalled != 0);
}
} catch (Throwable e) {
e.printStackTrace();
failed = e;
} finally {
synchronized (MultiThreadAsynchronousFileTest.class) {
LibaioContext.freeBuffer(buffer);
}
}
}
}
private static void addString(final String str, final ByteBuffer buffer) {
byte[] bytes = str.getBytes();
buffer.put(bytes);
}
private void addData(final AIOSequentialFile aio,
final ByteBuffer buffer,
final IOCallback callback) throws Exception {
aio.writeDirect(buffer, true, callback);
}
}