Skip to content

Commit e54b081

Browse files
committed
add deadlock detection
1 parent 7777bee commit e54b081

23 files changed

Lines changed: 471 additions & 181 deletions

codegen/yieldpass.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,6 @@ struct YieldInserter {
106106
return;
107107
}
108108

109-
errs() << "yields inserted to the " << F.getName() << "\n";
110-
errs() << F << "\n";
111-
112109
visited.insert(name);
113110

114111
Builder Builder(&*F.begin());
@@ -140,6 +137,9 @@ struct YieldInserter {
140137
}
141138
}
142139
#endif
140+
141+
errs() << "yields inserted to the " << F.getName() << "\n";
142+
errs() << F << "\n";
143143
}
144144

145145
bool ItsYieldInst(Instruction *inst) {

runtime/include/futex.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
struct CoroBase;
1010

1111
struct FutexQueues {
12+
// TODO(kmitkin): due to usage in as_atomic functions rewrite to custom hash
13+
// table & linked list
1214
std::unordered_map<std::uintptr_t, std::deque<CoroBase *>> queues;
1315

1416
void Push(FutexState state, CoroBase *coro) {

runtime/include/lib.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ extern FutexQueues futex_queues;
2727

2828
extern "C" void CoroYield();
2929

30+
extern "C" bool CoroSpin(bool condition);
31+
3032
struct CoroBase : public std::enable_shared_from_this<CoroBase> {
3133
CoroBase(const CoroBase&) = delete;
3234
CoroBase(CoroBase&&) = delete;
@@ -72,7 +74,13 @@ struct CoroBase : public std::enable_shared_from_this<CoroBase> {
7274
futex_queues.Push(state, this);
7375
}
7476

75-
bool IsBlocked() { return futex_queues.IsBlocked(fstate, this); }
77+
void SetSpinLocked(bool is_spinlocked) {
78+
this->is_spinlocked = is_spinlocked;
79+
}
80+
81+
bool IsBlocked() {
82+
return is_spinlocked || futex_queues.IsBlocked(fstate, this);
83+
}
7684

7785
// Checks if the coroutine is parked.
7886
bool IsParked() const;
@@ -86,6 +94,7 @@ struct CoroBase : public std::enable_shared_from_this<CoroBase> {
8694

8795
friend void CoroBody(int);
8896
friend void ::CoroYield();
97+
friend bool ::CoroSpin(bool);
8998

9099
template <typename Target, typename... Args>
91100
friend class Coro;
@@ -99,6 +108,7 @@ struct CoroBase : public std::enable_shared_from_this<CoroBase> {
99108
// Futex state on which coroutine is blocked.
100109
public:
101110
FutexState fstate{};
111+
bool is_spinlocked{false};
102112
// Name.
103113
std::string_view name;
104114
boost::context::fiber_context ctx;

runtime/include/minimization.h

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#pragma once
22
#include <unordered_set>
33

4-
#include "lib.h"
54
#include "scheduler_fwd.h"
65

76
/**
@@ -11,8 +10,9 @@
1110
struct RoundMinimizor {
1211
// Minimizes the number of tasks in the nonlinearized history; modifies
1312
// argument `nonlinear_history`.
14-
virtual void Minimize(SchedulerWithReplay& sched,
15-
Scheduler::BothHistories& nonlinear_history) const = 0;
13+
virtual void Minimize(
14+
SchedulerWithReplay& sched,
15+
Scheduler::NonLinearizableHistory& nonlinear_history) const = 0;
1616

1717
/**
1818
* Returns ids of tasks taken from `full_history`, excluding those ids, that
@@ -37,8 +37,9 @@ struct GreedyRoundMinimizor : public RoundMinimizor {
3737
* pairs of tasks and remove them together, this is done to account for
3838
* data-structures that have the `add/remove` semantics.
3939
*/
40-
void Minimize(SchedulerWithReplay& sched,
41-
Scheduler::BothHistories& nonlinear_history) const override;
40+
void Minimize(
41+
SchedulerWithReplay& sched,
42+
Scheduler::NonLinearizableHistory& nonlinear_history) const override;
4243

4344
protected:
4445
/**
@@ -54,7 +55,7 @@ struct GreedyRoundMinimizor : public RoundMinimizor {
5455
*/
5556
virtual Scheduler::Result OnTasksRemoved(
5657
SchedulerWithReplay& sched,
57-
const Scheduler::BothHistories& nonlinear_history,
58+
const Scheduler::NonLinearizableHistory& nonlinear_history,
5859
const std::unordered_set<int>& task_ids) const = 0;
5960
};
6061

@@ -71,7 +72,7 @@ struct SameInterleavingMinimizor : public GreedyRoundMinimizor {
7172
protected:
7273
virtual Scheduler::Result OnTasksRemoved(
7374
SchedulerWithReplay& sched,
74-
const Scheduler::BothHistories& nonlinear_history,
75+
const Scheduler::NonLinearizableHistory& nonlinear_history,
7576
const std::unordered_set<int>& task_ids) const override;
7677
};
7778

@@ -92,7 +93,7 @@ struct StrategyExplorationMinimizor : public GreedyRoundMinimizor {
9293
protected:
9394
virtual Scheduler::Result OnTasksRemoved(
9495
SchedulerWithReplay& sched,
95-
const Scheduler::BothHistories& nonlinear_history,
96+
const Scheduler::NonLinearizableHistory& nonlinear_history,
9697
const std::unordered_set<int>& task_ids) const override;
9798

9899
private:

runtime/include/minimization_smart.h

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22

33
#include <random>
44
#include <set>
5-
#include <string>
65
#include <unordered_map>
76
#include <vector>
87

9-
#include "lib.h"
108
#include "minimization.h"
119
#include "pretty_print.h"
1210
#include "scheduler_fwd.h"
@@ -37,21 +35,22 @@ struct SmartMinimizor : public RoundMinimizor {
3735
int offsprings_generation_attemps = 10,
3836
int initial_mutations_count = 10);
3937

40-
void Minimize(SchedulerWithReplay& sched,
41-
Scheduler::BothHistories& nonlinear_histories) const override;
38+
void Minimize(
39+
SchedulerWithReplay& sched,
40+
Scheduler::NonLinearizableHistory& nonlinear_histories) const override;
4241

4342
private:
4443
struct Solution {
4544
explicit Solution(const Strategy& strategy,
46-
const Scheduler::BothHistories& histories,
45+
const Scheduler::NonLinearizableHistory& histories,
4746
int total_tasks);
4847

4948
float GetFitness() const;
5049
int GetValidTasks() const;
5150

5251
std::unordered_map<int, std::unordered_set<int>>
5352
tasks; // ThreadId -> { ValidTaskId1, ValidTaskId2, ... }
54-
Scheduler::BothHistories nonlinear_histories;
53+
Scheduler::NonLinearizableHistory nonlinear_histories;
5554
// Fitness is a value in range [0.0, 1.0], the bigger it is, the better is
5655
// the Solution.
5756
private:

runtime/include/pct_strategy.h

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#pragma once
22

33
#include <cassert>
4+
#include <limits>
5+
#include <optional>
46
#include <random>
57

68
#include "scheduler.h"
@@ -25,23 +27,21 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
2527
PrepareForDepth(current_depth, avg_k);
2628
}
2729

28-
// If there aren't any non returned tasks and the amount of finished tasks
29-
// is equal to the max_tasks the finished task will be returned
30-
TaskWithMetaData Next() override {
31-
return this->NextVerifiedFor(NextThreadId());
32-
}
33-
34-
size_t NextThreadId() override {
30+
std::optional<size_t> NextThreadId() override {
3531
auto& threads = this->threads;
36-
int max = std::numeric_limits<int>::min();
32+
size_t max = std::numeric_limits<size_t>::min();
3733
size_t index_of_max = 0;
3834
// Have to ignore waiting threads, so can't do it faster than O(n)
3935
for (size_t i = 0; i < threads.size(); ++i) {
4036
// Ignore waiting tasks
41-
// debug(stderr, "prior: %d, number %d\n", priorities[i], i);
37+
debug(stderr, "prior: %d, number %d\n", priorities[i], i);
4238
if (!threads[i].empty() && threads[i].back()->IsBlocked()) {
43-
// debug(stderr, "blocked on %p val %d\n",
44-
// threads[i].back()->fstate.addr, threads[i].back()->fstate.value);
39+
// NOTE(kmitkin): if we don't get deadlock here, then we able to make a
40+
// progress,
41+
// so we have to check spinlock condition again
42+
threads[i].back()->SetSpinLocked(false);
43+
debug(stderr, "blocked on %p val %d\n", threads[i].back()->fstate.addr,
44+
threads[i].back()->fstate.value);
4545
// dual waiting if request finished, but follow up isn't
4646
// skip dual tasks that already have finished the request
4747
// section(follow-up will be executed in another task, so we can't
@@ -55,10 +55,11 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
5555
}
5656
}
5757

58-
if (round_robin_stage > 0) {
58+
if (round_robin_stage > 0) [[unlikely]] {
5959
for (size_t attempt = 0; attempt < threads.size(); ++attempt) {
6060
auto i = (++last_chosen) % threads.size();
6161
if (!threads[i].empty() && threads[i].back()->IsBlocked()) {
62+
threads[i].back()->SetSpinLocked(false);
6263
continue;
6364
}
6465
index_of_max = i;
@@ -72,7 +73,7 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
7273
}
7374

7475
// TODO: Choose wiser constant
75-
if (count_chosen_same == 1000 && index_of_max == last_chosen) {
76+
if (count_chosen_same == 1000 && index_of_max == last_chosen) [[unlikely]] {
7677
round_robin_stage = 5;
7778
round_robin_start = index_of_max;
7879
}
@@ -83,8 +84,9 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
8384
count_chosen_same = 1;
8485
}
8586

86-
assert(max != std::numeric_limits<int>::min() &&
87-
"all threads are empty or blocked");
87+
if (max == std::numeric_limits<size_t>::min()) [[unlikely]] {
88+
return std::nullopt;
89+
}
8890

8991
// Check whether the priority change is required
9092
current_schedule_length++;
@@ -94,25 +96,26 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
9496
}
9597
}
9698

97-
// debug(stderr, "Chosen thread: %d, cnt_count: %d\n", index_of_max,
98-
// count_chosen_same);
99+
debug(stderr, "Chosen thread: %d, cnt_count: %d\n", index_of_max,
100+
count_chosen_same);
99101
last_chosen = index_of_max;
100102
return index_of_max;
101103
}
102104

103105
// NOTE: `Next` version use heuristics for livelock avoiding, but not there
104106
// refactor later to avoid copy-paste
105-
TaskWithMetaData NextSchedule() override {
107+
std::optional<TaskWithMetaData> NextSchedule() override {
106108
auto& round_schedule = this->round_schedule;
107109
auto& threads = this->threads;
108-
int max = std::numeric_limits<int>::min();
110+
size_t max = std::numeric_limits<size_t>::min();
109111
size_t index_of_max = 0;
110112
// Have to ignore waiting threads, so can't do it faster than O(n)
111113
for (size_t i = 0; i < threads.size(); ++i) {
112114
int task_index = this->GetNextTaskInThread(i);
113115
// Ignore waiting tasks
114116
if (task_index == threads[i].size() ||
115117
threads[i][task_index]->IsBlocked()) {
118+
threads[i][task_index]->SetSpinLocked(false);
116119
// dual waiting if request finished, but follow up isn't
117120
// skip dual tasks that already have finished the request
118121
// section(follow-up will be executed in another task, so we can't
@@ -126,7 +129,7 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
126129
}
127130
}
128131

129-
if (round_robin_stage > 0) {
132+
if (round_robin_stage > 0) [[unlikely]] {
130133
for (size_t attempt = 0; attempt < threads.size(); ++attempt) {
131134
auto i = (++last_chosen) % threads.size();
132135
int task_index = this->GetNextTaskInThread(i);
@@ -145,7 +148,7 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
145148
}
146149

147150
// TODO: Choose wiser constant
148-
if (count_chosen_same == 1000 && index_of_max == last_chosen) {
151+
if (count_chosen_same == 1000 && index_of_max == last_chosen) [[unlikely]] {
149152
round_robin_stage = 5;
150153
round_robin_start = index_of_max;
151154
}
@@ -155,6 +158,11 @@ struct PctStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
155158
} else {
156159
count_chosen_same = 1;
157160
}
161+
162+
if (max == std::numeric_limits<size_t>::min()) {
163+
return std::nullopt;
164+
}
165+
158166
last_chosen = index_of_max;
159167
// Picked thread is `index_of_max`
160168
int next_task_index = this->GetNextTaskInThread(index_of_max);

runtime/include/pick_strategy.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,24 @@
88

99
template <typename TargetObj, StrategyVerifier Verifier>
1010
struct PickStrategy : public BaseStrategyWithThreads<TargetObj, Verifier> {
11-
virtual size_t Pick() = 0;
11+
virtual std::optional<size_t> Pick() = 0;
1212

13-
virtual size_t PickSchedule() = 0;
13+
virtual std::optional<size_t> PickSchedule() = 0;
1414

1515
explicit PickStrategy(size_t threads_count,
1616
std::vector<TaskBuilder> constructors)
1717
: BaseStrategyWithThreads<TargetObj, Verifier>(threads_count,
1818
constructors) {}
1919

20-
size_t NextThreadId() override { return Pick(); }
20+
std::optional<size_t> NextThreadId() override { return Pick(); }
2121

22-
TaskWithMetaData NextSchedule() override {
22+
std::optional<TaskWithMetaData> NextSchedule() override {
2323
auto& round_schedule = this->round_schedule;
24-
size_t current_thread = PickSchedule();
24+
auto current_thread_opt = PickSchedule();
25+
if (!current_thread_opt.has_value()) {
26+
return std::nullopt;
27+
}
28+
size_t current_thread = current_thread_opt.value();
2529
int next_task_index = this->GetNextTaskInThread(current_thread);
2630
bool is_new = round_schedule[current_thread] != next_task_index;
2731

0 commit comments

Comments
 (0)