Shaka Player Embedded
mutex.h
Go to the documentation of this file.
1 // Copyright 2018 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #ifndef SHAKA_EMBEDDED_DEBUG_MUTEX_H_
16 #define SHAKA_EMBEDDED_DEBUG_MUTEX_H_
17 
18 #include <glog/logging.h>
19 
20 #include <atomic>
21 #include <mutex>
22 #include <string>
23 #include <thread>
24 #include <unordered_set>
25 
26 #include "src/debug/waitable.h"
28 #include "src/util/shared_lock.h"
29 
30 namespace shaka {
31 
42 template <typename _Mutex>
43 class DebugMutex : public Waitable {
44  public:
45  explicit DebugMutex(const std::string& name)
46  : Waitable(name), locked_by_(std::thread::id()) {}
47  ~DebugMutex() override {
48  CHECK_EQ(locked_by_, std::thread::id())
49  << "Attempt to destroy locked mutex.";
50  }
51 
52  std::thread::id GetProvider() const override {
53  return locked_by_;
54  }
55 
56  void lock() {
57  CHECK(!holds_shared_lock())
58  << "Cannot hold shared and unique lock at once.";
59  CHECK_NE(locked_by_, std::this_thread::get_id())
60  << "This isn't a recursive mutex.";
61 #ifdef DEBUG_DEADLOCKS
62  auto scope = WaitingTracker::ThreadWaiting(this);
63  // TODO: Add tracking to detect deadlocks with readers; this only works with
64  // deadlocks for the exclusive lock.
65 #endif
66 
67  mutex_.lock();
68 
69  locked_by_ = std::this_thread::get_id();
70  }
71 
72  bool try_lock() {
73  CHECK(!holds_shared_lock()) << "Cannot hold shared an unique lock at once.";
74  CHECK_NE(locked_by_, std::this_thread::get_id())
75  << "This isn't a recursive mutex.";
76 
77  bool ret = mutex_.try_lock();
78 
79  if (ret)
80  locked_by_ = std::this_thread::get_id();
81 
82  return ret;
83  }
84 
85  void unlock() {
86  CHECK_EQ(locked_by_, std::this_thread::get_id())
87  << "Attempt to unlock from wrong thread.";
88  locked_by_ = std::thread::id();
89 
90  mutex_.unlock();
91  }
92 
93 
94  void lock_shared() {
95  CHECK(!holds_shared_lock()) << "This isn't a recursive mutex.";
96  CHECK_NE(locked_by_, std::this_thread::get_id())
97  << "Cannot get shared lock with exclusive lock held.";
98  // TODO: Detect deadlocks with shared usage. We can't use the same paths
99  // for the exclusive lock because there can be multiple readers and it could
100  // report a false-positive.
101 
102  mutex_.lock_shared();
103 
104  add_shared_lock();
105  }
106 
108  CHECK(!holds_shared_lock()) << "This isn't a recursive mutex.";
109  CHECK_NE(locked_by_, std::this_thread::get_id())
110  << "Cannot get shared lock with exclusive lock held.";
111 
112  bool ret = mutex_.try_lock();
113 
114  if (ret)
115  add_shared_lock();
116 
117  return ret;
118  }
119 
120  void unlock_shared() {
121  CHECK(holds_shared_lock()) << "Attempt to unlock from wrong thread.";
122  CHECK_EQ(locked_by_, std::thread::id())
123  << "Must call unlock() before calling unlock_shared() when upgrading.";
124  remove_shared_lock();
125  mutex_.unlock_shared();
126  }
127 
128  private:
129  bool holds_shared_lock() {
130  std::unique_lock<std::mutex> lock(shared_locked_by_lock_);
131  return shared_locked_by_.count(std::this_thread::get_id()) > 0;
132  }
133 
134  void add_shared_lock() {
135  std::unique_lock<std::mutex> lock(shared_locked_by_lock_);
136  shared_locked_by_.insert(std::this_thread::get_id());
137  }
138 
139  void remove_shared_lock() {
140  std::unique_lock<std::mutex> lock(shared_locked_by_lock_);
141  shared_locked_by_.erase(std::this_thread::get_id());
142  }
143  _Mutex mutex_;
144  std::atomic<std::thread::id> locked_by_;
145  std::atomic<bool> is_upgrading_{false};
146 
147  std::mutex shared_locked_by_lock_;
148  std::unordered_set<std::thread::id> shared_locked_by_;
149 };
150 
151 #ifdef DEBUG_DEADLOCKS
154 #else
155 class Mutex final : public std::mutex {
156  public:
157  explicit Mutex(const std::string&) {}
158 };
159 class SharedMutex final : public util::shared_mutex {
160  public:
161  explicit SharedMutex(const std::string&) {}
162 };
163 #endif
164 
165 } // namespace shaka
166 
167 #endif // SHAKA_EMBEDDED_DEBUG_MUTEX_H_
void lock_shared()
Definition: mutex.h:94
std::thread::id GetProvider() const override
Definition: mutex.h:52
bool try_lock_shared()
Definition: mutex.h:107
SharedMutex(const std::string &)
Definition: mutex.h:161
DebugMutex(const std::string &name)
Definition: mutex.h:45
~DebugMutex() override
Definition: mutex.h:47
void lock()
Definition: mutex.h:56
void unlock()
Definition: mutex.h:85
std::string name() const
Definition: waitable.h:37
Mutex(const std::string &)
Definition: mutex.h:157
void unlock_shared()
Definition: mutex.h:120
bool try_lock()
Definition: mutex.h:72