/*
* Copyright 2017 Kjell Winblad (kjellwinblad@gmail.com, http://winsh.me).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* (see java/src/trees/lockbased/catreeutils/LICENSE)
*/
package trees.lockbased;
import java.util.Random;
import java.util.concurrent.locks.*;
import java.util.*;
import java.io.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.LockSupport;
import java.lang.reflect.Field;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import trees.lockbased.catreeutils.*;
import contention.abstractions.CompositionalMap;
/**
* Implementation of a Contention Adapting Search Tree (CA tree). CA
* trees are described in the following publication:
*
* Konstantinos Sagonas and Kjell Winblad.
* Contention Adapting Search Trees
* In Proceedings of the Fourteenth International Symposium on Parallel
* and Distributed Computing, Limassol, Cyprus, July 2015.
*
* This implementation uses AVL trees as the sequential data structure
* in the CA trees base nodes and has the optimizations described in
* sections V.A. and V.C. of the above mentioned publication.
*
* More information about CA trees and more CA tree implementations
* (e.g. implementations with range update and range query support)
* can be found at
* <a href="http://www.it.uu.se/research/group/languages/software/ca_tree">http://www.it.uu.se/research/group/languages/software/ca_tree</a>.
*
* @author Kjell Winblad
*/
public class CATreeMapAVL<K, V> extends AbstractMap<K,V> implements CompositionalMap<K, V> {
// ====== Costants =======
private final static boolean DEBUG = false;
private static final int LOCK_FREE_SPONTANIUS_PROBABLITY = 50000000;
private static final int OPTIMISTIC_RETRIES = 1;
// =======================
// ====== Fields =========
private volatile Object root = new DualLFCASAVLTreeMapSTD<K, V>();
private final Comparator<? super K> comparator;
// =======================
// ====== Declarations ===
static private final class RouteNode{
private volatile Object left;
private volatile Object right;
private final Object key;
final ReentrantLock lock = new ReentrantLock();
boolean valid = true;
private static final AtomicReferenceFieldUpdater<RouteNode, Object> leftUpdater =
AtomicReferenceFieldUpdater.newUpdater(RouteNode.class, Object.class, "left");
private static final AtomicReferenceFieldUpdater<RouteNode, Object> rightUpdater =
AtomicReferenceFieldUpdater.newUpdater(RouteNode.class, Object.class, "right");
public RouteNode(Object key, Object left, Object right){
this.key = key;
leftUpdater.lazySet(this, left);
rightUpdater.lazySet(this, right);
}
public Object getKey(){
return this.key;
}
public Object getLeft(){
return this.left;
}
public void setLeft(Object o){
leftUpdater.lazySet(this, o);
}
public Object getRight(){
return this.right;
}
public void setRight(Object o){
rightUpdater.lazySet(this, o);
}
public String toString(){
return "R(" + this.key + ")";
}
}
// ==== Functions for debuging and testing =====
void printDotHelper(Object n, PrintStream writeTo, int level){
try{
if(n instanceof RouteNode){
RouteNode node = (RouteNode)n;
//LEFT
writeTo.print("\"" + node + level+" \"");
writeTo.print(" -> ");
writeTo.print("\"" + node.getLeft() + (level +1)+" \"");
writeTo.println(";");
//RIGHT
writeTo.print("\"" + node + level+" \"");
writeTo.print(" -> ");
writeTo.print("\"" + node.getRight() + (level +1)+" \"");
writeTo.println(";");
printDotHelper(node.getLeft(), writeTo, level +1);
printDotHelper(node.getRight(), writeTo, level +1);
}
}catch(Exception e){
e.printStackTrace();
}
}
void printDot(Object node, String fileName){
try{
lockAll();
Process p = new ProcessBuilder("dot", "-Tsvg")
.redirectOutput(ProcessBuilder.Redirect.to(new File(fileName + ".svg")))
.start();
PrintStream writeTo = new PrintStream(p.getOutputStream());
writeTo.print("digraph G{\n");
writeTo.print(" graph [ordering=\"out\"];\n");
printDotHelper(node, writeTo, 0);
writeTo.print("}\n");
writeTo.close();
p.waitFor();
unlockAll();
}catch(Exception e){
e.printStackTrace();
}
}
//=============================================
//=== Constructors ============================
public CATreeMapAVL() {
comparator = null;
}
public CATreeMapAVL(Comparator<? super K> comparator) {
this.comparator = comparator;
}
//=== Helper Methods =======================
private int sizeHelper(Object currentNode){
if(currentNode == null){
return 0;
}else{
if(currentNode instanceof RouteNode){
RouteNode r = (RouteNode)currentNode;
int sizeSoFar = sizeHelper(r.getLeft());
return sizeSoFar + sizeHelper(r.getRight());
}else {
@SuppressWarnings("unchecked")
DualLFCASAVLTreeMapSTD<K,V> b = (DualLFCASAVLTreeMapSTD<K,V>)currentNode;
return b.size();
}
}
}
final private Object getBaseNodeUsingComparator(Object keyParam){
Object currNode = root;
@SuppressWarnings("unchecked")
K key = (K)keyParam;
while (currNode instanceof RouteNode) {
RouteNode currNodeR = (RouteNode)currNode;
@SuppressWarnings("unchecked")
K routeKey = (K)(currNodeR.getKey());
if (comparator.compare(key, routeKey) < 0) {
currNode = currNodeR.getLeft();
}else {
currNode = currNodeR.getRight();
}
}
return currNode;
}
final private Object getBaseNode(Object keyParam){
Object currNode = root;
if(comparator != null){
return getBaseNodeUsingComparator(keyParam);
}else{
@SuppressWarnings("unchecked")
Comparable<? super K> key = (Comparable<? super K>) keyParam;
while (currNode instanceof RouteNode) {
RouteNode currNodeR = (RouteNode)currNode;
@SuppressWarnings("unchecked")
K routeKey = (K)(currNodeR.getKey());
if (key.compareTo(routeKey) < 0) {
currNode = currNodeR.getLeft();
} else {
currNode = currNodeR.getRight();
}
}
return currNode;
}
}
final private void highContentionSplit(DualLFCASAVLTreeMapSTD<K, V> baseNode){
if(baseNode.getRoot() == null ||
(baseNode.getRoot().getLeft() == null &&
baseNode.getRoot().getRight() == null)){
baseNode.resetStatistics();//Fast path out if nrOfElem <= 1
return;
}
RouteNode parent = (RouteNode)baseNode.getParent();
Object[] writeBackSplitKey = new Object[1];
@SuppressWarnings("unchecked")
SplitableAndJoinableMap<K,V>[] writeBackRightTree = new SplitableAndJoinableMap[1];
@SuppressWarnings("unchecked")
DualLFCASAVLTreeMapSTD<K,V> leftTree = (DualLFCASAVLTreeMapSTD<K,V>)baseNode.split(writeBackSplitKey, writeBackRightTree);
if(leftTree == null){
baseNode.resetStatistics();
return;
}
@SuppressWarnings("unchecked")
K splitKey = (K)writeBackSplitKey[0];
DualLFCASAVLTreeMapSTD<K,V> rightTree = (DualLFCASAVLTreeMapSTD<K,V>)writeBackRightTree[0];
RouteNode newRoute = new RouteNode(splitKey, leftTree, rightTree);
leftTree.setParent(newRoute);
rightTree.setParent(newRoute);
if (parent == null) {
root = newRoute;
}else {
if (parent.getLeft() == baseNode){
parent.setLeft(newRoute);
}else{
parent.setRight(newRoute);
}
}
baseNode.invalidate();
}
final private DualLFCASAVLTreeMapSTD<K,V> leftmostBaseNode(Object node){
Object currentNode = node;
while(currentNode instanceof RouteNode){
RouteNode r = (RouteNode)currentNode;
currentNode = r.getLeft();
}
@SuppressWarnings("unchecked")
DualLFCASAVLTreeMapSTD<K,V> toReturn = (DualLFCASAVLTreeMapSTD<K,V>)currentNode;
return toReturn;
}
final private DualLFCASAVLTreeMapSTD<K,V> rightmostBaseNode(Object node){
Object currentNode = node;
while(currentNode instanceof RouteNode){
RouteNode r = (RouteNode)currentNode;
currentNode = r.getRight();
}
@SuppressWarnings("unchecked")
DualLFCASAVLTreeMapSTD<K,V> toReturn = (DualLFCASAVLTreeMapSTD<K,V>)currentNode;
return toReturn;
}
final private RouteNode parentOfUsingComparator(RouteNode node){
@SuppressWarnings("unchecked")
K key = (K)node.getKey();
Object prevNode = null;
Object currNode = root;
while (currNode != node) {
@SuppressWarnings("unchecked")
RouteNode currNodeR = (RouteNode)currNode;
@SuppressWarnings("unchecked")
K routeKey = (K)(currNodeR.getKey());
prevNode = currNode;
if (comparator.compare(key, routeKey) < 0) {
currNode = currNodeR.getLeft();
} else {
currNode = currNodeR.getRight();
}
}
return (RouteNode)prevNode;
}
final private RouteNode parentOf(RouteNode node){
if(comparator != null){
return parentOfUsingComparator(node);
}else{
@SuppressWarnings("unchecked")
Comparable<? super K> key = (Comparable<? super K>) node.getKey();
Object prevNode = null;
Object currNode = root;
while (currNode != node) {
RouteNode currNodeR = (RouteNode)currNode;
@SuppressWarnings("unchecked")
K routeKey = (K)(currNodeR.getKey());
prevNode = currNode;
if (key.compareTo(routeKey) < 0) {
currNode = currNodeR.getLeft();
} else {
currNode = currNodeR.getRight();
}
}
return (RouteNode)prevNode;
}
}
final private void lowContentionJoin(DualLFCASAVLTreeMapSTD<K, V> baseNode){
RouteNode parent = (RouteNode)baseNode.getParent();
if(parent == null){
baseNode.resetStatistics();
}else if (parent.getLeft() == baseNode) {
DualLFCASAVLTreeMapSTD<K,V> neighborBase = leftmostBaseNode(parent.getRight());
if (!neighborBase.tryLock()) {
baseNode.resetStatistics();
return;
} else if (!neighborBase.isValid()) {
neighborBase.unlock();
baseNode.resetStatistics();
return;
} else {
@SuppressWarnings("unchecked")
DualLFCASAVLTreeMapSTD<K,V> newNeighborBase = (DualLFCASAVLTreeMapSTD<K,V>)baseNode.join(neighborBase);
parent.lock.lock();
RouteNode gparent = null; // gparent = grandparent
do {
if (gparent != null){
gparent.lock.unlock();
}
gparent = parentOf(parent);
if (gparent != null){
gparent.lock.lock();
}
} while (gparent != null && !gparent.valid);
if (gparent == null) {
root = parent.getRight();
} else if(gparent.getLeft() == parent){
gparent.setLeft(parent.getRight());
} else {
gparent.setRight(parent.getRight());
}
parent.valid = false;
parent.lock.unlock();
if (gparent != null){
gparent.lock.unlock();
}
//Unlink is done!
//Put in joined base node
RouteNode neighborBaseParent = null;
if(parent.getRight() == neighborBase){
neighborBaseParent = gparent;
}else{
neighborBaseParent = (RouteNode)neighborBase.getParent();
}
newNeighborBase.setParent(neighborBaseParent);
if(neighborBaseParent == null){
root = newNeighborBase;
} else if (neighborBaseParent.getLeft() == neighborBase) {
neighborBaseParent.setLeft(newNeighborBase);
} else {
neighborBaseParent.setRight(newNeighborBase);
}
neighborBase.invalidate();
neighborBase.unlock();
baseNode.invalidate();
}
} else { /* This case is symmetric to the previous one */
DualLFCASAVLTreeMapSTD<K,V> neighborBase = rightmostBaseNode(parent.getLeft());//ff
if (!neighborBase.tryLock()) {//ff
baseNode.resetStatistics();//ff
} else if (!neighborBase.isValid()) {//ff
neighborBase.unlock();//ff
baseNode.resetStatistics();//ff
} else {
@SuppressWarnings("unchecked")
DualLFCASAVLTreeMapSTD<K,V> newNeighborBase = (DualLFCASAVLTreeMapSTD<K,V>)neighborBase.join(baseNode);//ff
parent.lock.lock();//ff
RouteNode gparent = null; // gparent = grandparent //ff
do {//ff
if (gparent != null){//ff
gparent.lock.unlock();//ff
}//ff
gparent = parentOf(parent);//ff
if (gparent != null){//ff
gparent.lock.lock();//ff
}//ff
} while (gparent != null && !gparent.valid);//ff
if (gparent == null) {//ff
root = parent.getLeft();//ff
} else if(gparent.getLeft() == parent){//ff
gparent.setLeft(parent.getLeft());//ff
} else {//ff
gparent.setRight(parent.getLeft());//ff
}//ff
parent.valid = false;
parent.lock.unlock();//ff
if (gparent != null){//ff
gparent.lock.unlock();//ff
}//ff
RouteNode neighborBaseParent = null;
if(parent.getLeft() == neighborBase){
neighborBaseParent = gparent;
}else{
neighborBaseParent = (RouteNode)neighborBase.getParent();
}
newNeighborBase.setParent(neighborBaseParent);//ff
if(neighborBaseParent == null){//ff
root = newNeighborBase;//ff
} else if (neighborBaseParent.getLeft() == neighborBase) {//ff
neighborBaseParent.setLeft(newNeighborBase);//ff
} else {//ff
neighborBaseParent.setRight(newNeighborBase);//ff
}//ff
neighborBase.invalidate();//ff
neighborBase.unlock();//ff
baseNode.invalidate();//ff
}
}
}
private final void adaptIfNeeded(DualLFCASAVLTreeMapSTD<K,V> baseNode){
if (baseNode.isHighContentionLimitReached()){
if(baseNode.oneElement()){
baseNode.transformToLockFree();
}else {
highContentionSplit(baseNode);
}
} else if (baseNode.isLowContentionLimitReached()) {
lowContentionJoin(baseNode);
}
}
private final boolean lfMapForUs(STDAVLNodeDCAS<K,V> lfMap, K key){
if(comparator != null){
return comparator.compare(key, lfMap.getKey()) == 0;
}else{
@SuppressWarnings("unchecked")
Comparable<? super K> keyComp = (Comparable<? super K>) key;
return keyComp.compareTo((K)lfMap.getKey() ) == 0;
}
}
final private void lockAllHelper(Object currentNode, ArrayList<DualLFCASAVLTreeMapSTD> lockedSoFar){
try {
if(currentNode != null){
if(currentNode instanceof RouteNode){
RouteNode r = (RouteNode)currentNode;
lockAllHelper(r.getLeft(), lockedSoFar);
lockAllHelper(r.getRight(), lockedSoFar);
}else {
DualLFCASAVLTreeMapSTD b = (DualLFCASAVLTreeMapSTD)currentNode;
b.lock();
if(b.isValid()){
lockedSoFar.add(b);
}else{
//Retry
b.unlock();
for(DualLFCASAVLTreeMapSTD m : lockedSoFar){
m.unlock();
}
throw new RuntimeException();
}
}
}
} catch (RuntimeException e){
//Retry
lockAllHelper(root, new ArrayList<DualLFCASAVLTreeMapSTD>());
}
}
final private void unlockAllHelper(Object currentNode) {
if(currentNode != null){
if(currentNode instanceof RouteNode) {
RouteNode b = (RouteNode)currentNode;
unlockAllHelper(b.getLeft());
unlockAllHelper(b.getRight());
} else {
DualLFCASAVLTreeMapSTD b = (DualLFCASAVLTreeMapSTD)currentNode;
b.unlock();
}
}
}
final private void lockAll(){
lockAllHelper(root, new ArrayList<DualLFCASAVLTreeMapSTD>());
}
final private void unlockAll(){
unlockAllHelper(root);
}
final private void addAllToList(Object currentNode, ArrayList<Map.Entry<K, V>> list){
if(currentNode == null){
return;
}else{
if(currentNode instanceof RouteNode){
@SuppressWarnings("unchecked")
RouteNode r = (RouteNode)currentNode;
addAllToList(r.getLeft(), list);
addAllToList(r.getRight(), list);
}else {
@SuppressWarnings("unchecked")
DualLFCASAVLTreeMapSTD<K, V> b = (DualLFCASAVLTreeMapSTD<K, V>)currentNode;
b.addAllToList(list);
return;
}
}
}
// ==== Public Interface =====
public int size(){
lockAll();
int size = sizeHelper(root);
unlockAll();
return size;
}
public boolean isEmpty(){
return size() == 0;
}
public boolean containsKey(Object key){
return get(key) != null;
}
public V get(Object key){
outerloop:
while(true){
@SuppressWarnings("unchecked")
DualLFCASAVLTreeMapSTD<K,V> baseNode = (DualLFCASAVLTreeMapSTD<K,V>)getBaseNode(key);
for(int i = 0; i < OPTIMISTIC_RETRIES; i++){
long optimisticReadToken = baseNode.getOptimisticReadToken();
V result = null;
if(optimisticReadToken != 0L){
if (baseNode.isValid() == false) {
continue outerloop; // retry
}
STDAVLNodeDCAS<K,V> lfMap = baseNode.getLockFreeMap();
if(lfMap!=null){
@SuppressWarnings("unchecked")
K k = (K)key;
if(lfMapForUs(lfMap, k)){
result = lfMap.getValue();
}
}else{
result = baseNode.get(key);
}
}
if(baseNode.validateOptimisticReadToken(optimisticReadToken)){
return result;
}
}
if(baseNode.lockIfNotLockFree()){
if (baseNode.isValid()) {
V result = baseNode.get(key);
baseNode.addToContentionStatistics();
adaptIfNeeded(baseNode);
baseNode.unlock();
return result;
}else{
baseNode.unlock();
}
}
}
}
public V put(K key, V value){
while(true){
@SuppressWarnings("unchecked")
DualLFCASAVLTreeMapSTD<K, V> baseNode = (DualLFCASAVLTreeMapSTD<K, V>)getBaseNode(key);
//First check if we can do it in a lock free way
long optimisticReadToken = 0;
STDAVLNodeDCAS<K,V> lfMap = null;
if((lfMap = baseNode.getLockFreeMap()) != null &&
0L != (optimisticReadToken = baseNode.getOptimisticReadToken()) &&
(lfMap = baseNode.getLockFreeMap()) != null &&
lfMapForUs(lfMap, key)){
if(!baseNode.isValid()){
continue;
}
baseNode.indicateWriteStart();
if(baseNode.validateOptimisticReadToken(optimisticReadToken)){
V result = lfMap.getAndSetValue(value);
baseNode.indicateWriteEnd();
return result;
}else{
baseNode.indicateWriteEnd();
}
}
// We could not do it without taking a lock
if(baseNode.lockIfNotLockFreeWithKey(key)){
// Check if valid
if (!baseNode.isValid()) {
baseNode.unlock();
continue; // retry
}
// Do the operation
V result = baseNode.put(key, value);
adaptIfNeeded(baseNode);
baseNode.unlock();
return result;
}
}
}
public V putIfAbsent(K key, V value){
while(true){
@SuppressWarnings("unchecked")
DualLFCASAVLTreeMapSTD<K, V> baseNode = (DualLFCASAVLTreeMapSTD<K, V>)getBaseNode(key);
// First check if we can do it without taking a lock
long optimisticReadToken = 0;
STDAVLNodeDCAS<K,V> lfMap = null;
if((lfMap = baseNode.getLockFreeMap()) != null &&
0L != (optimisticReadToken = baseNode.getOptimisticReadToken()) &&
(lfMap = baseNode.getLockFreeMap()) != null &&
lfMapForUs(lfMap, key)){
if(!baseNode.isValid()){
continue;
}
baseNode.indicateWriteStart();
if(baseNode.validateOptimisticReadToken(optimisticReadToken)){
V result = lfMap.getValue();
while(result == null && !lfMap.compareAndSet(null, value)){
result = lfMap.getValue();
}
baseNode.indicateWriteEnd();
return result;
}else{
baseNode.indicateWriteEnd();
}
}
if(baseNode.lockIfNotLockFreeWithKey(key)){
// Check if valid
if (!baseNode.isValid()) {
baseNode.unlock();
continue; // retry
}
// Do the operation
V result = baseNode.putIfAbsent(key, value);
adaptIfNeeded(baseNode);
baseNode.unlock();
return result;
}
}
}
public V remove(Object key){
while(true){
@SuppressWarnings("unchecked")
DualLFCASAVLTreeMapSTD<K,V> baseNode = (DualLFCASAVLTreeMapSTD<K,V>)getBaseNode(key);
// First check if we can do it without taking a lock
long optimisticReadToken = 0;
STDAVLNodeDCAS<K,V> lfMap = null;
if((lfMap = baseNode.getLockFreeMap()) != null &&
0L != (optimisticReadToken = baseNode.getOptimisticReadToken()) &&
(lfMap = baseNode.getLockFreeMap()) != null){
if(!baseNode.isValid()){
continue;
}
baseNode.indicateWriteStart();
if(baseNode.validateOptimisticReadToken(optimisticReadToken)){
@SuppressWarnings("unchecked")
K k = (K)key;
if(lfMapForUs(lfMap, k)){
V result = lfMap.getAndSetValue(null);
baseNode.indicateWriteEnd();
return result;
}else{
baseNode.indicateWriteEnd();
return null;
}
}else{
baseNode.indicateWriteEnd();
}
}
if(baseNode.lockIfNotLockFree()){
// Check if valid
if (baseNode.isValid() == false) {
baseNode.unlock();
continue; // retry
}
// Do the operation
V result = baseNode.remove(key);
adaptIfNeeded(baseNode);
baseNode.unlock();
return result;
}
}
}
public void clear(){
lockAll();
Object oldRoot = root;
root = new DualLFCASAVLTreeMapSTD<K, V>();
unlockAllHelper(oldRoot);
}
public Set<Map.Entry<K, V>> entrySet(){
ArrayList<Map.Entry<K, V>> list = new ArrayList<Map.Entry<K, V>>();
lockAll();
addAllToList(root, list);
unlockAll();
return new HashSet<Map.Entry<K, V>>(list);
}
// ===========================
}