/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, Version
* 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html). Initial Developer: H2 Group
*/
package org.h2.test.store;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import org.h2.dev.store.btree.MVMap;
import org.h2.dev.store.btree.MVStore;
import org.h2.test.TestBase;
import org.h2.util.Task;
/**
* Tests concurrently accessing a tree map store.
*/
public class TestConcurrent extends TestMVStore {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
public void test() throws InterruptedException {
testConcurrentIterate();
testConcurrentWrite();
testConcurrentRead();
}
private void testConcurrentIterate() {
MVStore s = MVStore.open(null, new TestMapFactory());
s.setMaxPageSize(3);
final MVMap<Integer, Integer> map = s.openMap("test");
final int len = 10;
final Random r = new Random();
Task t = new Task() {
@Override
public void call() throws Exception {
while (!stop) {
int x = r.nextInt(len);
if (r.nextBoolean()) {
map.remove(x);
} else {
map.put(x, r.nextInt(100));
}
}
}
};
t.execute();
for (int k = 0; k < 10000; k++) {
Iterator<Integer> it = map.keyIterator(r.nextInt(len));
long old = s.incrementVersion();
s.setRetainVersion(old - 100);
while (map.getVersion() == old) {
Thread.yield();
}
while (it.hasNext()) {
it.next();
}
}
t.get();
s.close();
}
/**
* Test what happens on concurrent write. Concurrent write may corrupt the
* map, so that keys and values may become null.
*/
private void testConcurrentWrite() throws InterruptedException {
final MVStore s = openStore(null);
final MVMap<Integer, Integer> m = s.openMap("data", Integer.class, Integer.class);
final int size = 20;
final Random rand = new Random(1);
Task task = new Task() {
public void call() throws Exception {
while (!stop) {
try {
if (rand.nextBoolean()) {
m.put(rand.nextInt(size), 1);
} else {
m.remove(rand.nextInt(size));
}
m.get(rand.nextInt(size));
} catch (NullPointerException e) {
// ignore
}
}
}
};
task.execute();
Thread.sleep(1);
for (int j = 0; j < 10; j++) {
for (int i = 0; i < 10; i++) {
try {
if (rand.nextBoolean()) {
m.put(rand.nextInt(size), 2);
} else {
m.remove(rand.nextInt(size));
}
m.get(rand.nextInt(size));
} catch (NullPointerException e) {
// ignore
}
}
s.incrementVersion();
Thread.sleep(1);
}
task.get();
// verify the structure is still somewhat usable
for (int x : m.keySet()) {
try {
m.get(x);
} catch (NullPointerException e) {
// ignore
}
}
for (int i = 0; i < size; i++) {
try {
m.get(i);
} catch (NullPointerException e) {
// ignore
}
}
s.close();
}
private void testConcurrentRead() throws InterruptedException {
final MVStore s = openStore(null);
final MVMap<Integer, Integer> m = s.openMap("data", Integer.class, Integer.class);
final int size = 3;
int x = (int) s.getCurrentVersion();
for (int i = 0; i < size; i++) {
m.put(i, x);
}
s.incrementVersion();
Task task = new Task() {
public void call() throws Exception {
while (!stop) {
long v = s.getCurrentVersion() - 1;
Map<Integer, Integer> old = m.openVersion(v);
for (int i = 0; i < size; i++) {
Integer x = old.get(i);
if (x == null || (int) v != x) {
Map<Integer, Integer> old2 = m.openVersion(v);
throw new AssertionError(x + "<>" + v + " at " + i + " " + old2);
}
}
}
}
};
task.execute();
Thread.sleep(1);
for (int j = 0; j < 100; j++) {
x = (int) s.getCurrentVersion();
for (int i = 0; i < size; i++) {
m.put(i, x);
}
s.incrementVersion();
Thread.sleep(1);
}
task.get();
s.close();
}
}