/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library 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 library 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.
*/
package com.liferay.portal.kernel.nio.intraband.blocking;
import static java.lang.Thread.sleep;
import com.liferay.portal.kernel.nio.intraband.BaseIntraband;
import com.liferay.portal.kernel.nio.intraband.ChannelContext;
import com.liferay.portal.kernel.nio.intraband.CompletionHandler;
import com.liferay.portal.kernel.nio.intraband.Datagram;
import com.liferay.portal.kernel.nio.intraband.DatagramHelper;
import com.liferay.portal.kernel.nio.intraband.IntrabandTestUtil;
import com.liferay.portal.kernel.nio.intraband.RecordCompletionHandler;
import com.liferay.portal.kernel.nio.intraband.blocking.ExecutorIntraband.ReadingCallable;
import com.liferay.portal.kernel.nio.intraband.blocking.ExecutorIntraband.WritingCallable;
import com.liferay.portal.kernel.nio.intraband.test.MockRegistrationReference;
import com.liferay.portal.kernel.test.CaptureHandler;
import com.liferay.portal.kernel.test.JDKLoggerTestUtil;
import com.liferay.portal.kernel.test.SyncThrowableThread;
import com.liferay.portal.kernel.test.rule.CodeCoverageAssertor;
import com.liferay.portal.kernel.util.Time;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.Pipe;
import java.nio.channels.Pipe.SinkChannel;
import java.nio.channels.Pipe.SourceChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
/**
* @author Shuyang Zhou
*/
public class ExecutorIntrabandTest {
@ClassRule
public static final CodeCoverageAssertor codeCoverageAssertor =
new CodeCoverageAssertor() {
@Override
public void appendAssertClasses(List<Class<?>> assertClasses) {
assertClasses.add(FutureRegistrationReference.class);
}
};
@Before
public void setUp() {
_executorIntraband = new ExecutorIntraband(_DEFAULT_TIMEOUT);
}
@After
public void tearDown() throws Exception {
_executorIntraband.close();
}
@Test
public void testDoSendDatagram() {
Queue<Datagram> sendingQueue = new LinkedList<>();
FutureRegistrationReference futureRegistrationReference =
new FutureRegistrationReference(
_executorIntraband, new ChannelContext(sendingQueue), null,
null) {
@Override
public boolean isValid() {
return true;
}
};
Datagram datagram1 = Datagram.createRequestDatagram(_TYPE, _data);
_executorIntraband.sendDatagram(futureRegistrationReference, datagram1);
Datagram datagram2 = Datagram.createRequestDatagram(_TYPE, _data);
_executorIntraband.sendDatagram(futureRegistrationReference, datagram2);
Datagram datagram3 = Datagram.createRequestDatagram(_TYPE, _data);
_executorIntraband.sendDatagram(futureRegistrationReference, datagram3);
Assert.assertEquals(3, sendingQueue.size());
Assert.assertSame(datagram1, sendingQueue.poll());
Assert.assertSame(datagram2, sendingQueue.poll());
Assert.assertSame(datagram3, sendingQueue.poll());
}
@Test
public void testReadingCallable() throws Exception {
// Exit gracefully on close
Pipe pipe = Pipe.open();
final SourceChannel sourceChannel = pipe.source();
SinkChannel sinkChannel = pipe.sink();
try {
MockRegistrationReference mockRegistrationReference =
new MockRegistrationReference(_executorIntraband);
ChannelContext channelContext = new ChannelContext(
new LinkedList<Datagram>());
channelContext.setRegistrationReference(mockRegistrationReference);
ReadingCallable readingCallable =
_executorIntraband.new ReadingCallable(
sourceChannel, channelContext);
SyncThrowableThread<Void> syncThrowableThread =
new SyncThrowableThread<>(
new Callable<Void>() {
@Override
public Void call() throws Exception {
sleep(100);
sourceChannel.close();
return null;
}
});
syncThrowableThread.start();
readingCallable.openLatch();
Void result = readingCallable.call();
syncThrowableThread.sync();
Assert.assertNull(result);
Assert.assertFalse(mockRegistrationReference.isValid());
}
finally {
sourceChannel.close();
sinkChannel.close();
}
}
@Test
public void testRegisterChannelDuplexWithErrors() throws Exception {
// Channel is null
try {
_executorIntraband.registerChannel(null);
Assert.fail();
}
catch (NullPointerException npe) {
Assert.assertEquals("Channel is null", npe.getMessage());
}
// Channel is not of type GatheringByteChannel
try {
_executorIntraband.registerChannel(
IntrabandTestUtil.<Channel>createProxy(Channel.class));
Assert.fail();
}
catch (IllegalArgumentException iae) {
Assert.assertEquals(
"Channel is not of type GatheringByteChannel",
iae.getMessage());
}
// Channel is not of type ScatteringByteChannel
try {
_executorIntraband.registerChannel(
IntrabandTestUtil.<Channel>createProxy(
GatheringByteChannel.class));
Assert.fail();
}
catch (IllegalArgumentException iae) {
Assert.assertEquals(
"Channel is not of type ScatteringByteChannel",
iae.getMessage());
}
// Channel is of type SelectableChannel and configured in nonblocking
// mode
SocketChannel[] peerSocketChannels =
IntrabandTestUtil.createSocketChannelPeers();
SocketChannel socketChannel = peerSocketChannels[0];
socketChannel.configureBlocking(false);
try {
_executorIntraband.registerChannel(socketChannel);
Assert.fail();
}
catch (IllegalArgumentException iae) {
Assert.assertEquals(
"Channel is of type SelectableChannel and configured in " +
"nonblocking mode",
iae.getMessage());
}
}
@Test
public void testRegisterChannelDuplexWithNonSelectableChannel()
throws Exception {
// Normal register, with unselectable channel
File tempFile = new File("tempFile");
tempFile.deleteOnExit();
RandomAccessFile randomAccessFile = new RandomAccessFile(
tempFile, "rw");
randomAccessFile.setLength(Integer.MAX_VALUE);
try (FileChannel fileChannel = randomAccessFile.getChannel()) {
FutureRegistrationReference futureRegistrationReference =
(FutureRegistrationReference)_executorIntraband.registerChannel(
fileChannel);
Assert.assertSame(
_executorIntraband, futureRegistrationReference.getIntraband());
Assert.assertTrue(futureRegistrationReference.isValid());
futureRegistrationReference.cancelRegistration();
Assert.assertFalse(futureRegistrationReference.isValid());
}
finally {
tempFile.delete();
}
}
@Test
public void testRegisterChannelDuplexWithSelectableChannel()
throws Exception {
// Normal register, with selectable channel
SocketChannel[] peerSocketChannels =
IntrabandTestUtil.createSocketChannelPeers();
SocketChannel socketChannel = peerSocketChannels[0];
socketChannel.configureBlocking(true);
try {
FutureRegistrationReference futureRegistrationReference =
(FutureRegistrationReference)_executorIntraband.registerChannel(
socketChannel);
Assert.assertSame(
_executorIntraband, futureRegistrationReference.getIntraband());
Assert.assertTrue(futureRegistrationReference.isValid());
futureRegistrationReference.cancelRegistration();
Assert.assertFalse(futureRegistrationReference.isValid());
}
finally {
peerSocketChannels[0].close();
peerSocketChannels[1].close();
}
}
@Test
public void testRegisterChannelReadWriteWithErrors() throws Exception {
// Gathering byte channel is null
try {
_executorIntraband.registerChannel(null, null);
Assert.fail();
}
catch (NullPointerException npe) {
Assert.assertEquals(
"Gathering byte channel is null", npe.getMessage());
}
// Scattering byte channel is null
try {
_executorIntraband.registerChannel(
null,
IntrabandTestUtil.<GatheringByteChannel>createProxy(
GatheringByteChannel.class));
Assert.fail();
}
catch (NullPointerException npe) {
Assert.assertEquals(
"Scattering byte channel is null", npe.getMessage());
}
// Scattering byte channel is of type SelectableChannel and configured
// in nonblocking mode
Pipe pipe = Pipe.open();
SourceChannel sourceChannel = pipe.source();
SinkChannel sinkChannel = pipe.sink();
sourceChannel.configureBlocking(false);
try {
_executorIntraband.registerChannel(sourceChannel, sinkChannel);
}
catch (IllegalArgumentException iae) {
Assert.assertEquals(
"Scattering byte channel is of type SelectableChannel and " +
"configured in nonblocking mode",
iae.getMessage());
}
// Gathering byte channel is of type SelectableChannel and configured in
// nonblocking mode
sourceChannel.configureBlocking(true);
sinkChannel.configureBlocking(false);
try {
_executorIntraband.registerChannel(sourceChannel, sinkChannel);
}
catch (IllegalArgumentException iae) {
Assert.assertEquals(
"Gathering byte channel is of type SelectableChannel and " +
"configured in nonblocking mode",
iae.getMessage());
}
}
@Test
public void testRegisterChannelReadWriteWithNonSelectableChannel()
throws Exception {
// Normal register, with unselectable channel
File tempFile = new File("tempFile");
tempFile.deleteOnExit();
try (RandomAccessFile randomAccessFile = new RandomAccessFile(
tempFile, "rw")) {
randomAccessFile.setLength(Integer.MAX_VALUE);
}
FileInputStream fileInputStream = new FileInputStream(tempFile);
FileOutputStream fileOutputStream = new FileOutputStream(
tempFile, true);
FileChannel readFileChannel = fileInputStream.getChannel();
FileChannel writeFileChannel = fileOutputStream.getChannel();
try {
FutureRegistrationReference futureRegistrationReference =
(FutureRegistrationReference)_executorIntraband.registerChannel(
readFileChannel, writeFileChannel);
Assert.assertSame(
_executorIntraband, futureRegistrationReference.getIntraband());
Assert.assertTrue(futureRegistrationReference.isValid());
futureRegistrationReference.writeFuture.cancel(true);
Assert.assertFalse(futureRegistrationReference.isValid());
futureRegistrationReference.cancelRegistration();
Assert.assertFalse(futureRegistrationReference.isValid());
}
finally {
readFileChannel.close();
writeFileChannel.close();
}
}
@Test
public void testRegisterChannelReadWriteWithSelectableChannel()
throws Exception {
// Normal register, with selectable channel
Pipe pipe = Pipe.open();
SourceChannel sourceChannel = pipe.source();
SinkChannel sinkChannel = pipe.sink();
sourceChannel.configureBlocking(true);
sinkChannel.configureBlocking(true);
try {
FutureRegistrationReference futureRegistrationReference =
(FutureRegistrationReference)_executorIntraband.registerChannel(
sourceChannel, sinkChannel);
Assert.assertSame(
_executorIntraband, futureRegistrationReference.getIntraband());
Assert.assertTrue(futureRegistrationReference.isValid());
futureRegistrationReference.writeFuture.cancel(true);
Assert.assertFalse(futureRegistrationReference.isValid());
futureRegistrationReference.cancelRegistration();
Assert.assertFalse(futureRegistrationReference.isValid());
}
finally {
sourceChannel.close();
sinkChannel.close();
}
}
@Test
public void testSendDatagramWithCallback() throws Exception {
// Submitted callback
Pipe readPipe = Pipe.open();
Pipe writePipe = Pipe.open();
GatheringByteChannel gatheringByteChannel = writePipe.sink();
ScatteringByteChannel scatteringByteChannel = readPipe.source();
FutureRegistrationReference futureRegistrationReference =
(FutureRegistrationReference)_executorIntraband.registerChannel(
writePipe.source(), readPipe.sink());
Object attachment = new Object();
RecordCompletionHandler<Object> recordCompletionHandler =
new RecordCompletionHandler<>();
_executorIntraband.sendDatagram(
futureRegistrationReference,
Datagram.createRequestDatagram(_TYPE, _data), attachment,
EnumSet.of(CompletionHandler.CompletionType.SUBMITTED),
recordCompletionHandler);
Datagram receiveDatagram = IntrabandTestUtil.readDatagramFully(
scatteringByteChannel);
recordCompletionHandler.waitUntilSubmitted();
Assert.assertSame(attachment, recordCompletionHandler.getAttachment());
Assert.assertEquals(_TYPE, receiveDatagram.getType());
ByteBuffer dataByteBuffer = receiveDatagram.getDataByteBuffer();
Assert.assertArrayEquals(_data, dataByteBuffer.array());
// Callback timeout, with log
try (CaptureHandler captureHandler =
JDKLoggerTestUtil.configureJDKLogger(
BaseIntraband.class.getName(), Level.WARNING)) {
List<LogRecord> logRecords = captureHandler.getLogRecords();
recordCompletionHandler = new RecordCompletionHandler<>();
_executorIntraband.sendDatagram(
futureRegistrationReference,
Datagram.createRequestDatagram(_TYPE, _data), attachment,
EnumSet.of(CompletionHandler.CompletionType.REPLIED),
recordCompletionHandler, 10, TimeUnit.MILLISECONDS);
Thread.sleep(20);
_executorIntraband.sendDatagram(
futureRegistrationReference,
Datagram.createRequestDatagram(_TYPE, _data), attachment,
EnumSet.of(CompletionHandler.CompletionType.DELIVERED),
recordCompletionHandler, 10, TimeUnit.MILLISECONDS);
while (logRecords.isEmpty());
IntrabandTestUtil.assertMessageStartWith(
logRecords.get(0), "Removed timeout response waiting datagram");
gatheringByteChannel.close();
scatteringByteChannel.close();
}
}
@Test
public void testWritingCallable() throws Exception {
// Normal writing
Pipe pipe = Pipe.open();
SourceChannel sourceChannel = pipe.source();
SinkChannel sinkChannel = pipe.sink();
BlockingQueue<Datagram> sendingQueue = new SynchronousQueue<>();
ChannelContext channelContext = new ChannelContext(sendingQueue);
channelContext.setRegistrationReference(
new MockRegistrationReference(_executorIntraband));
WritingCallable writingCallable =
_executorIntraband.new WritingCallable(sinkChannel, channelContext);
writingCallable.openLatch();
FutureTask<Void> futureTask = new FutureTask<>(writingCallable);
Thread writingThread = new Thread(futureTask);
writingThread.start();
Datagram datagram1 = Datagram.createRequestDatagram(_TYPE, _data);
sendingQueue.put(datagram1);
Datagram datagram2 = Datagram.createRequestDatagram(_TYPE, _data);
sendingQueue.put(datagram2);
Assert.assertTrue(
DatagramHelper.readFrom(
DatagramHelper.createReceiveDatagram(), sourceChannel));
Assert.assertTrue(
DatagramHelper.readFrom(
DatagramHelper.createReceiveDatagram(), sourceChannel));
// Interrupt on blocking take
while (writingThread.getState() != Thread.State.WAITING);
writingThread.interrupt();
Void result = futureTask.get();
Assert.assertNull(result);
writingThread.join();
sourceChannel.close();
sinkChannel.close();
// Interrupt on blocking write
pipe = Pipe.open();
sourceChannel = pipe.source();
sinkChannel = pipe.sink();
writingCallable = _executorIntraband.new WritingCallable(
sinkChannel, channelContext);
writingCallable.openLatch();
futureTask = new FutureTask<>(writingCallable);
writingThread = new Thread(futureTask);
writingThread.start();
int counter = 0;
while (sendingQueue.offer(
Datagram.createRequestDatagram(_TYPE, _data), 1,
TimeUnit.SECONDS)) {
counter++;
}
Assert.assertTrue(counter > 0);
writingThread.interrupt();
result = futureTask.get();
Assert.assertNull(result);
writingThread.join();
sourceChannel.close();
sinkChannel.close();
// Asynchronous close on blocking write
pipe = Pipe.open();
sourceChannel = pipe.source();
sinkChannel = pipe.sink();
writingCallable = _executorIntraband.new WritingCallable(
sinkChannel, channelContext);
writingCallable.openLatch();
futureTask = new FutureTask<>(writingCallable);
writingThread = new Thread(futureTask);
writingThread.start();
counter = 0;
while (sendingQueue.offer(
Datagram.createRequestDatagram(_TYPE, _data), 1,
TimeUnit.SECONDS)) {
counter++;
}
Assert.assertTrue(counter > 0);
sinkChannel.close();
result = futureTask.get();
Assert.assertNull(result);
writingThread.join();
sourceChannel.close();
sinkChannel.close();
// Change to nonblocking at runtime
pipe = Pipe.open();
sourceChannel = pipe.source();
sinkChannel = pipe.sink();
sinkChannel.configureBlocking(false);
writingCallable = _executorIntraband.new WritingCallable(
sinkChannel, channelContext);
writingCallable.openLatch();
futureTask = new FutureTask<>(writingCallable);
writingThread = new Thread(futureTask);
writingThread.start();
counter = 0;
while (sendingQueue.offer(
Datagram.createRequestDatagram(_TYPE, _data), 1,
TimeUnit.SECONDS) ||
writingThread.isAlive()) {
counter++;
}
Assert.assertTrue(counter > 0);
try {
futureTask.get();
Assert.fail();
}
catch (ExecutionException ee) {
Assert.assertEquals(
IllegalStateException.class, ee.getCause().getClass());
}
writingThread.join();
sourceChannel.close();
sinkChannel.close();
}
private static final String _DATA_STRING =
ExecutorIntrabandTest.class.getName();
private static final long _DEFAULT_TIMEOUT = Time.SECOND;
private static final byte _TYPE = 1;
private final byte[] _data = _DATA_STRING.getBytes(
Charset.defaultCharset());
private ExecutorIntraband _executorIntraband;
}