summaryrefslogtreecommitdiffstats
path: root/src/common/lockdep.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/lockdep.cc')
-rw-r--r--src/common/lockdep.cc400
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;
+}
+
+