mirror of https://github.com/apache/activemq.git
https://issues.apache.org/jira/browse/AMQ-3434: Contention in PLIist creation results in NPE on load - FilePendingMessageCursor. Resolve contention on creation, tidy up ListIndex iterator remove and plist release, additional test that stresses contention such that it can reproduce the stomp load test scenario
git-svn-id: https://svn.apache.org/repos/asf/activemq/trunk@1153420 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
24cd2b3f29
commit
bf59b7d70f
|
@ -133,6 +133,9 @@ public class FilePendingMessageCursor extends AbstractPendingMessageCursor imple
|
|||
@Override
|
||||
public synchronized void release() {
|
||||
iterating = false;
|
||||
if (iter instanceof DiskIterator) {
|
||||
((DiskIterator)iter).release();
|
||||
};
|
||||
if (flushRequired) {
|
||||
flushRequired = false;
|
||||
if (!hasSpace()) {
|
||||
|
@ -417,7 +420,7 @@ public class FilePendingMessageCursor extends AbstractPendingMessageCursor imple
|
|||
}
|
||||
|
||||
protected synchronized void flushToDisk() {
|
||||
if (!memoryList.isEmpty()) {
|
||||
if (!memoryList.isEmpty() && store != null) {
|
||||
long start = 0;
|
||||
if (LOG.isTraceEnabled()) {
|
||||
start = System.currentTimeMillis();
|
||||
|
@ -483,7 +486,7 @@ public class FilePendingMessageCursor extends AbstractPendingMessageCursor imple
|
|||
}
|
||||
|
||||
final class DiskIterator implements Iterator<MessageReference> {
|
||||
private final Iterator<PListEntry> iterator;
|
||||
private final PList.PListIterator iterator;
|
||||
DiskIterator() {
|
||||
try {
|
||||
iterator = getDiskList().iterator();
|
||||
|
@ -510,5 +513,8 @@ public class FilePendingMessageCursor extends AbstractPendingMessageCursor imple
|
|||
iterator.remove();
|
||||
}
|
||||
|
||||
public void release() {
|
||||
iterator.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ import java.util.Set;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.apache.kahadb.index.ListIndex;
|
||||
import org.apache.kahadb.index.ListNode;
|
||||
import org.apache.kahadb.journal.Location;
|
||||
import org.apache.kahadb.page.Transaction;
|
||||
import org.apache.kahadb.util.ByteSequence;
|
||||
|
@ -58,11 +57,11 @@ public class PList extends ListIndex<String, Location> {
|
|||
}
|
||||
|
||||
void read(DataInput in) throws IOException {
|
||||
this.headPageId = in.readLong();
|
||||
setHeadPageId(in.readLong());
|
||||
}
|
||||
|
||||
public void write(DataOutput out) throws IOException {
|
||||
out.writeLong(this.headPageId);
|
||||
out.writeLong(getHeadPageId());
|
||||
}
|
||||
|
||||
public synchronized void destroy() throws IOException {
|
||||
|
@ -185,17 +184,19 @@ public class PList extends ListIndex<String, Location> {
|
|||
return size() == 0;
|
||||
}
|
||||
|
||||
synchronized public Iterator<PListEntry> iterator() throws IOException {
|
||||
public PListIterator iterator() throws IOException {
|
||||
return new PListIterator();
|
||||
}
|
||||
|
||||
private final class PListIterator implements Iterator<PListEntry> {
|
||||
public final class PListIterator implements Iterator<PListEntry> {
|
||||
final Iterator<Map.Entry<String, Location>> iterator;
|
||||
final Transaction tx;
|
||||
|
||||
PListIterator() throws IOException {
|
||||
tx = store.pageFile.tx();
|
||||
this.iterator = iterator(tx);
|
||||
synchronized (indexLock) {
|
||||
this.iterator = iterator(tx);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -234,6 +235,16 @@ public class PList extends ListIndex<String, Location> {
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
try {
|
||||
tx.rollback();
|
||||
} catch (IOException unexpected) {
|
||||
IllegalStateException e = new IllegalStateException(unexpected);
|
||||
e.initCause(unexpected);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void claimFileLocations(final Set<Integer> candidates) throws IOException {
|
||||
|
@ -254,6 +265,6 @@ public class PList extends ListIndex<String, Location> {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "" + name + ",[headPageId=" + headPageId + ",tailPageId=" + tailPageId + ", size=" + size() + "]";
|
||||
return name + "[headPageId=" + getHeadPageId() + ",tailPageId=" + getTailPageId() + ", size=" + size() + "]";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@ public class PListStore extends ServiceSupport implements BrokerServiceAware, Ru
|
|||
private int indexPageSize = PageFile.DEFAULT_PAGE_SIZE;
|
||||
private int indexCacheSize = PageFile.DEFAULT_PAGE_CACHE_SIZE;
|
||||
private int indexWriteBatchSize = PageFile.DEFAULT_WRITE_BATCH_SIZE;
|
||||
private boolean indexEnablePageCaching = true;
|
||||
|
||||
public Object getIndexLock() {
|
||||
return indexLock;
|
||||
|
@ -110,6 +111,14 @@ public class PListStore extends ServiceSupport implements BrokerServiceAware, Ru
|
|||
this.indexWriteBatchSize = indexWriteBatchSize;
|
||||
}
|
||||
|
||||
public boolean getIndexEnablePageCaching() {
|
||||
return indexEnablePageCaching;
|
||||
}
|
||||
|
||||
public void setIndexEnablePageCaching(boolean indexEnablePageCaching) {
|
||||
this.indexEnablePageCaching = indexEnablePageCaching;
|
||||
}
|
||||
|
||||
protected class MetaData {
|
||||
protected MetaData(PListStore store) {
|
||||
this.store = store;
|
||||
|
@ -223,10 +232,10 @@ public class PListStore extends ServiceSupport implements BrokerServiceAware, Ru
|
|||
result = pl;
|
||||
this.persistentLists.put(name, pl);
|
||||
}
|
||||
final PList load = result;
|
||||
final PList toLoad = result;
|
||||
getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||
public void execute(Transaction tx) throws IOException {
|
||||
load.load(tx);
|
||||
toLoad.load(tx);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -269,6 +278,7 @@ public class PListStore extends ServiceSupport implements BrokerServiceAware, Ru
|
|||
this.journal.setWriteBatchSize(getJournalMaxWriteBatchSize());
|
||||
this.journal.start();
|
||||
this.pageFile = new PageFile(directory, "tmpDB");
|
||||
this.pageFile.setEnablePageCaching(getIndexEnablePageCaching());
|
||||
this.pageFile.setPageSize(getIndexPageSize());
|
||||
this.pageFile.setWriteBatchSize(getIndexWriteBatchSize());
|
||||
this.pageFile.setPageCacheSize(getIndexCacheSize());
|
||||
|
@ -340,12 +350,21 @@ public class PListStore extends ServiceSupport implements BrokerServiceAware, Ru
|
|||
|
||||
public void run() {
|
||||
try {
|
||||
final int lastJournalFileId = journal.getLastAppendLocation().getDataFileId();
|
||||
final Set<Integer> candidates = journal.getFileMap().keySet();
|
||||
LOG.trace("Full gc candidate set:" + candidates);
|
||||
if (candidates.size() > 1) {
|
||||
// prune current write
|
||||
for (Iterator<Integer> iterator = candidates.iterator(); iterator.hasNext();) {
|
||||
if (iterator.next() >= lastJournalFileId) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
List<PList> plists = null;
|
||||
synchronized (this) {
|
||||
plists = new ArrayList(persistentLists.values());
|
||||
synchronized (indexLock) {
|
||||
synchronized (this) {
|
||||
plists = new ArrayList(persistentLists.values());
|
||||
}
|
||||
}
|
||||
for (PList list : plists) {
|
||||
list.claimFileLocations(candidates);
|
||||
|
|
|
@ -18,11 +18,11 @@ package org.apache.activemq.store.kahadb.plist;
|
|||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
@ -44,8 +44,9 @@ public class PListTest {
|
|||
private PListStore store;
|
||||
private PList plist;
|
||||
final ByteSequence payload = new ByteSequence(new byte[400]);
|
||||
final String idSeed = new String("Seed");
|
||||
final String idSeed = new String("Seed" + new byte[1024]);
|
||||
final Vector<Throwable> exceptions = new Vector<Throwable>();
|
||||
ExecutorService executor;
|
||||
|
||||
|
||||
@Test
|
||||
|
@ -146,7 +147,18 @@ public class PListTest {
|
|||
assertFalse(plist.remove("doesNotExist"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testRemoveSingleEntry() throws Exception {
|
||||
plist.addLast("First", new ByteSequence("A".getBytes()));
|
||||
|
||||
Iterator<PListEntry> iterator = plist.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
PListEntry v = iterator.next();
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveSecondPosition() throws Exception {
|
||||
plist.addLast("First", new ByteSequence("A".getBytes()));
|
||||
|
@ -154,7 +166,7 @@ public class PListTest {
|
|||
|
||||
assertTrue(plist.remove(1));
|
||||
assertTrue(plist.remove(0));
|
||||
assertFalse(plist.remove(3));
|
||||
assertFalse(plist.remove(0));
|
||||
}
|
||||
|
||||
|
||||
|
@ -165,36 +177,47 @@ public class PListTest {
|
|||
IOHelper.mkdirs(directory);
|
||||
IOHelper.deleteChildren(directory);
|
||||
store = new PListStore();
|
||||
store.setCleanupInterval(400);
|
||||
store.setDirectory(directory);
|
||||
store.setJournalMaxFileLength(1024*5);
|
||||
store.start();
|
||||
|
||||
final ByteSequence payload = new ByteSequence(new byte[1024*4]);
|
||||
final ByteSequence payload = new ByteSequence(new byte[1024*2]);
|
||||
|
||||
|
||||
final Vector<Throwable> exceptions = new Vector<Throwable>();
|
||||
final int iterations = 1000;
|
||||
final int iterations = 5000;
|
||||
final int numLists = 10;
|
||||
|
||||
final PList[] lists = new PList[numLists];
|
||||
String threadName = Thread.currentThread().getName();
|
||||
for (int i=0; i<numLists; i++) {
|
||||
lists[i] = store.getPList("List" + i);
|
||||
Thread.currentThread().setName("C:"+String.valueOf(i));
|
||||
lists[i] = store.getPList(String.valueOf(i));
|
||||
}
|
||||
Thread.currentThread().setName(threadName);
|
||||
|
||||
ExecutorService executor = Executors.newFixedThreadPool(100);
|
||||
executor = Executors.newFixedThreadPool(100);
|
||||
class A implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
final String threadName = Thread.currentThread().getName();
|
||||
try {
|
||||
for (int i=0; i<iterations; i++) {
|
||||
PList candidate = lists[i%numLists];
|
||||
candidate.addLast(String.valueOf(i), payload);
|
||||
PListEntry entry = candidate.getFirst();
|
||||
assertTrue(candidate.remove(String.valueOf(i)));
|
||||
Thread.currentThread().setName("ALRF:"+candidate.getName());
|
||||
synchronized (plistLocks(candidate)) {
|
||||
candidate.addLast(String.valueOf(i), payload);
|
||||
PListEntry entry = candidate.getFirst();
|
||||
assertTrue(candidate.remove(String.valueOf(i)));
|
||||
}
|
||||
}
|
||||
} catch (Exception error) {
|
||||
LOG.error("Unexpcted ex", error);
|
||||
error.printStackTrace();
|
||||
exceptions.add(error);
|
||||
} finally {
|
||||
Thread.currentThread().setName(threadName);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -202,16 +225,22 @@ public class PListTest {
|
|||
class B implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
final String threadName = Thread.currentThread().getName();
|
||||
try {
|
||||
for (int i=0; i<iterations; i++) {
|
||||
PList candidate = lists[i%numLists];
|
||||
candidate.addLast(String.valueOf(i), payload);
|
||||
PListEntry entry = candidate.getFirst();
|
||||
assertTrue(candidate.remove(String.valueOf(i)));
|
||||
Thread.currentThread().setName("ALRF:"+candidate.getName());
|
||||
synchronized (plistLocks(candidate)) {
|
||||
candidate.addLast(String.valueOf(i), payload);
|
||||
PListEntry entry = candidate.getFirst();
|
||||
assertTrue(candidate.remove(String.valueOf(i)));
|
||||
}
|
||||
}
|
||||
} catch (Exception error) {
|
||||
error.printStackTrace();
|
||||
exceptions.add(error);
|
||||
} finally {
|
||||
Thread.currentThread().setName(threadName);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -244,7 +273,7 @@ public class PListTest {
|
|||
|
||||
final int numThreads = 20;
|
||||
final int iterations = 2000;
|
||||
ExecutorService executor = Executors.newFixedThreadPool(100);
|
||||
executor = Executors.newFixedThreadPool(100);
|
||||
for (int i=0; i<numThreads; i++) {
|
||||
new Job(i, PListTest.TaskType.ADD, iterations).run();
|
||||
}
|
||||
|
@ -333,7 +362,7 @@ public class PListTest {
|
|||
}
|
||||
|
||||
LOG.info("parallel add and remove");
|
||||
ExecutorService executor = Executors.newFixedThreadPool(numLists*2);
|
||||
executor = Executors.newFixedThreadPool(numLists*2);
|
||||
for (int i=0; i<numLists*2; i++) {
|
||||
executor.execute(new Job(i, i>=numLists ? PListTest.TaskType.ADD : PListTest.TaskType.REMOVE, iterations));
|
||||
}
|
||||
|
@ -344,7 +373,72 @@ public class PListTest {
|
|||
assertTrue("no exceptions", exceptions.isEmpty());
|
||||
}
|
||||
|
||||
enum TaskType {CREATE, DELETE, ADD, REMOVE, ITERATE}
|
||||
// for non determinant issues, increasing this may help diagnose
|
||||
final int numRepeats = 1;
|
||||
|
||||
@Test
|
||||
public void testRepeatStressWithCache() throws Exception {
|
||||
for (int i=0; i<numRepeats;i++) {
|
||||
do_testConcurrentAddIterateRemove(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatStressWithOutCache() throws Exception {
|
||||
for (int i=0; i<numRepeats;i++) {
|
||||
do_testConcurrentAddIterateRemove(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void do_testConcurrentAddIterateRemove(boolean enablePageCache) throws Exception {
|
||||
File directory = store.getDirectory();
|
||||
store.stop();
|
||||
IOHelper.mkdirs(directory);
|
||||
IOHelper.deleteChildren(directory);
|
||||
store = new PListStore();
|
||||
store.setIndexEnablePageCaching(enablePageCache);
|
||||
store.setIndexPageSize(2*1024);
|
||||
store.setDirectory(directory);
|
||||
store.start();
|
||||
|
||||
final int iterations = 5000;
|
||||
final int numLists = 50;
|
||||
|
||||
LOG.info("create");
|
||||
for (int i=0; i<numLists;i++) {
|
||||
new Job(i, PListTest.TaskType.CREATE, iterations).run();
|
||||
}
|
||||
|
||||
LOG.info("fill");
|
||||
for (int i=0; i<numLists;i++) {
|
||||
new Job(i, PListTest.TaskType.ADD, iterations).run();
|
||||
}
|
||||
|
||||
LOG.info("parallel add and remove");
|
||||
executor = Executors.newFixedThreadPool(400);
|
||||
final int numProducer = 5;
|
||||
final int numConsumer = 50;
|
||||
for (int i=0; i<numLists; i++) {
|
||||
for (int j=0; j<numProducer; j++) {
|
||||
executor.execute(new Job(i, PListTest.TaskType.ADD, iterations*2));
|
||||
}
|
||||
for (int k=0;k<numConsumer; k++) {
|
||||
executor.execute(new Job(i, TaskType.ITERATE_REMOVE, iterations/4));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=numLists; i<numLists*10; i++) {
|
||||
executor.execute(new Job(i, PListTest.TaskType.ADD, iterations));
|
||||
}
|
||||
|
||||
executor.shutdown();
|
||||
LOG.info("wait for parallel work to complete");
|
||||
boolean shutdown = executor.awaitTermination(60*60, TimeUnit.SECONDS);
|
||||
assertTrue("test did not timeout ", shutdown);
|
||||
assertTrue("no exceptions", exceptions.isEmpty());
|
||||
}
|
||||
|
||||
enum TaskType {CREATE, DELETE, ADD, REMOVE, ITERATE, ITERATE_REMOVE}
|
||||
|
||||
class Job implements Runnable {
|
||||
|
||||
|
@ -360,54 +454,104 @@ public class PListTest {
|
|||
|
||||
@Override
|
||||
public void run() {
|
||||
final String threadName = Thread.currentThread().getName();
|
||||
try {
|
||||
PList plist = null;
|
||||
switch (task) {
|
||||
case CREATE:
|
||||
plist = store.getPList("List-" + id);
|
||||
Thread.currentThread().setName("C:"+id);
|
||||
plist = store.getPList(String.valueOf(id));
|
||||
LOG.info("Job-" + id + ", CREATE");
|
||||
break;
|
||||
case DELETE:
|
||||
store.removePList("List-" + id);
|
||||
Thread.currentThread().setName("D:"+id);
|
||||
store.removePList(String.valueOf(id));
|
||||
break;
|
||||
case ADD:
|
||||
plist = store.getPList("List-" + id);
|
||||
Thread.currentThread().setName("A:"+id);
|
||||
plist = store.getPList(String.valueOf(id));
|
||||
|
||||
for (int j = 0; j < iterations; j++) {
|
||||
plist.addLast(idSeed + "id" + j, payload);
|
||||
if (j > 0 && j % (iterations / 2) == 0) {
|
||||
LOG.info("Job-" + id + ", Done: " + j);
|
||||
synchronized (plistLocks(plist)) {
|
||||
plist.addLast ("PL>" + id + idSeed + "-" + j, payload);
|
||||
}
|
||||
}
|
||||
LOG.info("Job-" + id + ", Add, done: " + iterations);
|
||||
break;
|
||||
case REMOVE:
|
||||
plist = store.getPList("List-" + id);
|
||||
Thread.currentThread().setName("R:"+id);
|
||||
plist = store.getPList(String.valueOf(id));
|
||||
synchronized (plistLocks(plist)) {
|
||||
|
||||
for (int j = iterations -1; j >= 0; j--) {
|
||||
plist.remove(idSeed + "id" + j);
|
||||
if (j > 0 && j % (iterations / 2) == 0) {
|
||||
LOG.info("Job-" + id + " Done remove: " + j);
|
||||
for (int j = iterations -1; j >= 0; j--) {
|
||||
plist.remove("PL>" + id + idSeed + "-" + j);
|
||||
if (j > 0 && j % (iterations / 2) == 0) {
|
||||
LOG.info("Job-" + id + " Done remove: " + j);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ITERATE:
|
||||
plist = store.getPList("List-" + id);
|
||||
Thread.currentThread().setName("I:"+id);
|
||||
plist = store.getPList(String.valueOf(id));
|
||||
|
||||
Iterator<PListEntry> iterator = plist.iterator();
|
||||
PListEntry element = null;
|
||||
while (iterator.hasNext()) {
|
||||
element = iterator.next();
|
||||
synchronized (plistLocks(plist)) {
|
||||
Iterator<PListEntry> iterator = plist.iterator();
|
||||
PListEntry element = null;
|
||||
while (iterator.hasNext()) {
|
||||
element = iterator.next();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ITERATE_REMOVE:
|
||||
Thread.currentThread().setName("IRM:"+id);
|
||||
plist = store.getPList(String.valueOf(id));
|
||||
|
||||
int removeCount = 0;
|
||||
synchronized (plistLocks(plist)) {
|
||||
|
||||
Iterator<PListEntry> removeIterator = plist.iterator();
|
||||
PListEntry v = null;
|
||||
|
||||
while (removeIterator.hasNext()) {
|
||||
v = removeIterator.next();
|
||||
removeIterator.remove();
|
||||
if (removeCount++ > iterations) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.info("Job-" + id + " Done remove: " + removeCount);
|
||||
break;
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
exceptions.add(e);
|
||||
executor.shutdownNow();
|
||||
} finally {
|
||||
Thread.currentThread().setName(threadName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<PList, Object> locks = new HashMap<PList, Object>();
|
||||
private Object plistLocks(PList plist) {
|
||||
Object lock = null;
|
||||
synchronized (locks) {
|
||||
if (locks.containsKey(plist)) {
|
||||
lock = locks.get(plist);
|
||||
} else {
|
||||
lock = new Object();
|
||||
locks.put(plist, lock);
|
||||
}
|
||||
}
|
||||
return lock;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
File directory = new File("target/test/PlistDB");
|
||||
|
@ -421,7 +565,7 @@ public class PListTest {
|
|||
store = new PListStore();
|
||||
store.setDirectory(directory);
|
||||
store.start();
|
||||
plist = store.getPList("test");
|
||||
plist = store.getPList("main");
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.apache.kahadb.util.Marshaller;
|
|||
public class ListIndex<Key,Value> implements Index<Key,Value> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ListIndex.class);
|
||||
|
||||
public final static long NOT_SET = -1;
|
||||
protected PageFile pageFile;
|
||||
protected long headPageId;
|
||||
protected long tailPageId;
|
||||
|
@ -40,7 +40,7 @@ public class ListIndex<Key,Value> implements Index<Key,Value> {
|
|||
|
||||
protected AtomicBoolean loaded = new AtomicBoolean();
|
||||
|
||||
private final ListNode.Marshaller<Key, Value> marshaller = new ListNode.Marshaller<Key, Value>(this);
|
||||
private ListNode.NodeMarshaller<Key, Value> marshaller;
|
||||
private Marshaller<Key> keyMarshaller;
|
||||
private Marshaller<Value> valueMarshaller;
|
||||
|
||||
|
@ -49,7 +49,7 @@ public class ListIndex<Key,Value> implements Index<Key,Value> {
|
|||
|
||||
public ListIndex(PageFile pageFile, long headPageId) {
|
||||
this.pageFile = pageFile;
|
||||
this.headPageId = headPageId;
|
||||
setHeadPageId(headPageId);
|
||||
}
|
||||
|
||||
synchronized public void load(Transaction tx) throws IOException {
|
||||
|
@ -61,20 +61,23 @@ public class ListIndex<Key,Value> implements Index<Key,Value> {
|
|||
if( valueMarshaller == null ) {
|
||||
throw new IllegalArgumentException("The value marshaller must be set before loading the ListIndex");
|
||||
}
|
||||
|
||||
final Page<ListNode<Key,Value>> p = tx.load(headPageId, null);
|
||||
|
||||
marshaller = new ListNode.NodeMarshaller<Key, Value>(keyMarshaller, valueMarshaller);
|
||||
final Page<ListNode<Key,Value>> p = tx.load(getHeadPageId(), null);
|
||||
if( p.getType() == Page.PAGE_FREE_TYPE ) {
|
||||
// Need to initialize it..
|
||||
ListNode<Key, Value> root = createNode(p);
|
||||
storeNode(tx, root, true);
|
||||
tailPageId = headPageId = p.getPageId();
|
||||
setHeadPageId(p.getPageId());
|
||||
setTailPageId(getHeadPageId());
|
||||
} else {
|
||||
ListNode<Key, Value> node = loadNode(tx, headPageId);
|
||||
ListNode<Key, Value> node = loadNode(tx, getHeadPageId());
|
||||
setTailPageId(getHeadPageId());
|
||||
size.addAndGet(node.size(tx));
|
||||
while (node.getNext() != -1) {
|
||||
while (node.getNext() != NOT_SET ) {
|
||||
node = loadNode(tx, node.getNext());
|
||||
size.addAndGet(node.size(tx));
|
||||
tailPageId = node.getPageId();
|
||||
setTailPageId(node.getPageId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,11 +89,11 @@ public class ListIndex<Key,Value> implements Index<Key,Value> {
|
|||
}
|
||||
|
||||
protected ListNode<Key,Value> getHead(Transaction tx) throws IOException {
|
||||
return loadNode(tx, headPageId);
|
||||
return loadNode(tx, getHeadPageId());
|
||||
}
|
||||
|
||||
protected ListNode<Key,Value> getTail(Transaction tx) throws IOException {
|
||||
return loadNode(tx, tailPageId);
|
||||
return loadNode(tx, getTailPageId());
|
||||
}
|
||||
|
||||
synchronized public boolean containsKey(Transaction tx, Key key) throws IOException {
|
||||
|
@ -201,25 +204,23 @@ public class ListIndex<Key,Value> implements Index<Key,Value> {
|
|||
Page<ListNode<Key,Value>> page = tx.load(pageId, marshaller);
|
||||
ListNode<Key, Value> node = page.get();
|
||||
node.setPage(page);
|
||||
node.setContainingList(this);
|
||||
return node;
|
||||
}
|
||||
|
||||
ListNode<Key,Value> createNode(Page<ListNode<Key,Value>> page) throws IOException {
|
||||
ListNode<Key,Value> node = new ListNode<Key,Value>(this);
|
||||
ListNode<Key,Value> node = new ListNode<Key,Value>();
|
||||
node.setPage(page);
|
||||
page.set(node);
|
||||
node.setContainingList(this);
|
||||
return node;
|
||||
}
|
||||
|
||||
ListNode<Key,Value> createNode(Transaction tx) throws IOException {
|
||||
Page<ListNode<Key,Value>> page = tx.load(tx.<Object>allocate(1).getPageId(), null);
|
||||
ListNode<Key,Value> node = new ListNode<Key,Value>(this);
|
||||
node.setPage(page);
|
||||
page.set(node);
|
||||
return node;
|
||||
public ListNode<Key,Value> createNode(Transaction tx) throws IOException {
|
||||
return createNode(tx.<ListNode<Key,Value>>load(tx.<ListNode<Key,Value>>allocate().getPageId(), null));
|
||||
}
|
||||
|
||||
void storeNode(Transaction tx, ListNode<Key,Value> node, boolean overflow) throws IOException {
|
||||
public void storeNode(Transaction tx, ListNode<Key,Value> node, boolean overflow) throws IOException {
|
||||
tx.store(node.getPage(), marshaller, overflow);
|
||||
}
|
||||
|
||||
|
@ -257,6 +258,10 @@ public class ListIndex<Key,Value> implements Index<Key,Value> {
|
|||
this.tailPageId = tailPageId;
|
||||
}
|
||||
|
||||
public long getTailPageId() {
|
||||
return tailPageId;
|
||||
}
|
||||
|
||||
public long size() {
|
||||
return size.get();
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.apache.kahadb.page.Page;
|
|||
import org.apache.kahadb.page.Transaction;
|
||||
import org.apache.kahadb.util.LinkedNode;
|
||||
import org.apache.kahadb.util.LinkedNodeList;
|
||||
import org.apache.kahadb.util.Marshaller;
|
||||
import org.apache.kahadb.util.VariableMarshaller;
|
||||
|
||||
/**
|
||||
|
@ -35,22 +36,24 @@ import org.apache.kahadb.util.VariableMarshaller;
|
|||
public final class ListNode<Key,Value> {
|
||||
private final static boolean ADD_FIRST = true;
|
||||
private final static boolean ADD_LAST = false;
|
||||
private final static long NOT_SET = -1;
|
||||
|
||||
// The index that this node is part of.
|
||||
private final ListIndex<Key,Value> index;
|
||||
private ListIndex<Key,Value> containingList;
|
||||
|
||||
// The page associated with this node
|
||||
private Page<ListNode<Key,Value>> page;
|
||||
|
||||
protected LinkedNodeList<KeyValueEntry<Key, Value>> entries = new LinkedNodeList<KeyValueEntry<Key, Value>>();
|
||||
private LinkedNodeList<KeyValueEntry<Key, Value>> entries = new LinkedNodeList<KeyValueEntry<Key, Value>>() {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PageId:" + page.getPageId() + ", index:" + containingList + super.toString();
|
||||
}
|
||||
};
|
||||
|
||||
// The next page after this one.
|
||||
private long next = NOT_SET;
|
||||
private long next = ListIndex.NOT_SET;
|
||||
|
||||
public int size(Transaction tx) {
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
static final class KeyValueEntry<Key, Value> extends LinkedNode<KeyValueEntry<Key, Value>> implements Entry<Key, Value>
|
||||
{
|
||||
|
@ -83,11 +86,13 @@ public final class ListNode<Key,Value> {
|
|||
private final class ListNodeIterator implements Iterator<ListNode<Key,Value>> {
|
||||
|
||||
private final Transaction tx;
|
||||
private final ListIndex<Key,Value> index;
|
||||
ListNode<Key,Value> nextEntry;
|
||||
|
||||
private ListNodeIterator(Transaction tx, ListNode<Key,Value> current) throws IOException {
|
||||
private ListNodeIterator(Transaction tx, ListNode<Key,Value> current) {
|
||||
this.tx = tx;
|
||||
nextEntry = current;
|
||||
index = current.getContainingList();
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
|
@ -96,8 +101,8 @@ public final class ListNode<Key,Value> {
|
|||
|
||||
public ListNode<Key,Value> next() {
|
||||
ListNode<Key,Value> current = nextEntry;
|
||||
if( nextEntry !=null ) {
|
||||
if (nextEntry.next != NOT_SET) {
|
||||
if( current !=null ) {
|
||||
if (current.next != ListIndex.NOT_SET) {
|
||||
try {
|
||||
nextEntry = index.loadNode(tx, current.next);
|
||||
} catch (IOException unexpected) {
|
||||
|
@ -120,64 +125,96 @@ public final class ListNode<Key,Value> {
|
|||
private final class ListIterator implements Iterator<Entry<Key, Value>> {
|
||||
|
||||
private final Transaction tx;
|
||||
ListNode<Key,Value> current, prev;
|
||||
private final ListIndex<Key,Value> targetList;
|
||||
ListNode<Key,Value> currentNode, previousNode;
|
||||
KeyValueEntry<Key, Value> nextEntry;
|
||||
KeyValueEntry<Key, Value> toRemove;
|
||||
KeyValueEntry<Key, Value> entryToRemove;
|
||||
|
||||
private ListIterator(Transaction tx, ListNode<Key,Value> current, long nextIndex) throws IOException {
|
||||
private ListIterator(Transaction tx, ListNode<Key,Value> current, long start) {
|
||||
this.tx = tx;
|
||||
this.current = current;
|
||||
this.currentNode = current;
|
||||
this.targetList = current.getContainingList();
|
||||
nextEntry = current.entries.getHead();
|
||||
if (nextIndex > 0 && nextEntry != null) {
|
||||
for (long i=0; i<nextIndex; i++) {
|
||||
nextEntry = nextEntry.getNext();
|
||||
if (nextEntry == null) {
|
||||
if (!nextFromNextListNode())
|
||||
throw new NoSuchElementException("Index out of range: " + nextIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start > 0) {
|
||||
moveToRequestedStart(start);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean nextFromNextListNode() {
|
||||
boolean haveNext = false;
|
||||
if (current.getNext() != NOT_SET) {
|
||||
private void moveToRequestedStart(final long start) {
|
||||
long count = 0;
|
||||
while (hasNext() && count < start) {
|
||||
next();
|
||||
count++;
|
||||
}
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException("Index " + start + " out of current range: " + count);
|
||||
}
|
||||
}
|
||||
|
||||
private KeyValueEntry<Key, Value> getFromNextNode() {
|
||||
KeyValueEntry<Key, Value> result = null;
|
||||
if (currentNode.getNext() != ListIndex.NOT_SET) {
|
||||
try {
|
||||
prev = current;
|
||||
current = index.loadNode(tx, current.getNext());
|
||||
previousNode = currentNode;
|
||||
currentNode = targetList.loadNode(tx, currentNode.getNext());
|
||||
} catch (IOException unexpected) {
|
||||
NoSuchElementException e = new NoSuchElementException(unexpected.getLocalizedMessage());
|
||||
e.initCause(unexpected);
|
||||
throw e;
|
||||
}
|
||||
nextEntry = current.entries.getHead();
|
||||
haveNext = nextEntry != null;
|
||||
result = currentNode.entries.getHead();
|
||||
}
|
||||
return haveNext;
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return nextEntry !=null || nextFromNextListNode();
|
||||
if (nextEntry == null) {
|
||||
nextEntry = getFromNextNode();
|
||||
}
|
||||
return nextEntry != null;
|
||||
}
|
||||
|
||||
public Entry<Key, Value> next() {
|
||||
if( nextEntry !=null ) {
|
||||
toRemove = nextEntry;
|
||||
nextEntry=toRemove.getNext();
|
||||
return toRemove;
|
||||
entryToRemove = nextEntry;
|
||||
nextEntry = entryToRemove.getNext();
|
||||
return entryToRemove;
|
||||
} else {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
if (toRemove == null) {
|
||||
throw new IllegalStateException("can only remove once, call next again");
|
||||
if (entryToRemove == null) {
|
||||
throw new IllegalStateException("can only remove once, call hasNext();next() again");
|
||||
}
|
||||
try {
|
||||
doRemove(tx, current, prev, toRemove);
|
||||
index.onRemove();
|
||||
toRemove = null;
|
||||
entryToRemove.unlink();
|
||||
entryToRemove = null;
|
||||
ListNode<Key,Value> toRemoveNode = null;
|
||||
if (currentNode.entries.isEmpty()) {
|
||||
// may need to free this node
|
||||
if (currentNode.isHead() && currentNode.isTail()) {
|
||||
// store empty list
|
||||
} else if (currentNode.isHead()) {
|
||||
// new head
|
||||
toRemoveNode = currentNode;
|
||||
nextEntry = getFromNextNode();
|
||||
targetList.setHeadPageId(currentNode.getPageId());
|
||||
} else if (currentNode.isTail()) {
|
||||
toRemoveNode = currentNode;
|
||||
previousNode.setNext(ListIndex.NOT_SET);
|
||||
previousNode.store(tx);
|
||||
targetList.setTailPageId(previousNode.getPageId());
|
||||
}
|
||||
}
|
||||
targetList.onRemove();
|
||||
|
||||
if (toRemoveNode != null) {
|
||||
tx.free(toRemoveNode.getPage());
|
||||
} else {
|
||||
currentNode.store(tx);
|
||||
}
|
||||
} catch (IOException unexpected) {
|
||||
IllegalStateException e = new IllegalStateException(unexpected.getLocalizedMessage());
|
||||
e.initCause(unexpected);
|
||||
|
@ -192,11 +229,13 @@ public final class ListNode<Key,Value> {
|
|||
* @param <Key>
|
||||
* @param <Value>
|
||||
*/
|
||||
static public class Marshaller<Key,Value> extends VariableMarshaller<ListNode<Key,Value>> {
|
||||
private final ListIndex<Key,Value> index;
|
||||
static public final class NodeMarshaller<Key,Value> extends VariableMarshaller<ListNode<Key,Value>> {
|
||||
private final Marshaller<Key> keyMarshaller;
|
||||
private final Marshaller<Value> valueMarshaller;
|
||||
|
||||
public Marshaller(ListIndex<Key,Value> index) {
|
||||
this.index = index;
|
||||
public NodeMarshaller(Marshaller<Key> keyMarshaller, Marshaller<Value> valueMarshaller) {
|
||||
this.keyMarshaller = keyMarshaller;
|
||||
this.valueMarshaller = valueMarshaller;
|
||||
}
|
||||
|
||||
public void writePayload(ListNode<Key,Value> node, DataOutput os) throws IOException {
|
||||
|
@ -209,58 +248,31 @@ public final class ListNode<Key,Value> {
|
|||
os.writeShort(count);
|
||||
KeyValueEntry<Key, Value> entry = node.entries.getHead();
|
||||
while (entry != null) {
|
||||
index.getKeyMarshaller().writePayload((Key) entry.getKey(), os);
|
||||
index.getValueMarshaller().writePayload((Value) entry.getValue(), os);
|
||||
keyMarshaller.writePayload((Key) entry.getKey(), os);
|
||||
valueMarshaller.writePayload((Value) entry.getValue(), os);
|
||||
entry = entry.getNext();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ListNode<Key,Value> readPayload(DataInput is) throws IOException {
|
||||
ListNode<Key,Value> node = new ListNode<Key,Value>(index);
|
||||
ListNode<Key,Value> node = new ListNode<Key,Value>();
|
||||
node.next = is.readLong();
|
||||
final short size = is.readShort();
|
||||
for (short i = 0; i < size; i++) {
|
||||
node.entries.addLast(
|
||||
new KeyValueEntry(index.getKeyMarshaller().readPayload(is),
|
||||
index.getValueMarshaller().readPayload(is)));
|
||||
new KeyValueEntry(keyMarshaller.readPayload(is),
|
||||
valueMarshaller.readPayload(is)));
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
public ListNode(ListIndex<Key, Value> index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
private void doRemove(final Transaction tx, final ListNode current, final ListNode prev, KeyValueEntry<Key, Value> entry) throws IOException {
|
||||
entry.unlink();
|
||||
if (current.entries.isEmpty()) {
|
||||
if (current.getPageId() == index.getHeadPageId()) {
|
||||
if (current.getNext() != NOT_SET) {
|
||||
// new head
|
||||
index.setHeadPageId(current.getNext());
|
||||
tx.free(current.getPageId());
|
||||
} else {
|
||||
// store current in empty state
|
||||
store(tx);
|
||||
}
|
||||
} else {
|
||||
// need to unlink the node
|
||||
prev.setNext(current.next);
|
||||
index.storeNode(tx, prev, false);
|
||||
tx.free(current.getPageId());
|
||||
}
|
||||
} else {
|
||||
store(tx);
|
||||
}
|
||||
}
|
||||
|
||||
public Value put(Transaction tx, Key key, Value value) throws IOException {
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException("Key cannot be null");
|
||||
}
|
||||
entries.addLast(new KeyValueEntry(key, value));
|
||||
entries.addLast(new KeyValueEntry<Key, Value>(key, value));
|
||||
store(tx, ADD_LAST);
|
||||
return null;
|
||||
}
|
||||
|
@ -269,14 +281,14 @@ public final class ListNode<Key,Value> {
|
|||
if (key == null) {
|
||||
throw new IllegalArgumentException("Key cannot be null");
|
||||
}
|
||||
entries.addFirst(new KeyValueEntry(key, value));
|
||||
entries.addFirst(new KeyValueEntry<Key, Value>(key, value));
|
||||
store(tx, ADD_FIRST);
|
||||
return null;
|
||||
}
|
||||
|
||||
private void store(Transaction tx, boolean addFirst) throws IOException {
|
||||
try {
|
||||
index.storeNode(tx, this, false);
|
||||
getContainingList().storeNode(tx, this, false);
|
||||
} catch ( Transaction.PageOverflowIOException e ) {
|
||||
// If we get an overflow
|
||||
split(tx, addFirst);
|
||||
|
@ -284,23 +296,23 @@ public final class ListNode<Key,Value> {
|
|||
}
|
||||
|
||||
private void store(Transaction tx) throws IOException {
|
||||
index.storeNode(tx, this, false);
|
||||
getContainingList().storeNode(tx, this, false);
|
||||
}
|
||||
|
||||
private void split(Transaction tx, boolean isAddFirst) throws IOException {
|
||||
ListNode<Key, Value> extension = index.createNode(tx);
|
||||
ListNode<Key, Value> extension = getContainingList().createNode(tx);
|
||||
if (isAddFirst) {
|
||||
// head keeps the first entry, insert extension with the rest
|
||||
extension.setNext(this.getNext());
|
||||
this.setNext(extension.getPageId());
|
||||
extension.setEntries(entries.getHead().splitAfter());
|
||||
} else {
|
||||
index.setTailPageId(extension.getPageId());
|
||||
this.setNext(extension.getPageId());
|
||||
extension.setEntries(entries.getTail().getPrevious().splitAfter());
|
||||
getContainingList().setTailPageId(extension.getPageId());
|
||||
}
|
||||
index.storeNode(tx, this, false);
|
||||
extension.store(tx, isAddFirst);
|
||||
store(tx);
|
||||
}
|
||||
|
||||
// called after a split
|
||||
|
@ -308,7 +320,7 @@ public final class ListNode<Key,Value> {
|
|||
this.entries = list;
|
||||
}
|
||||
|
||||
public Value get(Transaction tx, Key key) throws IOException {
|
||||
public Value get(Transaction tx, Key key) {
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException("Key cannot be null");
|
||||
}
|
||||
|
@ -324,15 +336,15 @@ public final class ListNode<Key,Value> {
|
|||
return result;
|
||||
}
|
||||
|
||||
public boolean isEmpty(final Transaction tx) throws IOException {
|
||||
public boolean isEmpty(final Transaction tx) {
|
||||
return entries.isEmpty();
|
||||
}
|
||||
|
||||
public Entry<Key,Value> getFirst(Transaction tx) throws IOException {
|
||||
public Entry<Key,Value> getFirst(Transaction tx) {
|
||||
return entries.getHead();
|
||||
}
|
||||
|
||||
public Entry<Key,Value> getLast(Transaction tx) throws IOException {
|
||||
public Entry<Key,Value> getLast(Transaction tx) {
|
||||
return entries.getTail();
|
||||
}
|
||||
|
||||
|
@ -353,7 +365,7 @@ public final class ListNode<Key,Value> {
|
|||
tx.free(this.getPageId());
|
||||
}
|
||||
|
||||
public boolean contains(Transaction tx, Key key) throws IOException {
|
||||
public boolean contains(Transaction tx, Key key) {
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException("Key cannot be null");
|
||||
}
|
||||
|
@ -392,10 +404,30 @@ public final class ListNode<Key,Value> {
|
|||
public void setNext(long next) {
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
|
||||
public void setContainingList(ListIndex<Key, Value> list) {
|
||||
this.containingList = list;
|
||||
}
|
||||
|
||||
public ListIndex<Key,Value> getContainingList() {
|
||||
return containingList;
|
||||
}
|
||||
|
||||
public boolean isHead() {
|
||||
return getPageId() == containingList.getHeadPageId();
|
||||
}
|
||||
|
||||
public boolean isTail() {
|
||||
return getPageId() == containingList.getTailPageId();
|
||||
}
|
||||
|
||||
public int size(Transaction tx) {
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[ListNode(" + page.getPageId() + "->" + next + ") " + entries.toString() + "]";
|
||||
return "[ListNode(" + (page != null ? page.getPageId() + "->" + next : "null") + ")[" + entries.size() + "]]";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ public class PageFile {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[PageWrite:"+page.getPageId()+"]";
|
||||
return "[PageWrite:"+page.getPageId()+ "-" + page.getType() + "]";
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -827,9 +827,7 @@ public class PageFile {
|
|||
|
||||
public void freePage(long pageId) {
|
||||
freeList.add(pageId);
|
||||
if( enablePageCaching ) {
|
||||
pageCache.remove(pageId);
|
||||
}
|
||||
removeFromCache(pageId);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -932,9 +930,9 @@ public class PageFile {
|
|||
}
|
||||
}
|
||||
|
||||
void removeFromCache(Page page) {
|
||||
void removeFromCache(long pageId) {
|
||||
if (enablePageCaching) {
|
||||
pageCache.remove(page.getPageId());
|
||||
pageCache.remove(pageId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -129,8 +129,6 @@ public class Transaction implements Iterable<Page> {
|
|||
* if the PageFile is not loaded
|
||||
*/
|
||||
public <T> Page<T> allocate(int count) throws IOException {
|
||||
// TODO: we need to track allocated pages so that they can be returned if the
|
||||
// transaction gets rolled back.
|
||||
Page<T> rc = pageFile.allocate(count);
|
||||
allocateList.add(new Sequence(rc.getPageId(), rc.getPageId()+count-1));
|
||||
return rc;
|
||||
|
|
|
@ -197,7 +197,7 @@ public class LinkedNode<T extends LinkedNode<T>> {
|
|||
|
||||
public void linkToHead(LinkedNodeList<T> target) {
|
||||
if (list != null) {
|
||||
throw new IllegalArgumentException("This node is already linked to a node");
|
||||
throw new IllegalArgumentException("This node is already linked to a list");
|
||||
}
|
||||
|
||||
if (target.head == null) {
|
||||
|
|
|
@ -204,7 +204,7 @@ public class SequenceSet extends LinkedNodeList<Sequence> {
|
|||
return sequence;
|
||||
}
|
||||
if (sequence.range() > count ) {
|
||||
Sequence rc = new Sequence(sequence.first, sequence.first+count);
|
||||
Sequence rc = new Sequence(sequence.first, sequence.first+count-1);
|
||||
sequence.first+=count;
|
||||
return rc;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue