Skip to content

Commit 5bd4a71

Browse files
committed
Add cycle detecting locks, fix Orchid deadlock 2
subgraph/Orchid#10 Conflicts: orchid/src/com/subgraph/orchid/circuits/CircuitIO.java
1 parent a012b83 commit 5bd4a71

File tree

5 files changed

+162
-36
lines changed

5 files changed

+162
-36
lines changed

orchid/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@
114114
<version>4.11</version>
115115
<scope>test</scope>
116116
</dependency>
117+
<dependency>
118+
<groupId>com.google.guava</groupId>
119+
<artifactId>guava</artifactId>
120+
<version>16.0.1</version>
121+
</dependency>
117122
</dependencies>
118123

119124
</project>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.subgraph.orchid;
2+
3+
import com.google.common.util.concurrent.CycleDetectingLockFactory;
4+
5+
import java.util.concurrent.locks.ReentrantLock;
6+
7+
/**
8+
* Created by android on 8/22/14.
9+
*/
10+
public class Threading {
11+
static {
12+
// Default policy goes here. If you want to change this, use one of the static methods before
13+
// instantiating any orchid objects. The policy change will take effect only on new objects
14+
// from that point onwards.
15+
throwOnLockCycles();
16+
}
17+
18+
private static CycleDetectingLockFactory.Policy policy;
19+
public static CycleDetectingLockFactory factory;
20+
21+
public static ReentrantLock lock(String name) {
22+
return factory.newReentrantLock(name);
23+
}
24+
25+
public static void warnOnLockCycles() {
26+
setPolicy(CycleDetectingLockFactory.Policies.WARN);
27+
}
28+
29+
public static void throwOnLockCycles() {
30+
setPolicy(CycleDetectingLockFactory.Policies.THROW);
31+
}
32+
33+
public static void ignoreLockCycles() {
34+
setPolicy(CycleDetectingLockFactory.Policies.DISABLED);
35+
}
36+
37+
public static void setPolicy(CycleDetectingLockFactory.Policy policy) {
38+
Threading.policy = policy;
39+
factory = CycleDetectingLockFactory.newInstance(policy);
40+
}
41+
42+
public static CycleDetectingLockFactory.Policy getPolicy() {
43+
return policy;
44+
}
45+
}

orchid/src/com/subgraph/orchid/circuits/CircuitIO.java

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.concurrent.BlockingQueue;
1010
import java.util.concurrent.LinkedBlockingQueue;
1111
import java.util.concurrent.TimeUnit;
12+
import java.util.concurrent.locks.ReentrantLock;
1213
import java.util.logging.Level;
1314
import java.util.logging.Logger;
1415

