/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* 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.utils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.voltcore.logging.VoltLogger;
import org.voltcore.utils.DBBPool;
import org.voltcore.utils.DBBPool.BBContainer;
import org.voltdb.utils.BinaryDeque.BinaryDequeReader;
public class TestPBDMultipleReaders {
private final static VoltLogger logger = new VoltLogger("EXPORT");
private static final int s_segmentFillCount = 47;
private PersistentBinaryDeque m_pbd;
private class PBDReader {
private String m_readerId;
private int m_totalRead;
private BinaryDequeReader m_reader;
public PBDReader(String readerId) throws IOException {
m_readerId = readerId;
m_reader = m_pbd.openForRead(m_readerId);
}
public int readToEndOfSegment() throws Exception {
int end = (m_totalRead/s_segmentFillCount + 1) * s_segmentFillCount;
boolean done = false;
int numRead = 0;
for (int i=m_totalRead; i<end && !done; i++) {
BBContainer bbC = m_reader.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
if (bbC==null) {
done = true;
continue;
}
numRead++;
bbC.discard();
}
return numRead;
}
}
@Test
public void testMultipleParallelReaders() throws Exception {
int numBuffers = 100;
for (int i=0; i<numBuffers; i++) {
m_pbd.offer( DBBPool.wrapBB(TestPersistentBinaryDeque.getFilledBuffer(i)) );
}
int numSegments = TestPersistentBinaryDeque.getSortedDirectoryListing().size();
int numReaders = 3;
PBDReader[] readers = new PBDReader[numReaders];
for (int i=0; i<numReaders; i++) {
readers[i] = new PBDReader("reader" + i);
}
int currNumSegments = numSegments;
for (int i=0; i<numSegments; i++) {
// One reader finishing shouldn't discard the segment
readers[0].readToEndOfSegment();
assertEquals(currNumSegments, TestPersistentBinaryDeque.getSortedDirectoryListing().size());
// Once all readers finish reading, the segment should get discarded.
for (int j=1; j<numReaders; j++) {
readers[j].readToEndOfSegment();
}
if (i < numSegments-1) currNumSegments--;
assertEquals(currNumSegments, TestPersistentBinaryDeque.getSortedDirectoryListing().size());
}
}
@Test
public void testOpenReaders() throws Exception {
String cursorId = "reader";
BinaryDequeReader reader1 = m_pbd.openForRead(cursorId);
BinaryDequeReader reader2 = m_pbd.openForRead(cursorId);
BinaryDequeReader another = m_pbd.openForRead("another");
assertTrue(reader1==reader2);
assertFalse(reader1==another);
int numBuffers = 50;
for (int i=0; i<numBuffers; i++) {
m_pbd.offer( DBBPool.wrapBB(TestPersistentBinaryDeque.getFilledBuffer(i)) );
}
for (int j=0; j<numBuffers; j++) {
BBContainer bbC = reader1.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
bbC.discard();
}
assertTrue(reader1.isEmpty());
assertTrue(reader2.isEmpty());
assertFalse(another.isEmpty());
}
@Test
public void testSegmentClosingWriterOnly() throws Exception {
// Initially no readers and nothing written. Open segments must be 1
assertEquals(1, m_pbd.numOpenSegments());
for (int i=0; i<3; i++) {
for (int j=0; j<s_segmentFillCount; j++) {
m_pbd.offer( DBBPool.wrapBB(TestPersistentBinaryDeque.getFilledBuffer(j)) );
}
assertEquals(1, m_pbd.numOpenSegments());
}
}
@Test
public void testSegmentClosingWriterReaderLockStep() throws Exception {
assertEquals(1, m_pbd.numOpenSegments());
BinaryDequeReader reader = m_pbd.openForRead("reader0");
for (int i=0; i<3; i++) {
for (int j=0; j<s_segmentFillCount; j++) {
m_pbd.offer( DBBPool.wrapBB(TestPersistentBinaryDeque.getFilledBuffer(j)) );
BBContainer bbC = reader.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
bbC.discard();
}
assertEquals(1, m_pbd.numOpenSegments());
}
}
@Test
public void testSegmentClosingWriterReader() throws Exception {
assertEquals(1, m_pbd.numOpenSegments());
int numSegments = 3;
for (int i=0; i<numSegments; i++) {
for (int j=0; j<s_segmentFillCount; j++) {
m_pbd.offer( DBBPool.wrapBB(TestPersistentBinaryDeque.getFilledBuffer(j)) );
}
assertEquals(1, m_pbd.numOpenSegments());
}
BinaryDequeReader reader = m_pbd.openForRead("reader0");
for (int i=0; i<numSegments; i++) {
for (int j=0; j<46; j++) {
BBContainer bbC = reader.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
bbC.discard();
}
int expected = (i == numSegments-1) ? 1 : 2;
assertEquals(expected, m_pbd.numOpenSegments());
BBContainer bbC = reader.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
bbC.discard();
// there should only be 1 open because last discard closes and deletes
assertEquals(1, m_pbd.numOpenSegments());
}
}
@Test
public void testSegmentClosingWriterMultipleReaders() throws Exception {
assertEquals(1, m_pbd.numOpenSegments());
int numSegments = 5;
for (int i=0; i<numSegments; i++) {
for (int j=0; j<s_segmentFillCount; j++) {
m_pbd.offer( DBBPool.wrapBB(TestPersistentBinaryDeque.getFilledBuffer(j)) );
}
assertEquals(1, m_pbd.numOpenSegments());
}
BinaryDequeReader reader0 = m_pbd.openForRead("reader0");
BinaryDequeReader reader1 = m_pbd.openForRead("reader1");
// Position first reader0 on penultimate segment and reader1 on first segment
for (int i=0; i<numSegments-1; i++) {
for (int j=0; j<46; j++) {
BBContainer bbC = reader0.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
bbC.discard();
if (i==0) {
bbC = reader1.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
bbC.discard();
}
}
BBContainer bbC = reader0.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
bbC.discard();
if (i==0) {
assertEquals(2, m_pbd.numOpenSegments());
} else {
assertEquals(3, m_pbd.numOpenSegments());
}
}
BBContainer bbC = reader1.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
bbC.discard();
// Both readers finished reading first segment, so that is closed and deleted,
// which reduces the # of open segments by 1
assertEquals(2, m_pbd.numOpenSegments());
// reader0 at penultimate. Move reader1 through segments and check open segments
for (int i=1; i<numSegments-1; i++) {
for (int j=0; j<46; j++) {
bbC = reader1.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
bbC.discard();
}
int expected = (i == numSegments-2) ? 2 : 3;
assertEquals(expected, m_pbd.numOpenSegments());
bbC = reader1.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
bbC.discard();
expected = (i == numSegments-2) ? 1 : 2;
assertEquals(expected, m_pbd.numOpenSegments());
}
// read the last segment
for (int j=0; j<s_segmentFillCount; j++) {
bbC = reader0.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
bbC.discard();
bbC = reader1.poll(PersistentBinaryDeque.UNSAFE_CONTAINER_FACTORY);
bbC.discard();
}
assertEquals(1, m_pbd.numOpenSegments());
}
@Before
public void setUp() throws Exception {
TestPersistentBinaryDeque.setupTestDir();
m_pbd = new PersistentBinaryDeque(TestPersistentBinaryDeque.TEST_NONCE, TestPersistentBinaryDeque.TEST_DIR, logger );
}
@After
public void tearDown() throws Exception {
try {
m_pbd.close();
} catch (Exception e) {}
try {
TestPersistentBinaryDeque.tearDownTestDir();
} finally {
m_pbd = null;
}
System.gc();
System.runFinalization();
}
}