1 //===-- sanitizer_deadlock_detector.h ---------------------------*- C++ -*-===//
3 // This file is distributed under the University of Illinois Open Source
4 // License. See LICENSE.TXT for details.
6 //===----------------------------------------------------------------------===//
8 // This file is a part of Sanitizer runtime.
9 // The deadlock detector maintains a directed graph of lock acquisitions.
10 // When a lock event happens, the detector checks if the locks already held by
11 // the current thread are reachable from the newly acquired lock.
13 // The detector can handle only a fixed amount of simultaneously live locks
14 // (a lock is alive if it has been locked at least once and has not been
15 // destroyed). When the maximal number of locks is reached the entire graph
16 // is flushed and the new lock epoch is started. The node ids from the old
17 // epochs can not be used with any of the detector methods except for
18 // nodeBelongsToCurrentEpoch().
20 // FIXME: this is work in progress, nothing really works yet.
22 //===----------------------------------------------------------------------===//
24 #ifndef SANITIZER_DEADLOCK_DETECTOR_H
25 #define SANITIZER_DEADLOCK_DETECTOR_H
27 #include "sanitizer_common.h"
28 #include "sanitizer_bvgraph.h"
30 namespace __sanitizer
{
32 // Thread-local state for DeadlockDetector.
33 // It contains the locks currently held by the owning thread.
35 class DeadlockDetectorTLS
{
41 n_recursive_locks
= 0;
45 bool empty() const { return bv_
.empty(); }
47 void ensureCurrentEpoch(uptr current_epoch
) {
48 if (epoch_
== current_epoch
) return;
50 epoch_
= current_epoch
;
51 n_recursive_locks
= 0;
55 uptr
getEpoch() const { return epoch_
; }
57 // Returns true if this is the first (non-recursive) acquisition of this lock.
58 bool addLock(uptr lock_id
, uptr current_epoch
, u32 stk
) {
59 // Printf("addLock: %zx %zx stk %u\n", lock_id, current_epoch, stk);
60 CHECK_EQ(epoch_
, current_epoch
);
61 if (!bv_
.setBit(lock_id
)) {
62 // The lock is already held by this thread, it must be recursive.
63 CHECK_LT(n_recursive_locks
, ARRAY_SIZE(recursive_locks
));
64 recursive_locks
[n_recursive_locks
++] = lock_id
;
67 CHECK_LT(n_all_locks_
, ARRAY_SIZE(all_locks_with_contexts_
));
68 // lock_id < BV::kSize, can cast to a smaller int.
69 u32 lock_id_short
= static_cast<u32
>(lock_id
);
70 LockWithContext l
= {lock_id_short
, stk
};
71 all_locks_with_contexts_
[n_all_locks_
++] = l
;
75 void removeLock(uptr lock_id
) {
76 if (n_recursive_locks
) {
77 for (sptr i
= n_recursive_locks
- 1; i
>= 0; i
--) {
78 if (recursive_locks
[i
] == lock_id
) {
80 Swap(recursive_locks
[i
], recursive_locks
[n_recursive_locks
]);
85 // Printf("remLock: %zx %zx\n", lock_id, epoch_);
86 if (!bv_
.clearBit(lock_id
))
87 return; // probably addLock happened before flush
89 for (sptr i
= n_all_locks_
- 1; i
>= 0; i
--) {
90 if (all_locks_with_contexts_
[i
].lock
== static_cast<u32
>(lock_id
)) {
91 Swap(all_locks_with_contexts_
[i
],
92 all_locks_with_contexts_
[n_all_locks_
- 1]);
100 u32
findLockContext(uptr lock_id
) {
101 for (uptr i
= 0; i
< n_all_locks_
; i
++)
102 if (all_locks_with_contexts_
[i
].lock
== static_cast<u32
>(lock_id
))
103 return all_locks_with_contexts_
[i
].stk
;
107 const BV
&getLocks(uptr current_epoch
) const {
108 CHECK_EQ(epoch_
, current_epoch
);
112 uptr
getNumLocks() const { return n_all_locks_
; }
113 uptr
getLock(uptr idx
) const { return all_locks_with_contexts_
[idx
].lock
; }
118 uptr recursive_locks
[64];
119 uptr n_recursive_locks
;
120 struct LockWithContext
{
124 LockWithContext all_locks_with_contexts_
[64];
129 // For deadlock detection to work we need one global DeadlockDetector object
130 // and one DeadlockDetectorTLS object per evey thread.
131 // This class is not thread safe, all concurrent accesses should be guarded
132 // by an external lock.
133 // Most of the methods of this class are not thread-safe (i.e. should
134 // be protected by an external lock) unless explicitly told otherwise.
136 class DeadlockDetector
{
138 typedef BV BitVector
;
140 uptr
size() const { return g_
.size(); }
145 available_nodes_
.clear();
146 recycled_nodes_
.clear();
151 // Allocate new deadlock detector node.
152 // If we are out of available nodes first try to recycle some.
153 // If there is nothing to recycle, flush the graph and increment the epoch.
154 // Associate 'data' (opaque user's object) with the new node.
155 uptr
newNode(uptr data
) {
156 if (!available_nodes_
.empty())
157 return getAvailableNode(data
);
158 if (!recycled_nodes_
.empty()) {
159 // Printf("recycling: n_edges_ %zd\n", n_edges_);
160 for (sptr i
= n_edges_
- 1; i
>= 0; i
--) {
161 if (recycled_nodes_
.getBit(edges_
[i
].from
) ||
162 recycled_nodes_
.getBit(edges_
[i
].to
)) {
163 Swap(edges_
[i
], edges_
[n_edges_
- 1]);
167 CHECK(available_nodes_
.empty());
168 // removeEdgesFrom was called in removeNode.
169 g_
.removeEdgesTo(recycled_nodes_
);
170 available_nodes_
.setUnion(recycled_nodes_
);
171 recycled_nodes_
.clear();
172 return getAvailableNode(data
);
174 // We are out of vacant nodes. Flush and increment the current_epoch_.
175 current_epoch_
+= size();
176 recycled_nodes_
.clear();
177 available_nodes_
.setAll();
180 return getAvailableNode(data
);
183 // Get data associated with the node created by newNode().
184 uptr
getData(uptr node
) const { return data_
[nodeToIndex(node
)]; }
186 bool nodeBelongsToCurrentEpoch(uptr node
) {
187 return node
&& (node
/ size() * size()) == current_epoch_
;
190 void removeNode(uptr node
) {
191 uptr idx
= nodeToIndex(node
);
192 CHECK(!available_nodes_
.getBit(idx
));
193 CHECK(recycled_nodes_
.setBit(idx
));
194 g_
.removeEdgesFrom(idx
);
197 void ensureCurrentEpoch(DeadlockDetectorTLS
<BV
> *dtls
) {
198 dtls
->ensureCurrentEpoch(current_epoch_
);
201 // Returns true if there is a cycle in the graph after this lock event.
202 // Ideally should be called before the lock is acquired so that we can
203 // report a deadlock before a real deadlock happens.
204 bool onLockBefore(DeadlockDetectorTLS
<BV
> *dtls
, uptr cur_node
) {
205 ensureCurrentEpoch(dtls
);
206 uptr cur_idx
= nodeToIndex(cur_node
);
207 return g_
.isReachable(cur_idx
, dtls
->getLocks(current_epoch_
));
210 u32
findLockContext(DeadlockDetectorTLS
<BV
> *dtls
, uptr node
) {
211 return dtls
->findLockContext(nodeToIndex(node
));
214 // Add cur_node to the set of locks held currently by dtls.
215 void onLockAfter(DeadlockDetectorTLS
<BV
> *dtls
, uptr cur_node
, u32 stk
= 0) {
216 ensureCurrentEpoch(dtls
);
217 uptr cur_idx
= nodeToIndex(cur_node
);
218 dtls
->addLock(cur_idx
, current_epoch_
, stk
);
221 // Experimental *racy* fast path function.
222 // Returns true if all edges from the currently held locks to cur_node exist.
223 bool hasAllEdges(DeadlockDetectorTLS
<BV
> *dtls
, uptr cur_node
) {
224 uptr local_epoch
= dtls
->getEpoch();
225 // Read from current_epoch_ is racy.
226 if (cur_node
&& local_epoch
== current_epoch_
&&
227 local_epoch
== nodeToEpoch(cur_node
)) {
228 uptr cur_idx
= nodeToIndexUnchecked(cur_node
);
229 for (uptr i
= 0, n
= dtls
->getNumLocks(); i
< n
; i
++) {
230 if (!g_
.hasEdge(dtls
->getLock(i
), cur_idx
))
238 // Adds edges from currently held locks to cur_node,
239 // returns the number of added edges, and puts the sources of added edges
240 // into added_edges[].
241 // Should be called before onLockAfter.
242 uptr
addEdges(DeadlockDetectorTLS
<BV
> *dtls
, uptr cur_node
, u32 stk
,
244 ensureCurrentEpoch(dtls
);
245 uptr cur_idx
= nodeToIndex(cur_node
);
246 uptr added_edges
[40];
247 uptr n_added_edges
= g_
.addEdges(dtls
->getLocks(current_epoch_
), cur_idx
,
248 added_edges
, ARRAY_SIZE(added_edges
));
249 for (uptr i
= 0; i
< n_added_edges
; i
++) {
250 if (n_edges_
< ARRAY_SIZE(edges_
)) {
251 Edge e
= {(u16
)added_edges
[i
], (u16
)cur_idx
,
252 dtls
->findLockContext(added_edges
[i
]), stk
,
254 edges_
[n_edges_
++] = e
;
256 // Printf("Edge%zd: %u %zd=>%zd in T%d\n",
257 // n_edges_, stk, added_edges[i], cur_idx, unique_tid);
259 return n_added_edges
;
262 bool findEdge(uptr from_node
, uptr to_node
, u32
*stk_from
, u32
*stk_to
,
264 uptr from_idx
= nodeToIndex(from_node
);
265 uptr to_idx
= nodeToIndex(to_node
);
266 for (uptr i
= 0; i
< n_edges_
; i
++) {
267 if (edges_
[i
].from
== from_idx
&& edges_
[i
].to
== to_idx
) {
268 *stk_from
= edges_
[i
].stk_from
;
269 *stk_to
= edges_
[i
].stk_to
;
270 *unique_tid
= edges_
[i
].unique_tid
;
277 // Test-only function. Handles the before/after lock events,
278 // returns true if there is a cycle.
279 bool onLock(DeadlockDetectorTLS
<BV
> *dtls
, uptr cur_node
, u32 stk
= 0) {
280 ensureCurrentEpoch(dtls
);
281 bool is_reachable
= !isHeld(dtls
, cur_node
) && onLockBefore(dtls
, cur_node
);
282 addEdges(dtls
, cur_node
, stk
, 0);
283 onLockAfter(dtls
, cur_node
, stk
);
287 // Handles the try_lock event, returns false.
288 // When a try_lock event happens (i.e. a try_lock call succeeds) we need
289 // to add this lock to the currently held locks, but we should not try to
290 // change the lock graph or to detect a cycle. We may want to investigate
291 // whether a more aggressive strategy is possible for try_lock.
292 bool onTryLock(DeadlockDetectorTLS
<BV
> *dtls
, uptr cur_node
, u32 stk
= 0) {
293 ensureCurrentEpoch(dtls
);
294 uptr cur_idx
= nodeToIndex(cur_node
);
295 dtls
->addLock(cur_idx
, current_epoch_
, stk
);
299 // Returns true iff dtls is empty (no locks are currently held) and we can
300 // add the node to the currently held locks w/o chanding the global state.
301 // This operation is thread-safe as it only touches the dtls.
302 bool onFirstLock(DeadlockDetectorTLS
<BV
> *dtls
, uptr node
, u32 stk
= 0) {
303 if (!dtls
->empty()) return false;
304 if (dtls
->getEpoch() && dtls
->getEpoch() == nodeToEpoch(node
)) {
305 dtls
->addLock(nodeToIndexUnchecked(node
), nodeToEpoch(node
), stk
);
311 // Finds a path between the lock 'cur_node' (currently not held in dtls)
312 // and some currently held lock, returns the length of the path
314 uptr
findPathToLock(DeadlockDetectorTLS
<BV
> *dtls
, uptr cur_node
, uptr
*path
,
316 tmp_bv_
.copyFrom(dtls
->getLocks(current_epoch_
));
317 uptr idx
= nodeToIndex(cur_node
);
318 CHECK(!tmp_bv_
.getBit(idx
));
319 uptr res
= g_
.findShortestPath(idx
, tmp_bv_
, path
, path_size
);
320 for (uptr i
= 0; i
< res
; i
++)
321 path
[i
] = indexToNode(path
[i
]);
323 CHECK_EQ(path
[0], cur_node
);
327 // Handle the unlock event.
328 // This operation is thread-safe as it only touches the dtls.
329 void onUnlock(DeadlockDetectorTLS
<BV
> *dtls
, uptr node
) {
330 if (dtls
->getEpoch() == nodeToEpoch(node
))
331 dtls
->removeLock(nodeToIndexUnchecked(node
));
334 // Tries to handle the lock event w/o writing to global state.
335 // Returns true on success.
336 // This operation is thread-safe as it only touches the dtls
337 // (modulo racy nature of hasAllEdges).
338 bool onLockFast(DeadlockDetectorTLS
<BV
> *dtls
, uptr node
, u32 stk
= 0) {
339 if (hasAllEdges(dtls
, node
)) {
340 dtls
->addLock(nodeToIndexUnchecked(node
), nodeToEpoch(node
), stk
);
346 bool isHeld(DeadlockDetectorTLS
<BV
> *dtls
, uptr node
) const {
347 return dtls
->getLocks(current_epoch_
).getBit(nodeToIndex(node
));
350 uptr
testOnlyGetEpoch() const { return current_epoch_
; }
351 bool testOnlyHasEdge(uptr l1
, uptr l2
) {
352 return g_
.hasEdge(nodeToIndex(l1
), nodeToIndex(l2
));
354 // idx1 and idx2 are raw indices to g_, not lock IDs.
355 bool testOnlyHasEdgeRaw(uptr idx1
, uptr idx2
) {
356 return g_
.hasEdge(idx1
, idx2
);
360 for (uptr from
= 0; from
< size(); from
++)
361 for (uptr to
= 0; to
< size(); to
++)
362 if (g_
.hasEdge(from
, to
))
363 Printf(" %zx => %zx\n", from
, to
);
367 void check_idx(uptr idx
) const { CHECK_LT(idx
, size()); }
369 void check_node(uptr node
) const {
370 CHECK_GE(node
, size());
371 CHECK_EQ(current_epoch_
, nodeToEpoch(node
));
374 uptr
indexToNode(uptr idx
) const {
376 return idx
+ current_epoch_
;
379 uptr
nodeToIndexUnchecked(uptr node
) const { return node
% size(); }
381 uptr
nodeToIndex(uptr node
) const {
383 return nodeToIndexUnchecked(node
);
386 uptr
nodeToEpoch(uptr node
) const { return node
/ size() * size(); }
388 uptr
getAvailableNode(uptr data
) {
389 uptr idx
= available_nodes_
.getAndClearFirstOne();
391 return indexToNode(idx
);
407 uptr data_
[BV::kSize
];
408 Edge edges_
[BV::kSize
* 32];
412 } // namespace __sanitizer
414 #endif // SANITIZER_DEADLOCK_DETECTOR_H