Shaka Player Embedded
waiting_tracker.cc
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 
16 
17 #include <glog/logging.h>
18 
19 #include <mutex>
20 #include <sstream>
21 #include <string>
22 #include <thread>
23 #include <unordered_map>
24 #include <unordered_set>
25 
26 #include "src/debug/mutex.h"
27 #include "src/debug/thread.h"
28 #include "src/debug/thread_event.h"
29 #include "src/debug/waitable.h"
30 #include "src/util/macros.h"
31 
32 namespace shaka {
33 
34 #ifdef DEBUG_DEADLOCKS
35 
36 namespace {
37 
39 std::mutex global_mutex_;
40 std::unordered_map<std::thread::id, const Waitable*> waiting_threads_;
41 std::unordered_map<std::thread::id, const Thread*> all_threads_;
43 
44 std::string ThreadName(std::thread::id id) {
45  std::stringstream ss;
46  if (all_threads_.count(id) > 0)
47  ss << all_threads_.at(id)->name() << " (" << id << ")";
48  else
49  ss << id;
50  return ss.str();
51 }
52 
57 void DetectDeadlock(const Waitable* start, std::thread::id start_thread_id) {
58  std::stringstream trace;
59  std::unordered_set<std::thread::id> seen;
60  seen.insert(start_thread_id);
61 
62  // If a thread is waiting on something, find the thread that will provide it.
63  // If that produces a cycle, then we are deadlocked.
64  std::thread::id prev_thread_id = start_thread_id;
65  const Waitable* waiting_on = start;
66  int i = 0;
67  while (true) {
68  const std::thread::id provider = waiting_on->GetProvider();
69  if (provider == std::thread::id())
70  break;
71 
72  // We can be called between setting the provider and indicating to this code
73  // it is done waiting.
74  if (provider == prev_thread_id)
75  break;
76 
77  // (0) FooThread -> "bar" provided by: (1)
78  trace << "(" << i << ") " << ThreadName(prev_thread_id) << " -> \""
79  << waiting_on->name() << "\" provided by"
80  << ": (" << (provider == start_thread_id ? 0 : i + 1) << ")\n";
81 
82  if (seen.count(provider)) {
83  LOG(FATAL)
84  << "Deadlock detected:\n"
85  << "(i) thread name (id) -> waiting on\n"
86  << "--------------------------------------------------------------\n"
87  << trace.str()
88  << "--------------------------------------------------------------";
89  }
90 
91  if (waiting_threads_.count(provider) == 0)
92  break;
93  seen.insert(provider);
94  prev_thread_id = provider;
95  waiting_on = waiting_threads_.at(provider);
96  i++;
97  }
98 }
99 
100 } // namespace
101 
102 WaitingTracker::TrackerScope::TrackerScope() : exec_(true) {}
103 
104 WaitingTracker::TrackerScope::TrackerScope(TrackerScope&& other) : exec_(true) {
105  other.exec_ = false;
106 }
107 
108 WaitingTracker::TrackerScope& WaitingTracker::TrackerScope::operator=(
109  TrackerScope&& other) {
110  other.exec_ = false;
111  exec_ = true;
112  return *this;
113 }
114 
115 WaitingTracker::TrackerScope::~TrackerScope() {
116  if (exec_) {
117  std::unique_lock<std::mutex> lock(global_mutex_);
118  CHECK_EQ(waiting_threads_.count(std::this_thread::get_id()), 1u);
119  waiting_threads_.erase(std::this_thread::get_id());
120  }
121 }
122 
123 
124 // static
125 void WaitingTracker::AddThread(const Thread* thread) {
126  std::unique_lock<std::mutex> lock(global_mutex_);
127  CHECK_EQ(all_threads_.count(thread->get_original_id()), 0u);
128  all_threads_[thread->get_original_id()] = thread;
129 }
130 
131 // static
132 void WaitingTracker::RemoveThread(const Thread* thread) {
133  std::unique_lock<std::mutex> lock(global_mutex_);
134  CHECK_EQ(all_threads_.count(thread->get_original_id()), 1u);
135  CHECK_EQ(waiting_threads_.count(thread->get_original_id()), 0u)
136  << "Attempt to destroy thread that is waiting.";
137  all_threads_.erase(thread->get_original_id());
138 }
139 
140 // static
141 void WaitingTracker::RemoveWaitable(const Waitable* waiting_on) {
142  std::unique_lock<std::mutex> lock(global_mutex_);
143  for (auto& pair : waiting_threads_) {
144  if (pair.second == waiting_on)
145  LOG(FATAL) << "Attempt to destroy an object someone is waiting for.";
146  }
147 }
148 
149 // static
150 void WaitingTracker::ThreadExit() {
151  std::unique_lock<std::mutex> lock(global_mutex_);
152  for (auto& pair : waiting_threads_) {
153  const std::thread::id provider = pair.second->GetProvider();
154  if (provider == std::this_thread::get_id()) {
155  LOG(FATAL) << "Waiting on an event whose provider thread has exited: "
156  << pair.second->name();
157  }
158  }
159 }
160 
161 // static
162 void WaitingTracker::UpdateProvider(const Waitable* waiting_on) {
163  std::unique_lock<std::mutex> lock(global_mutex_);
164 
165  const std::thread::id provider = waiting_on->GetProvider();
166  if (waiting_threads_.count(provider))
167  DetectDeadlock(waiting_threads_.at(provider), provider);
168 }
169 
170 // static
171 WaitingTracker::TrackerScope WaitingTracker::ThreadWaiting(
172  const Waitable* waiting_on) {
173  std::unique_lock<std::mutex> lock(global_mutex_);
174 
175  const std::thread::id this_thread_id = std::this_thread::get_id();
176  DetectDeadlock(waiting_on, this_thread_id);
177 
178  CHECK_EQ(waiting_threads_.count(this_thread_id), 0u)
179  << "Somehow waiting on two conditions at once.";
180  waiting_threads_[this_thread_id] = waiting_on;
181  return TrackerScope();
182 }
183 #endif
184 
185 } // namespace shaka
#define END_ALLOW_COMPLEX_STATICS
Definition: macros.h:43
Waitable(const std::string &name)
Definition: waitable.cc:21
#define BEGIN_ALLOW_COMPLEX_STATICS
Definition: macros.h:42