diff options
Diffstat (limited to 'src/common/lockdep.cc')
-rw-r--r-- | src/common/lockdep.cc | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/src/common/lockdep.cc b/src/common/lockdep.cc new file mode 100644 index 000000000..aa7d9e0f0 --- /dev/null +++ b/src/common/lockdep.cc @@ -0,0 +1,400 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2008-2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ +#include "lockdep.h" +#include <bitset> +#include "common/ceph_context.h" +#include "common/dout.h" +#include "common/valgrind.h" + +/******* Constants **********/ +#define lockdep_dout(v) lsubdout(g_lockdep_ceph_ctx, lockdep, v) +#define BACKTRACE_SKIP 2 + +/******* Globals **********/ +bool g_lockdep; +struct lockdep_stopper_t { + // disable lockdep when this module destructs. + ~lockdep_stopper_t() { + g_lockdep = 0; + } +}; + + +static pthread_mutex_t lockdep_mutex = PTHREAD_MUTEX_INITIALIZER; +static CephContext *g_lockdep_ceph_ctx = NULL; +static lockdep_stopper_t lockdep_stopper; +static ceph::unordered_map<std::string, int> lock_ids; +static std::map<int, std::string> lock_names; +static std::map<int, int> lock_refs; +static constexpr size_t MAX_LOCKS = 128 * 1024; // increase me as needed +static std::bitset<MAX_LOCKS> free_ids; // bit set = free +static ceph::unordered_map<pthread_t, std::map<int,ceph::BackTrace*> > held; +static constexpr size_t NR_LOCKS = 4096; // the initial number of locks +static std::vector<std::bitset<MAX_LOCKS>> follows(NR_LOCKS); // follows[a][b] means b taken after a +static std::vector<std::map<int,ceph::BackTrace *>> follows_bt(NR_LOCKS); +// upper bound of lock id +unsigned current_maxid; +int last_freed_id = -1; +static bool free_ids_inited; + +static bool lockdep_force_backtrace() +{ + return (g_lockdep_ceph_ctx != NULL && + g_lockdep_ceph_ctx->_conf->lockdep_force_backtrace); +} + +/******* Functions **********/ +void lockdep_register_ceph_context(CephContext *cct) +{ + static_assert((MAX_LOCKS > 0) && (MAX_LOCKS % 8 == 0), + "lockdep's MAX_LOCKS needs to be divisible by 8 to operate correctly."); + pthread_mutex_lock(&lockdep_mutex); + if (g_lockdep_ceph_ctx == NULL) { + ANNOTATE_BENIGN_RACE_SIZED(&g_lockdep_ceph_ctx, sizeof(g_lockdep_ceph_ctx), + "lockdep cct"); + ANNOTATE_BENIGN_RACE_SIZED(&g_lockdep, sizeof(g_lockdep), + "lockdep enabled"); + g_lockdep = true; + g_lockdep_ceph_ctx = cct; + lockdep_dout(1) << "lockdep start" << dendl; + if (!free_ids_inited) { + free_ids_inited = true; + // FIPS zeroization audit 20191115: this memset is not security related. + free_ids.set(); + } + } + pthread_mutex_unlock(&lockdep_mutex); +} + +void lockdep_unregister_ceph_context(CephContext *cct) +{ + pthread_mutex_lock(&lockdep_mutex); + if (cct == g_lockdep_ceph_ctx) { + lockdep_dout(1) << "lockdep stop" << dendl; + // this cct is going away; shut it down! + g_lockdep = false; + g_lockdep_ceph_ctx = NULL; + + // blow away all of our state, too, in case it starts up again. + for (unsigned i = 0; i < current_maxid; ++i) { + for (unsigned j = 0; j < current_maxid; ++j) { + delete follows_bt[i][j]; + } + } + + held.clear(); + lock_names.clear(); + lock_ids.clear(); + std::for_each(follows.begin(), std::next(follows.begin(), current_maxid), + [](auto& follow) { follow.reset(); }); + std::for_each(follows_bt.begin(), std::next(follows_bt.begin(), current_maxid), + [](auto& follow_bt) { follow_bt = {}; }); + } + pthread_mutex_unlock(&lockdep_mutex); +} + +int lockdep_dump_locks() +{ + pthread_mutex_lock(&lockdep_mutex); + if (!g_lockdep) + goto out; + + for (auto p = held.begin(); p != held.end(); ++p) { + lockdep_dout(0) << "--- thread " << p->first << " ---" << dendl; + for (auto q = p->second.begin(); + q != p->second.end(); + ++q) { + lockdep_dout(0) << " * " << lock_names[q->first] << "\n"; + if (q->second) + *_dout << *(q->second); + *_dout << dendl; + } + } +out: + pthread_mutex_unlock(&lockdep_mutex); + return 0; +} + +int lockdep_get_free_id(void) +{ + // if there's id known to be freed lately, reuse it + if (last_freed_id >= 0 && + free_ids.test(last_freed_id)) { + int tmp = last_freed_id; + last_freed_id = -1; + free_ids.reset(tmp); + lockdep_dout(1) << "lockdep reusing last freed id " << tmp << dendl; + return tmp; + } + + // walk through entire array and locate nonzero char, then find + // actual bit. + for (size_t i = 0; i < free_ids.size(); ++i) { + if (free_ids.test(i)) { + free_ids.reset(i); + return i; + } + } + + // not found + lockdep_dout(0) << "failing miserably..." << dendl; + return -1; +} + +static int _lockdep_register(const char *name) +{ + int id = -1; + + if (!g_lockdep) + return id; + ceph::unordered_map<std::string, int>::iterator p = lock_ids.find(name); + if (p == lock_ids.end()) { + id = lockdep_get_free_id(); + if (id < 0) { + lockdep_dout(0) << "ERROR OUT OF IDS .. have 0" + << " max " << MAX_LOCKS << dendl; + for (auto& p : lock_names) { + lockdep_dout(0) << " lock " << p.first << " " << p.second << dendl; + } + ceph_abort(); + } + if (current_maxid <= (unsigned)id) { + current_maxid = (unsigned)id + 1; + if (current_maxid == follows.size()) { + follows.resize(current_maxid + 1); + follows_bt.resize(current_maxid + 1); + } + } + lock_ids[name] = id; + lock_names[id] = name; + lockdep_dout(10) << "registered '" << name << "' as " << id << dendl; + } else { + id = p->second; + lockdep_dout(20) << "had '" << name << "' as " << id << dendl; + } + + ++lock_refs[id]; + + return id; +} + +int lockdep_register(const char *name) +{ + int id; + + pthread_mutex_lock(&lockdep_mutex); + id = _lockdep_register(name); + pthread_mutex_unlock(&lockdep_mutex); + return id; +} + +void lockdep_unregister(int id) +{ + if (id < 0) { + return; + } + + pthread_mutex_lock(&lockdep_mutex); + + std::string name; + auto p = lock_names.find(id); + if (p == lock_names.end()) + name = "unknown" ; + else + name = p->second; + + int &refs = lock_refs[id]; + if (--refs == 0) { + if (p != lock_names.end()) { + // reset dependency ordering + follows[id].reset(); + for (unsigned i=0; i<current_maxid; ++i) { + delete follows_bt[id][i]; + follows_bt[id][i] = NULL; + + delete follows_bt[i][id]; + follows_bt[i][id] = NULL; + follows[i].reset(id); + } + + lockdep_dout(10) << "unregistered '" << name << "' from " << id << dendl; + lock_ids.erase(p->second); + lock_names.erase(id); + } + lock_refs.erase(id); + free_ids.set(id); + last_freed_id = id; + } else if (g_lockdep) { + lockdep_dout(20) << "have " << refs << " of '" << name << "' " << + "from " << id << dendl; + } + pthread_mutex_unlock(&lockdep_mutex); +} + + +// does b follow a? +static bool does_follow(int a, int b) +{ + if (follows[a].test(b)) { + lockdep_dout(0) << "\n"; + *_dout << "------------------------------------" << "\n"; + *_dout << "existing dependency " << lock_names[a] << " (" << a << ") -> " + << lock_names[b] << " (" << b << ") at:\n"; + if (follows_bt[a][b]) { + follows_bt[a][b]->print(*_dout); + } + *_dout << dendl; + return true; + } + + for (unsigned i=0; i<current_maxid; i++) { + if (follows[a].test(i) && + does_follow(i, b)) { + lockdep_dout(0) << "existing intermediate dependency " << lock_names[a] + << " (" << a << ") -> " << lock_names[i] << " (" << i << ") at:\n"; + if (follows_bt[a][i]) { + follows_bt[a][i]->print(*_dout); + } + *_dout << dendl; + return true; + } + } + + return false; +} + +int lockdep_will_lock(const char *name, int id, bool force_backtrace, + bool recursive) +{ + pthread_t p = pthread_self(); + + pthread_mutex_lock(&lockdep_mutex); + if (!g_lockdep) { + pthread_mutex_unlock(&lockdep_mutex); + return id; + } + + if (id < 0) + id = _lockdep_register(name); + + lockdep_dout(20) << "_will_lock " << name << " (" << id << ")" << dendl; + + // check dependency graph + auto& m = held[p]; + for (auto p = m.begin(); p != m.end(); ++p) { + if (p->first == id) { + if (!recursive) { + lockdep_dout(0) << "\n"; + *_dout << "recursive lock of " << name << " (" << id << ")\n"; + auto bt = new ceph::ClibBackTrace(BACKTRACE_SKIP); + bt->print(*_dout); + if (p->second) { + *_dout << "\npreviously locked at\n"; + p->second->print(*_dout); + } + delete bt; + *_dout << dendl; + ceph_abort(); + } + } else if (!follows[p->first].test(id)) { + // new dependency + + // did we just create a cycle? + if (does_follow(id, p->first)) { + auto bt = new ceph::ClibBackTrace(BACKTRACE_SKIP); + lockdep_dout(0) << "new dependency " << lock_names[p->first] + << " (" << p->first << ") -> " << name << " (" << id << ")" + << " creates a cycle at\n"; + bt->print(*_dout); + *_dout << dendl; + + lockdep_dout(0) << "btw, i am holding these locks:" << dendl; + for (auto q = m.begin(); q != m.end(); ++q) { + lockdep_dout(0) << " " << lock_names[q->first] << " (" << q->first << ")" << dendl; + if (q->second) { + lockdep_dout(0) << " "; + q->second->print(*_dout); + *_dout << dendl; + } + } + + lockdep_dout(0) << "\n" << dendl; + + // don't add this dependency, or we'll get aMutex. cycle in the graph, and + // does_follow() won't terminate. + + ceph_abort(); // actually, we should just die here. + } else { + ceph::BackTrace* bt = NULL; + if (force_backtrace || lockdep_force_backtrace()) { + bt = new ceph::ClibBackTrace(BACKTRACE_SKIP); + } + follows[p->first].set(id); + follows_bt[p->first][id] = bt; + lockdep_dout(10) << lock_names[p->first] << " -> " << name << " at" << dendl; + //bt->print(*_dout); + } + } + } + pthread_mutex_unlock(&lockdep_mutex); + return id; +} + +int lockdep_locked(const char *name, int id, bool force_backtrace) +{ + pthread_t p = pthread_self(); + + pthread_mutex_lock(&lockdep_mutex); + if (!g_lockdep) + goto out; + if (id < 0) + id = _lockdep_register(name); + + lockdep_dout(20) << "_locked " << name << dendl; + if (force_backtrace || lockdep_force_backtrace()) + held[p][id] = new ceph::ClibBackTrace(BACKTRACE_SKIP); + else + held[p][id] = 0; +out: + pthread_mutex_unlock(&lockdep_mutex); + return id; +} + +int lockdep_will_unlock(const char *name, int id) +{ + pthread_t p = pthread_self(); + + if (id < 0) { + //id = lockdep_register(name); + ceph_assert(id == -1); + return id; + } + + pthread_mutex_lock(&lockdep_mutex); + if (!g_lockdep) + goto out; + lockdep_dout(20) << "_will_unlock " << name << dendl; + + // don't assert.. lockdep may be enabled at any point in time + //assert(held.count(p)); + //assert(held[p].count(id)); + + delete held[p][id]; + held[p].erase(id); +out: + pthread_mutex_unlock(&lockdep_mutex); + return id; +} + + |