Lock ordering made right

This commit is contained in:
leventov
2015-07-23 21:55:49 +03:00
parent 6e269323f8
commit 6580017b9c
6 changed files with 100 additions and 59 deletions
+34 -24
View File
@@ -1926,18 +1926,22 @@ Using nested contexts:
ChronicleMap<Integer, Set<Integer>> graph, int source, int target) {
if (source == target)
throw new IllegalArgumentException("loops are forbidden");
ExternalMapQueryContext<Integer, Set<Integer>, ?> sourceC = graph.queryContext(source);
ExternalMapQueryContext<Integer, Set<Integer>, ?> targetC = graph.queryContext(target);
// order for consistent lock acquisition => avoid dead lock
if (target < source) {
int t = source;
source = target;
target = t;
if (sourceC.segmentIndex() <= targetC.segmentIndex()) {
return innerAddEdge(source, sourceC, target, targetC);
} else {
return innerAddEdge(target, targetC, source, sourceC);
}
}
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> sc = graph.queryContext(source)) {
sc.updateLock().lock();
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> tc =
graph.queryContext(target)) {
private static boolean innerAddEdge(
int source, ExternalMapQueryContext<Integer, Set<Integer>, ?> sourceContext,
int target, ExternalMapQueryContext<Integer, Set<Integer>, ?> targetContext) {
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> sc = sourceContext) {
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> tc = targetContext) {
sc.updateLock().lock();
tc.updateLock().lock();
MapEntry<Integer, Set<Integer>> sEntry = sc.entry();
if (sEntry != null) {
@@ -2007,23 +2011,29 @@ Using nested contexts:
public static boolean removeEdge(
ChronicleMap<Integer, Set<Integer>> graph, int source, int target) {
ExternalMapQueryContext<Integer, Set<Integer>, ?> sourceC = graph.queryContext(source);
ExternalMapQueryContext<Integer, Set<Integer>, ?> targetC = graph.queryContext(target);
// order for consistent lock acquisition => avoid dead lock
if (target < source) {
int t = source;
source = target;
target = t;
if (sourceC.segmentIndex() <= targetC.segmentIndex()) {
return innerRemoveEdge(source, sourceC, target, targetC);
} else {
return innerRemoveEdge(target, targetC, source, sourceC);
}
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> sc = graph.queryContext(source)) {
sc.updateLock().lock();
MapEntry<Integer, Set<Integer>> sEntry = sc.entry();
if (sEntry == null)
return false;
Set<Integer> sNeighbours = sEntry.value().get();
if (!sNeighbours.remove(target))
return false;
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> tc =
graph.queryContext(target)) {
}
private static boolean innerRemoveEdge(
int source, ExternalMapQueryContext<Integer, Set<Integer>, ?> sourceContext,
int target, ExternalMapQueryContext<Integer, Set<Integer>, ?> targetContext) {
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> sc = sourceContext) {
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> tc = targetContext) {
sc.updateLock().lock();
MapEntry<Integer, Set<Integer>> sEntry = sc.entry();
if (sEntry == null)
return false;
Set<Integer> sNeighbours = sEntry.value().get();
if (!sNeighbours.remove(target))
return false;
tc.updateLock().lock();
MapEntry<Integer, Set<Integer>> tEntry = tc.entry();
if (tEntry == null)
@@ -7,6 +7,7 @@ import net.openhft.chronicle.map.MapMethods;
import net.openhft.chronicle.map.MapQueryContext;
import net.openhft.chronicle.set.SetEntryOperations;
import net.openhft.chronicle.set.SetQueryContext;
import org.jetbrains.annotations.Nullable;
/**
* Context of {@link ChronicleHash} operations with <i>individual keys</i>.
@@ -137,7 +138,17 @@ import net.openhft.chronicle.set.SetQueryContext;
* @param <K> the hash key type
* @see ChronicleHash#queryContext(Object)
*/
public interface HashQueryContext<K> extends HashContext<K>, InterProcessReadWriteUpdateLock {
public interface HashQueryContext<K> extends HashContext<K>, SegmentLock {
/**
* Returns the index of the accessed segment, where the queried key is located (or to which
* the key is going to be put).
*
* <p>This index might also be used as the {@code InterProcessReadWriteUpdateLock} identifier,
* because {@code ChronicleHashes} has per-segment locks.
*/
@Override
int segmentIndex();
/**
* Returns the queried key as a {@code Data}.
@@ -160,5 +171,6 @@ public interface HashQueryContext<K> extends HashContext<K>, InterProcessReadWri
* @implNote Might acquire {@link #readLock} before searching for the key, if the context
* is not locked yet.
*/
@Nullable
HashAbsentEntry<K> absentEntry();
}
@@ -1,5 +1,7 @@
/*
* Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com
* Copyright 2015 Higher Frequency Trading
*
* http://www.higherfrequencytrading.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,11 +19,19 @@
package net.openhft.chronicle.hash;
import net.openhft.chronicle.hash.locks.InterProcessReadWriteUpdateLock;
import org.jetbrains.annotations.NotNull;
public interface Segment {
long index();
@NotNull
InterProcessReadWriteUpdateLock lock();
/**
* {@link InterProcessReadWriteUpdateLock} of a segment in {@code ChronicleHash}.
*
* <p>In Chronicle-Map off-heap design, locks (and concurrency) are per-segment.
*
* @see ChronicleHashBuilder#minSegments(int)
* @see ChronicleHashBuilder#actualSegments(int)
*/
public interface SegmentLock extends InterProcessReadWriteUpdateLock {
/**
* Returns the index of the accessed segment.
*/
int segmentIndex();
}
@@ -18,6 +18,7 @@
package net.openhft.chronicle.hash.impl.stage.entry;
import net.openhft.chronicle.hash.SegmentLock;
import net.openhft.chronicle.hash.impl.BigSegmentHeader;
import net.openhft.chronicle.hash.impl.LocalLockState;
import net.openhft.chronicle.hash.impl.SegmentHeader;
@@ -38,7 +39,7 @@ import org.jetbrains.annotations.NotNull;
import static net.openhft.chronicle.hash.impl.LocalLockState.UNLOCKED;
@Staged
public abstract class SegmentStages implements InterProcessReadWriteUpdateLock {
public abstract class SegmentStages implements SegmentLock {
@StageRef Chaining chaining;
@StageRef public VanillaChronicleHashHolder<?, ?, ?> hh;
@@ -48,7 +49,6 @@ public abstract class SegmentStages implements InterProcessReadWriteUpdateLock {
public void initTheSegmentIndex(int segmentIndex) {
this.segmentIndex = segmentIndex;
}
// "SegHeader" because stage-generator doesn't support stage names - one is a prefix of another
@@ -27,8 +27,6 @@ import org.jetbrains.annotations.Nullable;
* (like during {@code get()}, {@code put()}, etc., opposed to <i>bulk</i> operations).
* This is the main context type of {@link MapMethods} and {@link MapRemoteOperations}.
*
*
*
* @param <K> the map key type
* @param <V> the map value type
* @param <R> the return type of {@link MapEntryOperations} specialized for the queried map
@@ -37,7 +37,7 @@ public class NestedContextsTest {
public void nestedContextsTest() throws ExecutionException, InterruptedException {
ChronicleMap<Integer, Set<Integer>> graph = ChronicleMapBuilder
.of(Integer.class, (Class<Set<Integer>>) (Class) Set.class)
.actualSegments(1)
.actualSegments(2)
.create();
addEdge(graph, 1, 2);
@@ -81,17 +81,22 @@ public class NestedContextsTest {
ChronicleMap<Integer, Set<Integer>> graph, int source, int target) {
if (source == target)
throw new IllegalArgumentException("loops are forbidden");
ExternalMapQueryContext<Integer, Set<Integer>, ?> sourceC = graph.queryContext(source);
ExternalMapQueryContext<Integer, Set<Integer>, ?> targetC = graph.queryContext(target);
// order for consistent lock acquisition => avoid dead lock
if (target < source) {
int t = source;
source = target;
target = t;
if (sourceC.segmentIndex() <= targetC.segmentIndex()) {
return innerAddEdge(source, sourceC, target, targetC);
} else {
return innerAddEdge(target, targetC, source, sourceC);
}
}
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> sc = graph.queryContext(source)) {
sc.updateLock().lock();
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> tc =
graph.queryContext(target)) {
private static boolean innerAddEdge(
int source, ExternalMapQueryContext<Integer, Set<Integer>, ?> sourceContext,
int target, ExternalMapQueryContext<Integer, Set<Integer>, ?> targetContext) {
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> sc = sourceContext) {
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> tc = targetContext) {
sc.updateLock().lock();
tc.updateLock().lock();
MapEntry<Integer, Set<Integer>> sEntry = sc.entry();
if (sEntry != null) {
@@ -161,23 +166,29 @@ public class NestedContextsTest {
public static boolean removeEdge(
ChronicleMap<Integer, Set<Integer>> graph, int source, int target) {
ExternalMapQueryContext<Integer, Set<Integer>, ?> sourceC = graph.queryContext(source);
ExternalMapQueryContext<Integer, Set<Integer>, ?> targetC = graph.queryContext(target);
// order for consistent lock acquisition => avoid dead lock
if (target < source) {
int t = source;
source = target;
target = t;
if (sourceC.segmentIndex() <= targetC.segmentIndex()) {
return innerRemoveEdge(source, sourceC, target, targetC);
} else {
return innerRemoveEdge(target, targetC, source, sourceC);
}
}
private static boolean innerRemoveEdge(
int source, ExternalMapQueryContext<Integer, Set<Integer>, ?> sourceContext,
int target, ExternalMapQueryContext<Integer, Set<Integer>, ?> targetContext) {
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> sc = sourceContext) {
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> tc = targetContext) {
sc.updateLock().lock();
MapEntry<Integer, Set<Integer>> sEntry = sc.entry();
if (sEntry == null)
return false;
Set<Integer> sNeighbours = sEntry.value().get();
if (!sNeighbours.remove(target))
return false;
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> sc = graph.queryContext(source)) {
sc.updateLock().lock();
MapEntry<Integer, Set<Integer>> sEntry = sc.entry();
if (sEntry == null)
return false;
Set<Integer> sNeighbours = sEntry.value().get();
if (!sNeighbours.remove(target))
return false;
try (ExternalMapQueryContext<Integer, Set<Integer>, ?> tc =
graph.queryContext(target)) {
tc.updateLock().lock();
MapEntry<Integer, Set<Integer>> tEntry = tc.entry();
if (tEntry == null)