[sanitizer] more code for deadlock detector, nothing really works yet (except for small unit tests).

llvm-svn: 201302
This commit is contained in:
Kostya Serebryany 2014-02-13 07:44:51 +00:00
parent 7aaa0c2ddb
commit 5e52d48e3a
6 changed files with 274 additions and 3 deletions

View File

@ -51,6 +51,7 @@ set(SANITIZER_HEADERS
sanitizer_common_interceptors_ioctl.inc
sanitizer_common_interceptors_format.inc
sanitizer_common_syscalls.inc
sanitizer_deadlock_detector.h
sanitizer_flags.h
sanitizer_internal_defs.h
sanitizer_lfstack.h

View File

@ -26,6 +26,7 @@ class BasicBitVector {
uptr size() const { return kSize; }
// No CTOR.
void clear() { bits_ = 0; }
void setAll() { bits_ = ~(basic_int_t)0; }
bool empty() const { return bits_ == 0; }
// Returns true if the bit has changed from 0 to 1.
bool setBit(uptr idx) {
@ -85,6 +86,13 @@ class TwoLevelBitVector {
for (uptr i = 0; i < kLevel1Size; i++)
l1_[i].clear();
}
void setAll() {
for (uptr i0 = 0; i0 < kLevel1Size; i0++) {
l1_[i0].setAll();
for (uptr i1 = 0; i1 < BV::kSize; i1++)
l2_[i0][i1].setAll();
}
}
bool empty() {
for (uptr i = 0; i < kLevel1Size; i++)
if (!l1_[i].empty())

View File

@ -44,8 +44,8 @@ class BVGraph {
}
// Returns true if there is a path from the node 'from'
// to any of the nodes in 'target'.
bool isReachable(uptr from, const BV &target) {
// to any of the nodes in 'targets'.
bool isReachable(uptr from, const BV &targets) {
BV to_visit, visited;
to_visit.clear();
to_visit.setUnion(v[from]);
@ -56,7 +56,7 @@ class BVGraph {
if (visited.setBit(idx))
to_visit.setUnion(v[idx]);
}
return target.intersectsWith(visited);
return targets.intersectsWith(visited);
}
private:

View File

@ -0,0 +1,158 @@
//===-- sanitizer_deadlock_detector.h ---------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is a part of Sanitizer runtime.
// The deadlock detector maintains a directed graph of lock acquisitions.
// When a lock event happens, the detector checks if the locks already held by
// the current thread are reachable from the newly acquired lock.
//
// FIXME: this is work in progress, nothing really works yet.
//
//===----------------------------------------------------------------------===//
#ifndef SANITIZER_DEADLOCK_DETECTOR_H
#define SANITIZER_DEADLOCK_DETECTOR_H
#include "sanitizer_common.h"
#include "sanitizer_bvgraph.h"
namespace __sanitizer {
// Thread-local state for DeadlockDetector.
// It contains the locks currently held by the owning thread.
class DeadlockDetectorTLS {
public:
// No CTOR.
void clear() { n_locks_ = 0; }
void addLock(uptr node) {
CHECK_LT(n_locks_, ARRAY_SIZE(locks_));
locks_[n_locks_++] = node;
}
void removeLock(uptr node) {
CHECK_NE(n_locks_, 0U);
for (sptr i = n_locks_ - 1; i >= 0; i--) {
if (locks_[i] == node) {
locks_[i] = locks_[n_locks_ - 1];
n_locks_--;
return;
}
}
CHECK(0);
}
uptr numLocks() const { return n_locks_; }
uptr getLock(uptr idx) const {
CHECK_LT(idx, n_locks_);
return locks_[idx];
}
private:
uptr n_locks_;
uptr locks_[64];
};
// DeadlockDetector.
// For deadlock detection to work we need one global DeadlockDetector object
// and one DeadlockDetectorTLS object per evey thread.
template <class BV>
class DeadlockDetector {
public:
typedef BV BitVector;
uptr size() const { return g_.size(); }
// No CTOR.
void clear() {
current_epoch_ = 0;
available_nodes_.clear();
recycled_nodes_.clear();
g_.clear();
}
// Allocate new deadlock detector node.
// If we are out of available nodes first try to recycle some.
// If there is nothing to recycle, flush the graph and increment the epoch.
// Associate 'data' (opaque user's object) with the new node.
uptr newNode(uptr data) {
if (!available_nodes_.empty())
return getAvailableNode(data);
if (!recycled_nodes_.empty()) {
CHECK(available_nodes_.empty());
available_nodes_.setUnion(recycled_nodes_);
recycled_nodes_.clear();
// FIXME: actually recycle nodes in the graph.
return getAvailableNode(data);
}
// We are out of vacant nodes. Flush and increment the current_epoch_.
uptr new_epoch = current_epoch_ + BV::kSize;
clear();
current_epoch_ = new_epoch;
available_nodes_.setAll();
return getAvailableNode(data);
}
// Get data associated with the node created by newNode().
uptr getData(uptr node) const { return data_[nodeToIndex(node)]; }
void removeNode(uptr node) {
uptr idx = nodeToIndex(node);
CHECK(!available_nodes_.getBit(idx));
CHECK(recycled_nodes_.setBit(idx));
// FIXME: also remove from the graph.
}
// Handle the lock event, return true if there is a cycle.
// FIXME: handle RW locks, recusive locks, etc.
bool onLock(DeadlockDetectorTLS *dtls, uptr cur_node) {
BV cur_locks;
cur_locks.clear();
uptr cur_idx = nodeToIndex(cur_node);
for (uptr i = 0, n = dtls->numLocks(); i < n; i++) {
uptr prev_node = dtls->getLock(i);
uptr prev_idx = nodeToIndex(prev_node);
g_.addEdge(prev_idx, cur_idx);
cur_locks.setBit(prev_idx);
// Printf("OnLock %zx; prev %zx\n", cur_node, dtls->getLock(i));
}
dtls->addLock(cur_node);
return g_.isReachable(cur_idx, cur_locks);
}
// Handle the unlock event.
void onUnlock(DeadlockDetectorTLS *dtls, uptr node) {
dtls->removeLock(node);
}
private:
void check_idx(uptr idx) const { CHECK_LT(idx, size()); }
void check_node(uptr node) const {
CHECK_GE(node, size());
CHECK_EQ(current_epoch_, node / size() * size());
}
uptr indexToNode(uptr idx) {
check_idx(idx);
return idx | current_epoch_;
}
uptr nodeToIndex(uptr node) {
check_node(node);
return node % size();
}
uptr getAvailableNode(uptr data) {
uptr idx = available_nodes_.getAndClearFirstOne();
data_[idx] = data;
return indexToNode(idx);
}
uptr current_epoch_;
BV available_nodes_;
BV recycled_nodes_;
BVGraph<BV> g_;
uptr data_[BV::kSize];
};
} // namespace __sanitizer
#endif // SANITIZER_DEADLOCK_DETECTOR_H

View File

@ -6,6 +6,7 @@ set(SANITIZER_UNITTESTS
sanitizer_bitvector_test.cc
sanitizer_bvgraph_test.cc
sanitizer_common_test.cc
sanitizer_deadlock_detector_test.cc
sanitizer_flags_test.cc
sanitizer_format_interceptor_test.cc
sanitizer_ioctl_test.cc

View File

@ -0,0 +1,103 @@
//===-- sanitizer_deadlock_detector_test.cc -------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is a part of Sanitizer runtime.
// Tests for sanitizer_deadlock_detector.h
//
//===----------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_deadlock_detector.h"
#include "sanitizer_test_utils.h"
#include "gtest/gtest.h"
#include <algorithm>
#include <vector>
#include <set>
using namespace __sanitizer;
using namespace std;
template <class BV>
void TestDeadlockDetector() {
// Can't allocate on stack.
DeadlockDetector<BV> *dp = new DeadlockDetector<BV>;
DeadlockDetector<BV> &d = *dp;
DeadlockDetectorTLS dtls;
d.clear();
set<uptr> s;
for (size_t i = 0; i < d.size() * 3; i++) {
uptr node = d.newNode(0);
EXPECT_TRUE(s.insert(node).second);
}
d.clear();
s.clear();
// Add size() nodes.
for (size_t i = 0; i < d.size(); i++) {
uptr node = d.newNode(0);
EXPECT_TRUE(s.insert(node).second);
}
// Remove all nodes.
for (set<uptr>::iterator it = s.begin(); it != s.end(); ++it)
d.removeNode(*it);
// The nodes should be reused.
for (size_t i = 0; i < d.size(); i++) {
uptr node = d.newNode(0);
EXPECT_FALSE(s.insert(node).second);
}
// Cycle: n1->n2->n1
{
d.clear();
dtls.clear();
uptr n1 = d.newNode(1);
uptr n2 = d.newNode(2);
EXPECT_FALSE(d.onLock(&dtls, n1));
EXPECT_FALSE(d.onLock(&dtls, n2));
d.onUnlock(&dtls, n2);
d.onUnlock(&dtls, n1);
EXPECT_FALSE(d.onLock(&dtls, n2));
EXPECT_TRUE(d.onLock(&dtls, n1));
d.onUnlock(&dtls, n1);
d.onUnlock(&dtls, n2);
}
// Cycle: n1->n2->n3->n1
{
d.clear();
dtls.clear();
uptr n1 = d.newNode(1);
uptr n2 = d.newNode(2);
uptr n3 = d.newNode(3);
EXPECT_FALSE(d.onLock(&dtls, n1));
EXPECT_FALSE(d.onLock(&dtls, n2));
d.onUnlock(&dtls, n2);
d.onUnlock(&dtls, n1);
EXPECT_FALSE(d.onLock(&dtls, n2));
EXPECT_FALSE(d.onLock(&dtls, n3));
d.onUnlock(&dtls, n3);
d.onUnlock(&dtls, n2);
EXPECT_FALSE(d.onLock(&dtls, n3));
EXPECT_TRUE(d.onLock(&dtls, n1));
d.onUnlock(&dtls, n1);
d.onUnlock(&dtls, n3);
}
delete dp;
}
TEST(SanitizerCommon, DeadlockDetector) {
TestDeadlockDetector<BasicBitVector<> >();
TestDeadlockDetector<TwoLevelBitVector<2> >();
}