@@ -18,6 +19,7 @@
1819
import com.subgraph.orchid.ConnectionIOException;
1920
import com.subgraph.orchid.RelayCell;
2021
import com.subgraph.orchid.Stream;
22+
import com.subgraph.orchid.Threading;
2123
import com.subgraph.orchid.TorException;
2224
import com.subgraph.orchid.circuits.cells.CellImpl;
2325
import com.subgraph.orchid.circuits.cells.RelayCellImpl;
@@ -36,11 +38,10 @@ public class CircuitIO implements DashboardRenderable {
3638
private final BlockingQueue<RelayCell> relayCellResponseQueue;
3739
private final BlockingQueue<Cell> controlCellResponseQueue;
3840
private final Map<Integer, StreamImpl> streamMap;
39-
private final Object relaySendLock = new Object();
41+
private final ReentrantLock streamLock = Threading.lock("stream");
42+
private final ReentrantLock relaySendLock = Threading.lock("relaySend");
4043

41-
/** LOCKING: streamMap */
4244
private boolean isMarkedForClose;
43-
/** LOCKING: streamMap */
4445
private boolean isClosed;
4546

4647
CircuitIO(CircuitImpl circuit, Connection connection, int circuitId) {
@@ -171,14 +172,17 @@ private void processRelayDataCell(RelayCell cell) {
171172
}
172173
}
173174

174-
synchronized(streamMap) {
175+
streamLock.lock();
176+
try {
175177
final StreamImpl stream = streamMap.get(cell.getStreamId());
176178
// It's not unusual for the stream to not be found. For example, if a RELAY_CONNECTED arrives after
177179
// the client has stopped waiting for it, the stream will never be tracked and eventually the edge node
178180
// will send a RELAY_END for this stream.
179181
if(stream != null) {
180182
stream.addInputCell(cell);
181183
}
184+
} finally {
185+
streamLock.unlock();
182186
}
183187
}
184188

@@ -187,7 +191,8 @@ RelayCell createRelayCell(int relayCommand, int streamId, CircuitNode targetNode
187191
}
188192

189193
void sendRelayCellTo(RelayCell cell, CircuitNode targetNode) {
190-
synchronized(relaySendLock) {
194+
relaySendLock.lock();
195+
try {
191196
logRelayCell("Sending: ", cell);
192197
cell.setLength();
193198
targetNode.updateForwardDigest(cell);
@@ -200,6 +205,8 @@ void sendRelayCellTo(RelayCell cell, CircuitNode targetNode) {
200205
targetNode.waitForSendWindowAndDecrement();
201206

202207
sendCell(cell);
208+
} finally {
209+
relaySendLock.unlock();
203210
}
204211
}
205212

@@ -236,20 +243,26 @@ void sendCell(Cell cell) {
236243

237244
void markForClose() {
238245
boolean shouldClose;
239-
synchronized (streamMap) {
246+
streamLock.lock();
247+
try {
240248
if(isMarkedForClose) {
241249
return;
242250
}
243251
isMarkedForClose = true;
244252
shouldClose = streamMap.isEmpty();
253+
} finally {
254+
streamLock.unlock();
245255
}
246256
if(shouldClose)
247257
closeCircuit();
248258
}
249259

250260
boolean isMarkedForClose() {
251-
synchronized (streamMap) {
261+
streamLock.lock();
262+
try {
252263
return isMarkedForClose;
264+
} finally {
265+
streamLock.unlock();
253266
}
254267
}
255268

@@ -276,7 +289,8 @@ private void processCircuitSendme(RelayCell cell) {
276289
}
277290

278291
void destroyCircuit() {
279-
synchronized(streamMap) {
292+
streamLock.lock();
293+
try {
280294
if(isClosed) {
281295
return;
282296
}
@@ -287,31 +301,42 @@ void destroyCircuit() {
287301
s.close();
288302
}
289303
isClosed = true;
304+
} finally {
305+
streamLock.unlock();
290306
}
291307
}
292308

293309
StreamImpl createNewStream(boolean autoclose) {
294-
synchronized(streamMap) {
310+
streamLock.lock();
311+
try {
295312
final int streamId = circuit.getStatus().nextStreamId();
296313
final StreamImpl stream = new StreamImpl(circuit, circuit.getFinalCircuitNode(), streamId, autoclose);
297314
streamMap.put(streamId, stream);
298315
return stream;
316+
} finally {
317+
streamLock.unlock();
299318
}
300319
}
301320

302321
void removeStream(StreamImpl stream) {
303322
boolean shouldClose;
304-
synchronized(streamMap) {
323+
streamLock.lock();
324+
try {
305325
streamMap.remove(stream.getStreamId());
306326
shouldClose = streamMap.isEmpty() && isMarkedForClose;
327+
} finally {
328+
streamLock.unlock();
307329
}
308330
if(shouldClose)
309331
closeCircuit();
310332
}
311333

312334
List<Stream> getActiveStreams() {
313-
synchronized (streamMap) {
335+
streamLock.lock();
336+
try {
314337
return new ArrayList<Stream>(streamMap.values());
338+
} finally {
339+
streamLock.unlock();
315340
}
316341
}
317342

orchid/src/com/subgraph/orchid/circuits/CircuitManagerImpl.java

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.concurrent.ScheduledExecutorService;
1414
import java.util.concurrent.TimeUnit;
1515
import java.util.concurrent.TimeoutException;
16+
import java.util.concurrent.locks.ReentrantLock;
1617

1718
import com.subgraph.orchid.Circuit;
1819
import com.subgraph.orchid.CircuitBuildHandler;
@@ -29,6 +30,7 @@
2930
import com.subgraph.orchid.Router;
3031
import com.subgraph.orchid.Stream;
3132
import com.subgraph.orchid.StreamConnectFailedException;
33+
import com.subgraph.orchid.Threading;
3234
import com.subgraph.orchid.Tor;
3335
import com.subgraph.orchid.TorConfig;
3436
import com.subgraph.orchid.circuits.guards.EntryGuards;
@@ -62,6 +64,7 @@ interface CircuitFilter {
6264
private final TorInitializationTracker initializationTracker;
6365
private final CircuitPathChooser pathChooser;
6466
private final HiddenServiceManager hiddenServiceManager;
67+
private final ReentrantLock lock = Threading.lock("circuitManager");
6568

6669
public CircuitManagerImpl(TorConfig config, DirectoryDownloaderImpl directoryDownloader, Directory directory, ConnectionCache connectionCache, TorInitializationTracker initializationTracker) {
6770
this.config = config;
@@ -87,13 +90,19 @@ public void startBuildingCircuits() {
8790
scheduledExecutor.scheduleAtFixedRate(circuitCreationTask, 0, 1000, TimeUnit.MILLISECONDS);
8891
}
8992

90-
public synchronized void stopBuildingCircuits(boolean killCircuits) {
91-
scheduledExecutor.shutdownNow();
92-
if(killCircuits) {
93-
List<CircuitImpl> circuits = new ArrayList<CircuitImpl>(activeCircuits);
94-
for(CircuitImpl c: circuits) {
95-
c.destroyCircuit();
93+
public void stopBuildingCircuits(boolean killCircuits) {
94+
lock.lock();
95+
96+
try {
97+
scheduledExecutor.shutdownNow();
98+
if (killCircuits) {
99+
List<CircuitImpl> circuits = new ArrayList<CircuitImpl>(activeCircuits);
100+
for (CircuitImpl c : circuits) {
101+
c.destroyCircuit();
102+
}
96103
}
104+
} finally {
105+
lock.unlock();
97106
}
98107
}
99108

@@ -114,8 +123,10 @@ void removeActiveCircuit(CircuitImpl circuit) {
114123
}
115124
}
116125

117-
synchronized int getActiveCircuitCount() {
118-
return activeCircuits.size();
126+
int getActiveCircuitCount() {
127+
synchronized (activeCircuits) {
128+
return activeCircuits.size();
129+
}
119130
}
120131

121132
Set<Circuit> getPendingCircuits() {
@@ -126,17 +137,29 @@ public boolean filter(Circuit circuit) {
126137
});
127138
}
128139

129-
synchronized int getPendingCircuitCount() {
130-
return getPendingCircuits().size();
140+
int getPendingCircuitCount() {
141+
lock.lock();
142+
143+
try {
144+
return getPendingCircuits().size();
145+
} finally {
146+
lock.unlock();
147+
}
131148
}
132149

133150
Set<Circuit> getCircuitsByFilter(CircuitFilter filter) {
134151
final Set<Circuit> result = new HashSet<Circuit>();
152+
final Set<CircuitImpl> circuits = new HashSet<CircuitImpl>();
153+
135154
synchronized (activeCircuits) {
136-
for(CircuitImpl c: activeCircuits) {
137-
if(filter == null || filter.filter(c)) {
138-
result.add(c);
139-
}
155+
// the filter might lock additional objects, causing a deadlock, so don't
156+
// call it inside the monitor
157+
circuits.addAll(activeCircuits);
158+
}
159+
160+
for(CircuitImpl c: circuits) {
161+
if(filter == null || filter.filter(c)) {
162+
result.add(c);
140163
}
141164
}
142165
return result;

0 commit comments

Comments
 (0)