Skip to content

Commit 264abc2

Browse files
author
lexeyo
committed
cc core: Deadlock detector API refactoring
Implements new dependency tracking API for Deadlock detector. It clearly separates resource ownership and resource awaiting tracking. Adds a few more deadlock detector tests. commit_hash:3f99fd4a2a6ecd2b500dfec44e2f3284aca0e939
1 parent 92059af commit 264abc2

File tree

4 files changed

+117
-62
lines changed

4 files changed

+117
-62
lines changed

core/src/engine/deadlock_detector.cpp

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class CycleDetector final {
5454
cycle(std::move(cycle))
5555
{}
5656

57+
// Actors dependency cycle. Each actor (except the last) blocks the next one.
58+
// The last one blocks the first.
5759
std::vector<Vertex> cycle;
5860
};
5961

@@ -121,27 +123,26 @@ StateBase::StateBase(DeadlockDetector dd) {
121123

122124
StateBase::~StateBase() = default;
123125

124-
void StateBase::HookBeforeAddDependency(const Actor& subject, const Actor& object) {
126+
void StateBase::AddDependency(const Actor& from, const Actor& to) {
125127
if (!impl_->enabled) {
126128
return;
127129
}
128130

129131
const auto* current = current_task::GetCurrentTaskContextUnchecked();
130-
if (current && current == &subject && impl_->collect_stacktrace) {
132+
if (current && current == &from && impl_->collect_stacktrace) {
131133
auto bt = impl_->backtraces.Lock();
132134
bt->emplace(current, boost::stacktrace::stacktrace());
133135
}
134136

135137
auto edges = impl_->active_dependencies.Lock();
136-
auto& subject_dependencies = (*edges)[&subject];
138+
auto& dependencies = (*edges)[&from];
137139
UASSERT_MSG(
138-
std::find(subject_dependencies.begin(), subject_dependencies.end(), &object) == subject_dependencies.end(),
139-
fmt::format("Adding already existing dependency {} -> {}", ToAssertString(subject), ToAssertString(object))
140+
std::find(dependencies.begin(), dependencies.end(), &to) == dependencies.end(),
141+
fmt::format("Adding already existing dependency {} -> {}", ToAssertString(from), ToAssertString(to))
140142
);
141-
subject_dependencies.emplace_back(&object);
142-
143+
dependencies.emplace_back(&to);
143144
CycleDetector cd(*edges);
144-
auto cycle = cd.FindCycle(&object);
145+
auto cycle = cd.FindCycle(&to);
145146
if (cycle) {
146147
auto bt = impl_->backtraces.Lock();
147148
for (const auto& actor : *cycle) {
@@ -156,35 +157,50 @@ void StateBase::HookBeforeAddDependency(const Actor& subject, const Actor& objec
156157
}
157158
}
158159

159-
void StateBase::HookBeforeRemoveDependency(const Actor& subject, const Actor& object) noexcept {
160+
void StateBase::RemoveDependency(const Actor& from, const Actor& to) noexcept {
160161
if (!impl_->enabled) {
161162
return;
162163
}
163164

164165
auto edges = impl_->active_dependencies.Lock();
165-
auto& v = (*edges)[&subject];
166+
auto& v = (*edges)[&from];
166167

167-
auto it = std::find(v.begin(), v.end(), &object);
168+
auto it = std::find(v.begin(), v.end(), &to);
168169
if (it == v.end()) {
169-
utils::AbortWithStacktrace(fmt::format(
170-
"Trying to stop waiting while not waiting! {} => {}",
171-
ToAssertString(subject),
172-
ToAssertString(object)
173-
));
170+
utils::AbortWithStacktrace(
171+
fmt::format("Trying to stop waiting while not waiting! {} => {}", ToAssertString(from), ToAssertString(to))
172+
);
174173
}
175174
v.erase(it);
176175
if (v.empty()) {
177-
edges->erase(&subject);
176+
edges->erase(&from);
178177
}
179178
}
180179

181-
void StateBase::HookActorDestroy(const Actor& object) {
180+
void StateBase::OnResourceAcquire(const Actor& owner, const Actor& resource) {
181+
// Resource release is dependent on the owner
182+
AddDependency(resource, owner);
183+
}
184+
185+
void StateBase::OnResourceRelease(const Actor& owner, const Actor& resource) noexcept {
186+
RemoveDependency(resource, owner);
187+
}
188+
189+
void StateBase::OnWaitForResourceStart(const Actor& waiting, const Actor& resource) {
190+
AddDependency(waiting, resource);
191+
}
192+
193+
void StateBase::OnWaitForResourceFinish(const Actor& waiting, const Actor& resource) noexcept {
194+
RemoveDependency(waiting, resource);
195+
}
196+
197+
void StateBase::OnActorDestroy(const Actor& actor) {
182198
auto edges = impl_->active_dependencies.Lock();
183-
auto it = edges->find(&object);
199+
auto it = edges->find(&actor);
184200
if (it != edges->end() && !it->second.empty()) {
185201
utils::AbortWithStacktrace(fmt::format(
186202
"Trying to destroy {} while still waiting for {}!",
187-
ToAssertString(object),
203+
ToAssertString(actor),
188204
ToAssertString(*it->second.front())
189205
));
190206
}
@@ -204,16 +220,16 @@ State& GetState() {
204220
return pool.GetDeadlockDetectorState();
205221
}
206222

207-
WaitScope::WaitScope(const Actor& a)
208-
: actor_(a)
223+
WaitScope::WaitScope(const Actor& resource)
224+
: resource_(resource)
209225
{
210226
auto& dd_state = GetState();
211-
dd_state.HookBeforeAddDependency(current_task::GetCurrentTaskContext(), actor_);
227+
dd_state.OnWaitForResourceStart(current_task::GetCurrentTaskContext(), resource_);
212228
}
213229

214230
WaitScope::~WaitScope() {
215231
auto& dd_state = GetState();
216-
dd_state.HookBeforeRemoveDependency(current_task::GetCurrentTaskContext(), actor_);
232+
dd_state.OnWaitForResourceFinish(current_task::GetCurrentTaskContext(), resource_);
217233
}
218234

219235
} // namespace engine::deadlock_detector

core/src/engine/deadlock_detector.hpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,24 @@ class StateBase {
1818

1919
virtual ~StateBase();
2020

21-
void HookBeforeAddDependency(const Actor& subject, const Actor& object);
21+
void OnResourceAcquire(const Actor& owner, const Actor& resource);
2222

23-
void HookBeforeRemoveDependency(const Actor& subject, const Actor& object) noexcept;
23+
void OnResourceRelease(const Actor& owner, const Actor& resource) noexcept;
2424

25-
void HookActorDestroy(const Actor& object);
25+
void OnWaitForResourceStart(const Actor& waiting, const Actor& resource);
26+
27+
void OnWaitForResourceFinish(const Actor& waiting, const Actor& resource) noexcept;
28+
29+
void OnActorDestroy(const Actor& actor);
2630

2731
protected:
2832
virtual void OnCycleFound(const std::vector<const Actor*>& cycle) = 0;
2933

3034
private:
35+
void AddDependency(const Actor& from, const Actor& to);
36+
37+
void RemoveDependency(const Actor& from, const Actor& to) noexcept;
38+
3139
struct Impl;
3240
utils::FastPimpl<Impl, 232, 8> impl_;
3341
};
@@ -45,12 +53,12 @@ State& GetState();
4553

4654
class WaitScope final {
4755
public:
48-
explicit WaitScope(const Actor& a);
56+
explicit WaitScope(const Actor& resource);
4957
WaitScope(WaitScope&&) = delete;
5058
~WaitScope();
5159

5260
private:
53-
const Actor& actor_;
61+
const Actor& resource_;
5462
};
5563

5664
} // namespace engine::deadlock_detector

core/src/engine/deadlock_detector_test.cpp

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -67,45 +67,76 @@ UTEST(DeadlockDetector, Ctr) {
6767

6868
UTEST(DeadlockDetector, NoCycle) {
6969
MockState state;
70-
MockActor a1;
71-
MockActor a2;
70+
MockActor actor;
71+
MockActor resource;
7272

73-
state.HookBeforeAddDependency(a1, a2);
74-
state.HookBeforeRemoveDependency(a1, a2);
73+
state.OnResourceAcquire(actor, resource);
74+
state.OnResourceRelease(actor, resource);
75+
76+
state.OnWaitForResourceStart(actor, resource);
77+
state.OnWaitForResourceFinish(actor, resource);
7578

7679
SUCCEED();
7780
}
7881

79-
UTEST(DeadlockDetector, Cycle2) {
82+
UTEST(DeadlockDetector, Cycle2AcquireWait) {
8083
MockState state;
81-
MockActor a1;
82-
MockActor a2;
84+
MockActor actor;
85+
MockActor resource;
8386

84-
state.HookBeforeAddDependency(a1, a2);
85-
EXPECT_THROW_CYCLE(state.HookBeforeAddDependency(a2, a1), &a1, &a2);
87+
// actor -> resource -> actor
88+
state.OnResourceAcquire(actor, resource);
89+
EXPECT_THROW_CYCLE(state.OnWaitForResourceStart(actor, resource), &resource, &actor);
8690
}
8791

88-
UTEST(DeadlockDetector, Cycle3) {
92+
UTEST(DeadlockDetector, Cycle2WaitAcquire) {
8993
MockState state;
90-
MockActor a1;
91-
MockActor a2;
92-
MockActor a3;
94+
MockActor actor;
95+
MockActor resource;
9396

94-
state.HookBeforeAddDependency(a1, a2);
95-
state.HookBeforeAddDependency(a2, a3);
96-
EXPECT_THROW_CYCLE(state.HookBeforeAddDependency(a3, a1), &a1, &a3, &a2);
97+
// resource -> actor -> resource
98+
state.OnWaitForResourceStart(actor, resource);
99+
EXPECT_THROW_CYCLE(state.OnResourceAcquire(actor, resource), &actor, &resource);
100+
}
101+
102+
UTEST(DeadlockDetector, Cycle3) {
103+
MockState state;
104+
MockActor actor1;
105+
MockActor actor2;
106+
MockActor resource;
107+
108+
// resource -> actor1 -> actor2 -> resource
109+
state.OnResourceAcquire(actor1, resource);
110+
state.OnWaitForResourceStart(actor2, resource);
111+
EXPECT_THROW_CYCLE(state.OnWaitForResourceStart(actor1, actor2), &actor2, &actor1, &resource);
97112
}
98113

99114
UTEST(DeadlockDetector, RightCycle) {
100115
MockState state;
101-
MockActor a1;
102-
MockActor a2;
103-
MockActor b1;
104-
MockActor b2;
105-
106-
state.HookBeforeAddDependency(a1, a2);
107-
state.HookBeforeAddDependency(b1, b2);
108-
EXPECT_THROW_CYCLE(state.HookBeforeAddDependency(a2, a1), &a1, &a2);
116+
MockActor actor1;
117+
MockActor actor2;
118+
MockActor resource1;
119+
MockActor resource2;
120+
121+
// actor1 -> resource1 -> actor1
122+
// resource2 -> actor2
123+
state.OnResourceAcquire(actor1, resource1);
124+
state.OnResourceAcquire(actor2, resource2);
125+
EXPECT_THROW_CYCLE(state.OnWaitForResourceStart(actor1, resource1), &resource1, &actor1);
126+
}
127+
128+
UTEST(DeadlockDetector, ClassicMutualDeadlock) {
129+
MockState state;
130+
MockActor actor1;
131+
MockActor actor2;
132+
MockActor resource1;
133+
MockActor resource2;
134+
135+
// actor1 -> resource2 -> actor2 -> resource1 -> actor1
136+
state.OnResourceAcquire(actor1, resource1);
137+
state.OnResourceAcquire(actor2, resource2);
138+
state.OnWaitForResourceStart(actor1, resource2);
139+
EXPECT_THROW_CYCLE(state.OnWaitForResourceStart(actor2, resource1), &resource1, &actor2, &resource2, &actor1);
109140
}
110141

111142
UTEST(DeadlockDetector, NoCycleOnDiamond) {
@@ -130,12 +161,12 @@ UTEST(DeadlockDetector, NoCycleOnDiamond) {
130161
MockActor v3;
131162
MockActor v4;
132163

133-
state.HookBeforeAddDependency(v0, v1);
134-
state.HookBeforeAddDependency(v0, v4);
135-
state.HookBeforeAddDependency(v1, v2);
136-
state.HookBeforeAddDependency(v4, v2);
137-
state.HookBeforeAddDependency(v2, v3);
138-
state.HookBeforeAddDependency(start, v0);
164+
state.OnWaitForResourceStart(v0, v1);
165+
state.OnWaitForResourceStart(v0, v4);
166+
state.OnWaitForResourceStart(v1, v2);
167+
state.OnWaitForResourceStart(v4, v2);
168+
state.OnWaitForResourceStart(v2, v3);
169+
state.OnWaitForResourceStart(start, v0);
139170

140171
SUCCEED();
141172
}

core/src/engine/impl/mutex_impl.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ void MutexImpl<Waiters>::lock() {
163163
template <class Waiters>
164164
void MutexImpl<Waiters>::unlock() {
165165
auto& dd_state = deadlock_detector::GetState();
166-
dd_state.HookBeforeRemoveDependency(*this, current_task::GetCurrentTaskContext());
166+
dd_state.OnResourceRelease(current_task::GetCurrentTaskContext(), *this);
167167

168168
#if USERVER_IMPL_HAS_TSAN
169169
__tsan_mutex_pre_unlock(this, 0);
@@ -200,7 +200,7 @@ bool MutexImpl<Waiters>::try_lock() {
200200

201201
auto& dd_state = deadlock_detector::GetState();
202202
if (result) {
203-
dd_state.HookBeforeAddDependency(*this, current_task::GetCurrentTaskContext());
203+
dd_state.OnResourceAcquire(current_task::GetCurrentTaskContext(), *this);
204204
}
205205

206206
return result;
@@ -236,7 +236,7 @@ bool MutexImpl<Waiters>::try_lock_until(Deadline deadline) {
236236

237237
auto& dd_state = deadlock_detector::GetState();
238238
if (result) {
239-
dd_state.HookBeforeAddDependency(*this, current_task::GetCurrentTaskContext());
239+
dd_state.OnResourceAcquire(current_task::GetCurrentTaskContext(), *this);
240240
}
241241

242242
return result;

0 commit comments

Comments
 (0)