-
Notifications
You must be signed in to change notification settings - Fork 0
/
coroutine.cc
217 lines (167 loc) · 5.96 KB
/
coroutine.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//
// Created by xvvx on 18-12-2.
//
#include "coroutine.h"
#include <ucontext.h>
#include <iostream>
#include <list>
#include <sstream>
namespace ecoroutine {
namespace impl {
// states of coroutine
// initialized state is kReady, and the current running coroutine's state is kRunning.
// a coroutine's state will change to kHangUp if calling ecoroutine::yield().
// in the end, it will be dead.
enum class CoroutineState {
kReady,
kRunning,
kHangUp,
kDead
};
// tool function for handling error
static inline void HandleError(const std::string &msg) {
perror(msg.c_str());
exit(EXIT_FAILURE);
}
// pre-decleration
class Scheduler;
class Coroutine;
static void schedule();
//default size of coroutine stack
constexpr u_int32_t kStackSize = 1024 * 1024;
class Coroutine {
public:
Coroutine(coroutine_t id, CoroutineFunc &func);
~Coroutine() {
delete stack_;
}
static void RunCoroutineFunc(uint32_t high, uint32_t low) {
auto *c= reinterpret_cast<Coroutine*>((static_cast<size_t>(high) << 32) + low);
c->func_();
c->state_ = CoroutineState::kDead;
schedule();
}
char *stack_;
CoroutineFunc func_;
coroutine_t id_;
ucontext_t context_;
CoroutineState state_;
};
Coroutine::Coroutine(coroutine_t id, CoroutineFunc &func):
id_(id),
func_(func),
state_(CoroutineState::kReady) {
stack_ = (char*)malloc(kStackSize);
getcontext(&context_);
context_.uc_stack.ss_sp = stack_;
context_.uc_stack.ss_size = kStackSize;
context_.uc_link = nullptr;
makecontext(&context_, reinterpret_cast<void(*)()>(RunCoroutineFunc), 2,
reinterpret_cast<size_t>(this) >> 32, this);
}
class Scheduler {
public:
using CoroutinePtr = std::shared_ptr<Coroutine>;
using CoroutinePtrList = std::list<CoroutinePtr>;
using CoroutinePtrHashMap = std::unordered_map<coroutine_t, CoroutinePtr>;
public:
Scheduler() = default;
~Scheduler() = default;
coroutine_t Create(CoroutineFunc &func);
void Run(coroutine_t c);
void Yield();
void DoSchedule();
coroutine_t RunningId() {
return running_id_;
}
private:
CoroutinePtr GetCurrentCoroutine();
private:
CoroutinePtrHashMap id_map_;
CoroutinePtrList all_coroutines_;
CoroutinePtrList runnable_coroutines_;
ucontext_t main_context_;
coroutine_t next_id_ { 1 };
coroutine_t running_id_ { 0 };
};
coroutine_t Scheduler::Create(CoroutineFunc &func) {
auto new_coroutine_ptr = std::make_shared<Coroutine>(next_id_, func);
runnable_coroutines_.push_back(new_coroutine_ptr);
all_coroutines_.push_back(new_coroutine_ptr);
id_map_[new_coroutine_ptr->id_] = new_coroutine_ptr;
++next_id_;
return new_coroutine_ptr->id_;
}
void Scheduler::DoSchedule() {
//current is main coroutine
if (running_id_ == 0) {
if (!runnable_coroutines_.empty()) {
CoroutinePtr next = runnable_coroutines_.front();
runnable_coroutines_.pop_front();
next->state_ = CoroutineState::kRunning;
running_id_ = next->id_;
swapcontext(&main_context_, &next->context_);
//if come back from dead coroutine
if (next->state_ == CoroutineState::kDead) {
id_map_.erase(next->id_);
runnable_coroutines_.remove(next);
all_coroutines_.remove(next);
}
}
} else {
CoroutinePtr curr = GetCurrentCoroutine();
ucontext_t *curr_context = &curr->context_;
if(curr->state_==CoroutineState::kDead){
curr.reset();
}
//switch to main coroutine
running_id_ = 0;
swapcontext(curr_context, &main_context_);
}
}
void Scheduler::Yield() {
CoroutinePtr curr = GetCurrentCoroutine();
if (curr == nullptr) {
HandleError("can't yield main coroutine!\n");
}
curr->state_ = CoroutineState::kHangUp;
runnable_coroutines_.push_back(curr);
running_id_ = 0;
swapcontext(&curr->context_, &main_context_);
}
void Scheduler::Run(coroutine_t c) {
CoroutinePtr p = nullptr;
if (id_map_.count(c)) {
p = id_map_[c];
} else {
HandleError("no such coroutine_t");
}
p->state_ = CoroutineState::kReady;
DoSchedule();
}
Scheduler::CoroutinePtr Scheduler::GetCurrentCoroutine() {
CoroutinePtr curr = nullptr;
if (id_map_.count(running_id_)) {
curr = id_map_[running_id_];
}
return curr;
}
static Scheduler scheduler;
static void schedule() {
scheduler.DoSchedule();
}
}; // namespace impl
//function for users
coroutine_t create(CoroutineFunc &func) {
return impl::scheduler.Create(func);
}
coroutine_t self() {
return impl::scheduler.RunningId();
}
void yield() {
impl::scheduler.Yield();
}
void run(coroutine_t c) {
impl::scheduler.Run(c);
}
};