/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* This file contains original code and/or modifications of original code.
* Any modifications made by VoltDB Inc. are licensed under the following
* terms and conditions:
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
/* Copyright (C) 2008
* Evan Jones
* Massachusetts Institute of Technology
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package org.voltcore.network;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.SelectionKey;
import java.util.concurrent.atomic.AtomicLong;
import org.voltcore.utils.EstTime;
import org.voltcore.utils.EstTimeUpdater;
import org.voltdb.AdmissionControlGroup;
import junit.framework.TestCase;
public class TestNIOWriteStream extends TestCase {
private class MockPort extends VoltPort {
@Override
public String toString() {
return null;
}
public MockPort() throws UnknownHostException {
super(null, null, new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 21212), pool);
}
@Override
public void setInterests(int opsToAdd, int opsToRemove) {
this.opsToAdd = opsToAdd;
}
public boolean checkWriteSet() {
if (opsToAdd == SelectionKey.OP_WRITE) {
opsToAdd = 0;
return true;
}
return false;
}
int opsToAdd;
}
NetworkDBBPool pool;
@Override
public void setUp() {
//Use low size for each buffer in pool
pool = new NetworkDBBPool(64, 4);
}
@Override
public void tearDown() {
pool.clear();
}
/**
* Mock channel that will either consume all, some or no
* bytes from the buffer.
*/
private static class MockChannel implements GatheringByteChannel {
MockChannel(int behavior, int closeafter) {
m_behavior = behavior;
this.closeAfter = closeafter;
}
private int writeCount = 0;
private final int closeAfter;
private boolean didOversizeWrite = false;
private boolean wrotePartial = false;
public boolean m_open = true;
public int m_behavior;
public static int SINK = 0; // accept all data
public static int FULL = 1; // accept no data
public static int PARTIAL = 2; // accept some data
@Override
public int write(ByteBuffer src) throws IOException {
if (!m_open) throw new IOException();
if (closeAfter > 0 && ++writeCount >= closeAfter) {
m_open = false;
}
if (!src.isDirect()) {
if (src.remaining() > 1024 * 256) {
didOversizeWrite = true;
}
}
if (m_behavior == SINK) {
int remaining = src.remaining();
src.position(src.limit());
return remaining;
}
else if (m_behavior == FULL) {
return 0;
}
else if (m_behavior == PARTIAL) {
if (wrotePartial) {
return 0;
} else {
wrotePartial = true;
}
ByteBuffer copy = ByteBuffer.allocate(src.remaining());
src.get(copy.array(), 0, src.remaining()/2);
return src.remaining();
}
assert(false);
return -1;
}
@Override
public long write(ByteBuffer src[]) throws IOException {
if (!m_open) throw new IOException();
if (closeAfter > 0 && ++writeCount >= closeAfter) {
m_open = false;
}
if (m_behavior == SINK) {
int remaining = src[0].remaining();
src[0].position(src[0].limit());
return remaining;
}
else if (m_behavior == FULL) {
return 0;
}
else if (m_behavior == PARTIAL) {
if (wrotePartial) {
return 0;
} else {
wrotePartial = true;
}
ByteBuffer copy = ByteBuffer.allocate(src[0].remaining());
src[0].get(copy.array(), 0, src[0].remaining()/2);
return src[0].remaining();
}
assert(false);
return -1;
}
@Override
public void close() throws IOException {
// TODO Auto-generated method stub
}
@Override
public boolean isOpen() {
return m_open;
}
@Override
public long write(ByteBuffer[] srcs, int offset, int length)
throws IOException {
// TODO Auto-generated method stub
return 0;
}
}
public void testSink() throws IOException {
MockChannel channel = new MockChannel(MockChannel.SINK, 0);
MockPort port = new MockPort();
NIOWriteStream wstream = new NIOWriteStream(port);
assertTrue(wstream.isEmpty());
ByteBuffer tmp = ByteBuffer.allocate(2);
tmp.put((byte) 1);
tmp.put((byte) 2);
tmp.flip();
wstream.enqueue(tmp);
assertTrue(port.checkWriteSet());
assertEquals(1, wstream.getOutstandingMessageCount());
wstream.serializeQueuedWrites(pool);
assertEquals(2, wstream.drainTo(channel));
assertTrue(wstream.isEmpty());
assertEquals(0, wstream.getOutstandingMessageCount());
wstream.shutdown();
port.toString();
}
public void testFull() throws IOException {
MockChannel channel = new MockChannel(MockChannel.FULL, 0);
MockPort port = new MockPort();
NIOWriteStream wstream = new NIOWriteStream(port);
assertTrue(wstream.isEmpty());
ByteBuffer tmp = ByteBuffer.allocate(4);
tmp.put((byte)1);
tmp.put((byte)2);
tmp.put((byte)3);
tmp.put((byte)4);
tmp.flip();
wstream.enqueue(tmp);
assertTrue(port.checkWriteSet());
assertEquals(1, wstream.getOutstandingMessageCount());
wstream.serializeQueuedWrites(pool);
wstream.drainTo(channel);
assertFalse(wstream.isEmpty());
channel.m_behavior = MockChannel.SINK;
wstream.serializeQueuedWrites(pool);
int wrote = wstream.drainTo(channel);
assertEquals(4, wrote);
assertTrue(wstream.isEmpty());
assertEquals(0, wstream.getOutstandingMessageCount());
wstream.shutdown();
}
public void testPartial() throws IOException {
MockChannel channel = new MockChannel(MockChannel.PARTIAL, 0);
MockPort port = new MockPort();
NIOWriteStream wstream = new NIOWriteStream(port);
assertTrue(wstream.isEmpty());
ByteBuffer tmp = ByteBuffer.allocate(4);
tmp.put((byte)1);
tmp.put((byte)2);
tmp.put((byte)3);
tmp.put((byte)4);
tmp.flip();
wstream.enqueue(tmp);
assertTrue(port.checkWriteSet());
wstream.serializeQueuedWrites(pool);
int wrote = wstream.drainTo(channel);
assertFalse(wstream.isEmpty());
assertEquals(2, wrote);
channel.wrotePartial = false;
ByteBuffer tmp2 = ByteBuffer.allocate(4);
tmp2.put((byte)5);
tmp2.put((byte)6);
tmp2.put((byte)7);
tmp2.put((byte)8);
tmp2.flip();
wstream.enqueue(tmp2);
wstream.serializeQueuedWrites(pool);
wrote += wstream.drainTo( channel);
assertFalse(wstream.isEmpty());
// wrote half of half of the first buffer (note +=)
assertEquals(3, wrote);
channel.m_behavior = MockChannel.SINK;
wstream.serializeQueuedWrites(pool);
wrote += wstream.drainTo( channel);
assertEquals(8, wrote);
assertTrue(wstream.isEmpty());
wstream.shutdown();
}
public void testClosed() throws IOException {
MockChannel channel = new MockChannel(MockChannel.FULL, 0);
MockPort port = new MockPort();
NIOWriteStream wstream = new NIOWriteStream(port);
ByteBuffer tmp = ByteBuffer.allocate(4);
tmp.put((byte)1);
tmp.put((byte)2);
tmp.put((byte)3);
tmp.put((byte)4);
tmp.flip();
wstream.enqueue(tmp);
assertTrue(port.checkWriteSet());
wstream.serializeQueuedWrites(pool);
int closed = wstream.drainTo(channel);
assertEquals(closed, 0);
channel.m_open = false;
boolean threwException = false;
try {
wstream.serializeQueuedWrites(pool);
assertEquals( -1, wstream.drainTo( channel));
} catch (IOException e) {
threwException = true;
}
assertTrue(threwException);
wstream.shutdown();
}
public void testClosedWithBackPressure() throws IOException {
MockChannel channel = new MockChannel(MockChannel.SINK, 1);
MockPort port = new MockPort();
AdmissionControlGroup acg = new AdmissionControlGroup(2, 1024);
NIOWriteStream wstream = new NIOWriteStream(port, null, null, acg);
ByteBuffer tmp = ByteBuffer.allocate(6);
tmp.put((byte)1);
tmp.put((byte)2);
tmp.put((byte)3);
tmp.put((byte)4);
tmp.put((byte)5);
tmp.put((byte)6);
tmp.flip();
ByteBuffer tmp2 = ByteBuffer.allocate(4);
tmp2.put((byte)1);
tmp2.put((byte)2);
tmp2.put((byte)3);
tmp2.put((byte)4);
tmp2.flip();
wstream.enqueue(tmp);
wstream.enqueue(tmp2);
assertTrue(port.checkWriteSet());
boolean threwException = false;
try {
wstream.serializeQueuedWrites(pool);
//First write will succeed leaving 2 in first buffer and 4 in next
wstream.drainTo( channel);
} catch (IOException e) {
threwException = true;
}
assertTrue(threwException);
//Since ACG limit is 2 bytes we should be in backpressure.
assertTrue(acg.hasBackPressure());
assertEquals(6, acg.getPendingBytes());
wstream.shutdown();
//We should be out of backpressure.
assertFalse(acg.hasBackPressure());
//acg pending bytes must be 0
assertEquals(0, acg.getPendingBytes());
}
public void testLargeNonDirectWrite() throws IOException {
MockChannel channel = new MockChannel(MockChannel.SINK, 0);
MockPort port = new MockPort();
NIOWriteStream wstream = new NIOWriteStream(port);
ByteBuffer tmp = ByteBuffer.allocate(262144 * 3);
wstream.enqueue(tmp);
assertTrue(port.checkWriteSet());
wstream.serializeQueuedWrites(pool);
int written = wstream.drainTo( channel);
assertEquals( 262144 * 3, written);
assertFalse(channel.didOversizeWrite);
wstream.shutdown();
}
public void testLastWriteDelta() throws Exception {
EstTimeUpdater.pause = true;
Thread.sleep(10);
try {
final MockChannel channel = new MockChannel(MockChannel.SINK, 0);
MockPort port = new MockPort();
NIOWriteStream wstream = new NIOWriteStream(port);
assertEquals( 0, wstream.calculatePendingWriteDelta(999));
EstTimeUpdater.update(System.currentTimeMillis());
/**
* Test the basic write and drain
*/
final ByteBuffer b = ByteBuffer.allocate(5);
wstream.enqueue(b.duplicate());
assertEquals( 5, wstream.calculatePendingWriteDelta(EstTime.currentTimeMillis() + 5));
wstream.serializeQueuedWrites(pool);
wstream.drainTo( channel);
assertEquals( 0, wstream.calculatePendingWriteDelta(EstTime.currentTimeMillis() + 5));
Thread.sleep(20);
EstTimeUpdater.update(System.currentTimeMillis());
wstream.enqueue(b.duplicate());
assertEquals( 5, wstream.calculatePendingWriteDelta(EstTime.currentTimeMillis() + 5));
wstream.enqueue(b.duplicate());
assertEquals( 5, wstream.calculatePendingWriteDelta(EstTime.currentTimeMillis() + 5));
channel.m_behavior = MockChannel.PARTIAL;
wstream.serializeQueuedWrites(pool);
wstream.drainTo( channel );
assertEquals( 5, wstream.calculatePendingWriteDelta(EstTime.currentTimeMillis() + 5));
wstream.shutdown();
} finally {
EstTimeUpdater.pause = false;
}
}
public void testQueueMonitor() throws Exception {
final MockChannel channel = new MockChannel(MockChannel.FULL, 0);
MockPort port = new MockPort();
final AtomicLong queue = new AtomicLong();
NIOWriteStream wstream = new NIOWriteStream(port, null, null, new QueueMonitor() {
@Override
public boolean queue(int bytes) {
queue.addAndGet(bytes);
return false;
}
});
wstream.enqueue(ByteBuffer.allocate(32));
wstream.serializeQueuedWrites(pool);
assertEquals(32, queue.get());
wstream.drainTo(channel);
assertEquals(32, queue.get());
wstream.enqueue(ByteBuffer.allocate(32));
wstream.serializeQueuedWrites(pool);
assertEquals(64, queue.get());
wstream.shutdown();
assertEquals(0, queue.get());
}
}