/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package my.test.mvstore;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.Page;
import org.h2.mvstore.type.DataType;
import org.h2.mvstore.type.ObjectDataType;
public class LockFreeMVMap2<K, V> extends MVMap<K, V> implements Callable<Void> {
private static Merger merger;
static {
merger = new Merger();
merger.start();
}
public static void stopMerger() {
merger.stopMerger();
}
private static class ValueHolder<V> {
final V value;
//final byte tag;
ValueHolder(V value, int tag) {
this.value = value;
//this.tag = (byte) tag;
}
ValueHolder(V value) {
this(value, 0);
}
}
private volatile ConcurrentSkipListMap<K, V> current = new ConcurrentSkipListMap<K, V>();
private volatile ConcurrentSkipListMap<K, ValueHolder<V>> current2 = new ConcurrentSkipListMap<K, ValueHolder<V>>();
private volatile ConcurrentSkipListMap<K, V> merging;
private volatile ConcurrentSkipListMap<K, ValueHolder<V>> merging2 = new ConcurrentSkipListMap<K, ValueHolder<V>>();
private volatile ConcurrentSkipListSet<Object> removed;
public LockFreeMVMap2(DataType keyType, DataType valueType) {
super(keyType, valueType);
merger.addMap(this);
}
@Override
public V put(K key, V value) {
current2.put(key, new ValueHolder<V>(value));
return current.put(key, value);
}
@Override
public V get(Object key) {
V v = current.get(key);
if (v != null)
return v;
ValueHolder<V> vh = current2.get(key);
if (vh != null) {
if (vh.value == null)
return null;
return vh.value;
}
if (merging != null) {
v = merging.get(key);
if (v != null)
return v;
}
v = super.get(key);
if (v != null && removed.contains(key))
return null;
return v;
}
@SuppressWarnings("unchecked")
@Override
public V remove(Object key) {
V v = current.remove(key);
if (v != null)
return v;
ValueHolder<V> vh = current2.remove(key);
if (vh != null) {
if (vh.value == null)
return null;
return vh.value;
}
if (merging != null) {
v = merging.remove(key);
if (v != null)
return v;
}
v = super.get(key);
if (v != null) {
removed.add(key);
current2.put((K) key, new ValueHolder<V>(null));
}
return v;
}
@Override
public Void call() throws Exception {
beginMerge();
return null;
}
private void beginMerge() {
merging = current;
current = new ConcurrentSkipListMap<K, V>();
merging2 = current2;
current2 = new ConcurrentSkipListMap<K, ValueHolder<V>>();
merge();
merging = null;
merging2 = null;
}
private void merge() {
beforeWrite();
long v = writeVersion;
Page p;
Object key;
Object value;
for (Entry<K, V> e : merging.entrySet()) {
p = root.copy(v);
key = e.getKey();
value = e.getValue();
p = splitRootIfNeeded(p, v);
put(p, v, key, value);
newRoot(p);
}
for (Entry<K, ValueHolder<V>> e : merging2.entrySet()) {
key = e.getKey();
value = e.getValue().value;
p = root.copy(v);
if (value != null) {
p = splitRootIfNeeded(p, v);
put(p, v, key, value);
} else {
remove(p, v, key);
if (!p.isLeaf() && p.getTotalCount() == 0) {
p.removePage();
//p = Page.createEmpty(this, p.getVersion());
}
}
newRoot(p);
}
for (Object k : removed) {
p = root.copy(v);
remove(p, v, k);
if (!p.isLeaf() && p.getTotalCount() == 0) {
p.removePage();
//p = Page.createEmpty(this, p.getVersion());
}
newRoot(p);
removed.remove(k);
}
}
public static class Merger extends Thread {
private static final ExecutorService executorService = Executors.newCachedThreadPool();
private volatile boolean isRunning;
private final ArrayList<LockFreeMVMap2<?, ?>> maps = new ArrayList<LockFreeMVMap2<?, ?>>();
private final ArrayList<Future<Void>> futures = new ArrayList<Future<Void>>();
public synchronized void addMap(LockFreeMVMap2<?, ?> map) {
maps.add(map);
}
public Merger() {
super("BTree-Merger");
}
@Override
public void run() {
isRunning = true;
long millis = 5 * 60 * 1000;
while (isRunning) {
try {
sleep(millis);
} catch (InterruptedException e) {
//e.printStackTrace();
}
for (LockFreeMVMap2<?, ?> map : maps) {
futures.add(executorService.submit(map));
}
for (Future<Void> f : futures) {
try {
f.get();
} catch (Exception e) {
//e.printStackTrace();
}
}
futures.clear();
}
}
public void stopMerger() {
isRunning = false;
interrupt();
try {
join();
} catch (InterruptedException e) {
//e.printStackTrace();
}
executorService.shutdown();
}
}
/**
* A builder for this class.
*
* @param <K> the key type
* @param <V> the value type
*/
public static class Builder<K, V> implements MapBuilder<LockFreeMVMap2<K, V>, K, V> {
protected DataType keyType;
protected DataType valueType;
/**
* Create a new builder with the default key and value data types.
*/
public Builder() {
// ignore
}
/**
* Set the key data type.
*
* @param keyType the key type
* @return this
*/
public Builder<K, V> keyType(DataType keyType) {
this.keyType = keyType;
return this;
}
/**
* Set the key data type.
*
* @param valueType the key type
* @return this
*/
public Builder<K, V> valueType(DataType valueType) {
this.valueType = valueType;
return this;
}
@Override
public LockFreeMVMap2<K, V> create() {
if (keyType == null) {
keyType = new ObjectDataType();
}
if (valueType == null) {
valueType = new ObjectDataType();
}
return new LockFreeMVMap2<K, V>(keyType, valueType);
}
}
}