/* This file is part of VoltDB. * Copyright (C) 2008-2010 VoltDB L.L.C. * * This file contains original code and/or modifications of original code. * Any modifications made by VoltDB L.L.C. 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.voltdb.network; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.GatheringByteChannel; import junit.framework.*; import java.nio.channels.SelectionKey; import org.voltdb.utils.DBBPool; import org.voltdb.utils.EstTime; import org.voltdb.utils.EstTimeUpdater; public class NIOWriteStreamTest extends TestCase { private static class MockPort extends VoltPort { @Override public String toString() { return null; } public MockPort() { super(null, null, 2048, ""); } @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; } DBBPool pool; @Override public void setUp() { pool = new DBBPool(); } @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) { m_behavior = behavior; } private boolean wroteSizeZero = false; private boolean didOversizeWrite = false; private boolean wrotePartial = false; @Override public int write(ByteBuffer src) throws IOException { if (!m_open) throw new IOException(); if (!src.hasRemaining()) { wroteSizeZero = true; } if (!src.isDirect()) { if (src.remaining() > NIOWriteStream.MAX_GATHERING_WRITE) { 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; } public long write(ByteBuffer src[]) throws IOException { if (!m_open) throw new IOException(); 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; } 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 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); MockPort port = new MockPort(); NIOWriteStream wstream = new NIOWriteStream(port); assertTrue(wstream.isEmpty()); ByteBuffer tmp = ByteBuffer.allocate(5); tmp.put((byte) 1); tmp.put((byte) 2); tmp.flip(); assertTrue(wstream.enqueue(tmp)); assertTrue(port.checkWriteSet()); assertEquals(2, wstream.drainTo(channel, wstream.swapAndSerializeQueuedWrites(pool))); assertTrue(wstream.isEmpty()); wstream.shutdown(); port.toString(); } public void testFull() throws IOException { MockChannel channel = new MockChannel(MockChannel.FULL); MockPort port = new MockPort(); NIOWriteStream wstream = new NIOWriteStream(port); assertTrue(wstream.isEmpty()); ByteBuffer tmp = ByteBuffer.allocate(5); tmp.put((byte)1); tmp.put((byte)2); tmp.put((byte)3); tmp.put((byte)4); tmp.flip(); assertTrue(wstream.enqueue(tmp)); assertTrue(port.checkWriteSet()); assertEquals(0, wstream.drainTo(channel, wstream.swapAndSerializeQueuedWrites(pool))); assertFalse(wstream.isEmpty()); channel.m_behavior = MockChannel.SINK; int wrote = wstream.drainTo(channel, wstream.swapAndSerializeQueuedWrites(pool)); assertEquals(4, wrote); assertTrue(wstream.isEmpty()); wstream.shutdown(); } public void testPartial() throws IOException { MockChannel channel = new MockChannel(MockChannel.PARTIAL); MockPort port = new MockPort(); NIOWriteStream wstream = new NIOWriteStream(port); assertTrue(wstream.isEmpty()); ByteBuffer tmp = ByteBuffer.allocate(5); tmp.put((byte)1); tmp.put((byte)2); tmp.put((byte)3); tmp.put((byte)4); tmp.flip(); assertTrue(wstream.enqueue(tmp)); assertTrue(port.checkWriteSet()); int wrote = wstream.drainTo(channel, wstream.swapAndSerializeQueuedWrites(pool)); 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); org.voltdb.utils.DBBPool.BBContainer containers[] = wstream.swapAndSerializeQueuedWrites(pool); wrote += wstream.drainTo( channel, containers); assertFalse(wstream.isEmpty()); // wrote half of half of the first buffer (note +=) assertEquals(3, wrote); channel.m_behavior = MockChannel.SINK; wrote += wstream.drainTo( channel, wstream.swapAndSerializeQueuedWrites(pool)); assertEquals(8, wrote); assertTrue(wstream.isEmpty()); wstream.shutdown(); } public void testClosed() throws IOException { MockChannel channel = new MockChannel(MockChannel.FULL); MockPort port = new MockPort(); NIOWriteStream wstream = new NIOWriteStream(port); ByteBuffer tmp = ByteBuffer.allocate(5); tmp.put((byte)1); tmp.put((byte)2); tmp.put((byte)3); tmp.put((byte)4); tmp.flip(); assertTrue(wstream.enqueue(tmp)); assertTrue(port.checkWriteSet()); int closed = wstream.drainTo(channel, wstream.swapAndSerializeQueuedWrites(pool)); assertEquals(closed, 0); channel.m_open = false; boolean threwException = false; try { assertEquals( -1, wstream.drainTo( channel, wstream.swapAndSerializeQueuedWrites(pool))); } catch (IOException e) { threwException = true; } assertTrue(threwException); wstream.shutdown(); } public void testLargeNonDirectWrite() throws IOException { MockChannel channel = new MockChannel(MockChannel.SINK); MockPort port = new MockPort(); NIOWriteStream wstream = new NIOWriteStream(port); ByteBuffer tmp = ByteBuffer.allocate(NIOWriteStream.MAX_GATHERING_WRITE * 2); assertTrue(wstream.enqueue(tmp)); assertTrue(port.checkWriteSet()); int written = wstream.drainTo( channel, wstream.swapAndSerializeQueuedWrites(pool)); assertEquals( NIOWriteStream.MAX_GATHERING_WRITE * 2, written); assertFalse(channel.didOversizeWrite); assertTrue(channel.wroteSizeZero); wstream.shutdown(); } public void testLastWriteDelta() throws Exception { final MockChannel channel = new MockChannel(MockChannel.SINK); 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.drainTo( channel, wstream.swapAndSerializeQueuedWrites(pool)); 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.drainTo( channel, wstream.swapAndSerializeQueuedWrites(pool)); assertEquals( 5, wstream.calculatePendingWriteDelta(EstTime.currentTimeMillis() + 5)); wstream.shutdown(); } }