-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
coroutine
724 lines (612 loc) · 31.4 KB
/
coroutine
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
/*
Copyright (C) 2018-2024 Geoffrey Daniels. https://gpdaniels.com/
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License only.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef GTL_EXECUTION_COROUTINE_HPP
#define GTL_EXECUTION_COROUTINE_HPP
// Summary: Setjump/Longjump implementation of stackful coroutines.
#ifndef NDEBUG
# if defined(_MSC_VER)
# define __builtin_trap() __debugbreak()
# endif
/// @brief A simple assert macro to break the program if the coroutine is misused.
# define GTL_COROUTINE_ASSERT(ASSERTION, MESSAGE) static_cast<void>((ASSERTION) || (__builtin_trap(), 0))
#else
/// @brief At release time the assert macro is implemented as a nop.
# define GTL_COROUTINE_ASSERT(ASSERTION, MESSAGE) static_cast<void>(0)
#endif
#if (defined(linux) || defined(__linux) || defined(__linux__)) || defined(__APPLE__)
#if defined(_FORTIFY_SOURCE)
#define GTL_COROUTINE_UNFORTIFY_SOURCE
#pragma push_macro("_FORTIFY_SOURCE")
#undef _FORTIFY_SOURCE
#endif
#include <csetjmp>
#if defined(GTL_COROUTINE_UNFORTIFY_SOURCE)
#undef GTL_COROUTINE_UNFORTIFY_SOURCE
#pragma pop_macro("_FORTIFY_SOURCE")
#endif
#include <csignal>
#include <cstdint>
#include <cstdlib>
// Valgrind.
# if !defined(NDEBUG) && __has_include(<valgrind/valgrind.h>)
#include <valgrind/valgrind.h>
#define GTL_COROUTINE_HAVE_VALGRIND 1
# else
#define GTL_COROUTINE_HAVE_VALGRIND 0
# endif
#endif
#if defined(_WIN32)
# if defined(_MSC_VER)
# pragma warning(push, 0)
# endif
# define WIN32_LEAN_AND_MEAN
# define VC_EXTRALEAN
# define STRICT
# include <windows.h>
# if defined(_MSC_VER)
# pragma warning(pop)
# endif
# define sig_atomic_t int
#endif
#if defined(_MSC_VER)
# pragma warning(push, 0)
#endif
#include <mutex>
#include <thread>
#if defined(_MSC_VER)
# pragma warning(pop)
#endif
namespace gtl {
// Predeclarations.
class coroutine;
namespace this_coroutine {
static coroutine* get_self();
}
// The process to create a coroutine follows the proces described in the below paper:
// R.Engleschall's Portable Multithreading: "The Signal Stack Trick for User-Space Thread Creation" in Proceedings of the USENIX Annual Technical Conference, 2000
/// @brief The coroutine class creates and stores a stack and an execution context to enable non-pre-emptive threading of functions.
class coroutine final {
public:
# if !defined(_WIN32)
/// @brief The alignement of the stack memory.
constexpr static const unsigned long long stack_alignment = 16;
/// @brief The amount of memory to allocate for the stack of the coroutine.
constexpr static const unsigned long long stack_size = 32768; // Would use SIGSTKSZ but that macro calls a non-constexpr function.
/// @brief The id of the signal raised to trigger a signal handler.
constexpr static const int trigger_signal_identifier = SIGUSR1;
# endif
private:
/// @brief Friend accessor function that is allowed to access the coroutines private state.
friend coroutine* this_coroutine::get_self();
private:
/// @brief Each process can only have one signal handler, therefore only one thread can create a coroutine at a time, enforce this with a mutex.
static inline std::mutex signal_handler_mutex;
/// @brief Store a pointer to the currently executing coroutine as a global thread_local variable.
static inline thread_local coroutine* current = nullptr;
# if defined(_WIN32)
/// @brief Store the root thread fiber pointer
static inline thread_local void* thread_fiber = nullptr;
# endif
public:
/// @brief An identifier type used to uniquely identify coroutines.
class id final {
private:
/// @brief The identifier value.
unsigned long long int value;
public:
/// @brief Construct an identifier type from an integer.
/// @param unique_value The integer used as the id value.
explicit id(unsigned long long int unique_value = 0)
: value(unique_value) {
}
/// @brief The less than operator is the only one defined using the identifier value, all other operators are defined using this one.
/// @param rhs The right hand side of the comparison.
constexpr bool operator < (const id& rhs) const {
return (this->value < rhs.value);
}
/// @brief The greater than operator defined using the less than operator with the sides of the comparison reversed.
/// @param rhs The right hand side of the comparison.
constexpr bool operator > (const id& rhs) const {
return rhs < *this;
}
/// @brief The less than or equal to operator defined using the greater than operator and inverted.
/// @param rhs The right hand side of the comparison.
constexpr bool operator <=(const id& rhs) const {
return !(*this > rhs);
}
/// @brief The greater than or equal to operator defined using the less than operator and inverted.
/// @param rhs The right hand side of the comparison.
constexpr bool operator >=(const id& rhs) const {
return !(*this < rhs);
}
/// @brief The inequality operator defined using the less than and greater than operators.
/// @param rhs The right hand side of the comparison.
constexpr bool operator !=(const id& rhs) const {
return (*this < rhs) || (*this > rhs);
}
/// @brief The equality operator defined using the inequality operator and inverted.
/// @param rhs The right hand side of the comparison.
constexpr bool operator ==(const id& rhs) const {
return !(*this != rhs);
}
};
/// @brief Simple lambda class for containing a void() lambda.
class lambda final {
private:
/// @brief A pointer to a heap allocated copy of the function.
void* function;
/// @brief A lambda function to copy the function.
void* (*copier)(void*);
/// @brief A lambda function to execute the function.
void (*executor)(void*);
/// @brief A lambda function to delete the function.
void (*deleter)(void*);
public:
/// @brief The destructor calls the deleter function if it exists to cleanup the lambda function.
~lambda() {
if (this->deleter != nullptr) {
this->deleter(this->function);
}
}
/// @brief The default constructor sets all internal variables to nullptrs.
lambda()
: function(nullptr)
, copier(nullptr)
, executor(nullptr)
, deleter(nullptr) {
}
/// @brief The copy constructor copies variables from another object, if there is a copier function it is used to copy the function.
/// @param other The lambda to copy.
lambda(const lambda& other)
: function(other.copier ? other.copier(other.function) : nullptr)
, copier(other.copier)
, executor(other.executor)
, deleter(other.deleter) {
}
/// @brief Defaulted move constructor.
lambda(lambda&&) noexcept = default;
/// @brief The copy assignment operator copies variables from another object, if there is a copier function it is used to copy the function.
/// @param other The lambda to copy.
lambda& operator=(const lambda& other) {
if (this != &other) {
this->function = other.copier ? other.copier(other.function) : nullptr;
this->copier = other.copier;
this->executor = other.executor;
this->deleter = other.deleter;
}
return *this;
}
/// @brief Defaulted move assignment operator.
lambda& operator=(lambda&&) noexcept = default;
/// @brief Constructor from a function type copies a provided function to a heap allocated internal function,.
/// @param raw_function The function to wrap.
template <typename function_type>
lambda(const function_type& raw_function)
: function(new function_type(raw_function))
, copier([](void* function_pointer) -> void* {
if (function_pointer) {
return new function_type(*reinterpret_cast<function_type*>(function_pointer));
}
return nullptr;
})
, executor([](void* function_pointer) -> void {
return reinterpret_cast<function_type*>(function_pointer)->operator()();
})
, deleter([](void* function_pointer) -> void {
delete reinterpret_cast<function_type*>(function_pointer);
}) {
}
/// @brief Copy assignement operator from a function type copies a provided function to a heap allocated internal function,.
/// @param raw_function The function to wrap.
template <typename function_type>
lambda& operator=(const function_type& raw_function) {
if (this->function != nullptr) {
this->deleter(this->function);
}
this->function = new function_type(raw_function);
this->copier = [](void* function_pointer) -> void* {
if (function_pointer) {
return new function_type(*reinterpret_cast<function_type*>(function_pointer));
}
return nullptr;
};
this->executor = [](void* function_pointer) -> void {
return reinterpret_cast<function_type*>(function_pointer)->operator()();
};
this->deleter = [](void* function_pointer) -> void {
delete reinterpret_cast<function_type*>(function_pointer);
};
return *this;
}
public:
/// @brief The function call operator is overloaded to call the internal function.
void operator()() {
this->executor(this->function);
}
};
private:
# if !defined(_WIN32)
/// @brief Jump buffer to used to enter the coroutine.
sigjmp_buf coroutine_context;
/// @brief Jump buffer used to exit the coroutine.
sigjmp_buf parent_context;
/// @brief The signal set of the program at coroutine creation saved as a member variable so it can be restored in the coroutine context.
sigset_t parent_signal_set;
# else
/// @brief Fibre used to exit the coroutine.
void* parent_stack;
# endif
/// @brief Atomic flag that is set by the signal handler and checked by the constructor to ensure creation stages procede in order.
volatile sig_atomic_t signal_raised;
/// @brief Coroutine stack allocated on the heap stack using new.
void* stack;
# if GTL_COROUTINE_HAVE_VALGRIND
unsigned int valgrind_stack_id;
# endif
/// @brief Function to call from the coroutine context.
lambda function;
public:
/// @brief Destructor ensures the coroutine is finished.
~coroutine() {
// Ensure joined or throw.
if (this->joinable()) {
std::terminate();
}
# if !defined(_WIN32)
// Cleanup stack.
if (this->stack) {
std::free(this->stack);
}
// If we are testing with valgrind deregister the coroutine stack.
# if GTL_COROUTINE_HAVE_VALGRIND
VALGRIND_STACK_DEREGISTER(this->valgrind_stack_id);
# endif
# else
// Cleanup stack.
if (this->stack) {
DeleteFiber(this->stack);
}
# endif
}
/// @brief Default constructor does nothing.
coroutine()
: signal_raised(0)
, stack(nullptr) {
}
/// @brief Copy constructor is explicitly deleted.
coroutine(const coroutine&) = delete;
/// @brief Move constructor cannot be default as the default leaves other in an invalid state.
coroutine(coroutine&& other) noexcept
: coroutine() {
*this = std::move(other);
}
/// @brief Copy assignment operator is explicitly deleted.
coroutine& operator=(const coroutine&) = delete;
/// @brief Move assignment operator cannot be default as the default leaves other in an invalid state.
coroutine& operator=(coroutine&& other) noexcept {
// Ensure we're not moving on top of ourself.
if (this == &other) {
return *this;
}
// Swap all the member variables with those from other.
# if !defined(_WIN32)
std::swap(this->coroutine_context, other.coroutine_context);
std::swap(this->parent_context, other.parent_context);
std::swap(this->parent_signal_set, other.parent_signal_set);
# else
std::swap(this->parent_stack, other.parent_stack);
# endif
std::swap(this->signal_raised, other.signal_raised);
std::swap(this->stack, other.stack);
std::swap(this->function, other.function);
// Return this, other will be destructed and cleanup after itself.
return *this;
}
/// @brief Constructor creates a coroutine context, taking a function as an argument to call from the coroutine context.
/// @param coroutine_function Function to call from the coroutine context.
/// @param coroutine_arguments Function arguments to provide to the function at call time.
template <typename function_type, typename... argument_types>
coroutine(function_type&& coroutine_function, argument_types&&... coroutine_arguments)
# if !defined(_WIN32)
: signal_raised(0)
, stack(operator new(coroutine::stack_size + (coroutine::stack_alignment - 1))) {
# else
: stack(nullptr) {
# endif
# if !defined(_WIN32)
// If we are testing with valgrind register the coroutine stack.
# if GTL_COROUTINE_HAVE_VALGRIND
this->valgrind_stack_id = VALGRIND_STACK_REGISTER(this->stack, static_cast<unsigned char*>(this->stack) + coroutine::stack_size);
# endif
// Use a lock guard so the mutex is unlocked automatically when we return from the construtor.
std::lock_guard<std::mutex> signal_handler_lock(signal_handler_mutex);
// Cast the lock guard to void to prevent unused variable warnings.
static_cast<void>(signal_handler_lock);
// Step 1:
// Preserve the current signal mask and block an arbitrary worker signal.
// coroutine::trigger_signal_identifier is used here, but any signal can be used for this (even an already used one).
// This worker signal is later temporarily required for the trampoline step.
sigset_t coroutine_signal_set = {};
if (sigemptyset(&coroutine_signal_set) != 0) {
std::terminate();
}
if (sigaddset(&coroutine_signal_set, coroutine::trigger_signal_identifier) != 0) {
std::terminate();
}
sigset_t local_parent_signal_set = {};
if (sigprocmask(SIG_BLOCK, &coroutine_signal_set, &local_parent_signal_set) != 0) {
std::terminate();
}
// Step 2:
// Preserve a possibly existing signal action for the worker signal and configure a trampoline function as the new temporary signal action.
// The signal delivery is configured to occur on an alternate signal stack (see next step).
struct sigaction coroutine_signal_action = {};
coroutine_signal_action.sa_handler = signal_handler_trampoline;
coroutine_signal_action.sa_flags = SA_ONSTACK;
if (sigemptyset(&coroutine_signal_action.sa_mask) != 0) {
std::terminate();
}
struct sigaction parent_signal_action = {};
if (sigaction(coroutine::trigger_signal_identifier, &coroutine_signal_action, &parent_signal_action) != 0) {
std::terminate();
}
// Step 3:
// Preserve a possibly active alternate signal stack and configure the memory chunk starting at this->stack as the new temporary alternate signal stack of length size.
stack_t signal_stack = {};
signal_stack.ss_sp = reinterpret_cast<void*>((reinterpret_cast<std::uintptr_t>(this->stack) + (coroutine::stack_alignment - 1)) & ~std::uintptr_t(coroutine::stack_alignment - 1));
signal_stack.ss_size = coroutine::stack_size;
signal_stack.ss_flags = 0;
stack_t parent_signal_stack = {};
if (sigaltstack(&signal_stack, &parent_signal_stack) != 0) {
std::terminate();
}
// Step 4:
// Save parameters for the trampoline step in member variables.
// Send the current process the worker signal, temporarily unblock it and this way allow it to be delivered on the signal stack.
// This transfers execution control to the trampoline function.
this->function = [coroutine_function, coroutine_arguments...](){ coroutine_function(coroutine_arguments...); };
this->parent_signal_set = local_parent_signal_set;
this->signal_raised = 0;
// Store the currently executing coroutine in previous.
coroutine* parent_coroutine = gtl::coroutine::current;
// Save the new coroutine in current.
gtl::coroutine::current = this;
// Raise the signal.
if (raise(coroutine::trigger_signal_identifier) != 0) {
std::terminate();
}
// Valgrind seems to think the creation of alternative context is dodgy.
// Therefore the only sensible solution is to disable valgrind error reporting!
# if GTL_COROUTINE_HAVE_VALGRIND
VALGRIND_DISABLE_ERROR_REPORTING;
# endif
// Unblock and wait for the signal by suspending the current thread.
sigfillset(&coroutine_signal_set);
sigdelset(&coroutine_signal_set, coroutine::trigger_signal_identifier);
while(!this->signal_raised) {
if (sigsuspend(&coroutine_signal_set) != -1) {
std::terminate();
}
}
// Step 6:
// Restore the preserved alternate signal stack, preserved signal action and preserved signal mask for worker signal.
// This way an existing application configuration for the worker signal is restored.
if (sigaltstack(nullptr, &signal_stack) != 0) {
std::terminate();
}
signal_stack.ss_flags = SS_DISABLE;
if (sigaltstack(&signal_stack, nullptr) != 0) {
std::terminate();
}
if (sigaltstack(nullptr, &signal_stack) != 0) {
std::terminate();
}
if (!(parent_signal_stack.ss_flags & SS_DISABLE)) {
if (sigaltstack(&parent_signal_stack, nullptr) != 0) {
std::terminate();
}
}
if (sigaction(coroutine::trigger_signal_identifier, &parent_signal_action, nullptr) != 0) {
std::terminate();
}
if (sigprocmask(SIG_SETMASK, &parent_signal_set, nullptr) != 0) {
std::terminate();
}
// Step 7:
// Save the current machine context of the Create function.
// This allows us to return to this point after the next trampoline step.
if (sigsetjmp(this->parent_context, 0) == 0) {
// Step 8:
// Restore the previously saved machine context of the trampoline function to again transfer execution control onto the alternate stack.
// This time without(!) signal handler scope.
siglongjmp(this->coroutine_context, 1);
}
// Restore the currently running coroutine.
gtl::coroutine::current = parent_coroutine;
// Step 14:
// Return to the calling application.
# else
// Save parameters for the coroutine in member variables.
this->function = [coroutine_function, coroutine_arguments...](){ coroutine_function(coroutine_arguments...); };
// Create the fiber based coroutine.
this->stack = CreateFiber(0, [](LPVOID) {
// The coroutine function call.
gtl::coroutine::current->function();
// Clear the signal raised flag to indicate the coroutine is finished.
gtl::coroutine::current->signal_raised = 0;
// Yield to another coroutine or the root.
gtl::coroutine::current->yield();
}, nullptr);
// Check to see if the fiber failed to be created.
if (this->stack == nullptr) {
return;
}
// Set flag to indicate the coroutine is ready.
this->signal_raised = 1;
# endif
}
private:
# if !defined(_WIN32)
/// @brief Signal handler, called by the os with a new stack when this class calls raise.
/// @param signal_identifier The identifying code of the raised signal.
static void signal_handler_trampoline(int signal_identifier) {
// Don't need to validate which signal it is as this routine only handles one.
static_cast<void>(signal_identifier);
// Step 5:
// After the trampoline function asynchronously entered, save its machine context in the structure.
// Then immediately return from it to terminate the signal handler scope.
if (sigsetjmp(gtl::coroutine::current->coroutine_context, 0) == 0) {
gtl::coroutine::current->signal_raised = 1;
return;
}
// Step 9:
// After reaching the trampoline function again, immediately bootstrap into a clean stack frame by calling a second function.
coroutine::bootstrap();
}
/// @brief Container function jumped to and from when joining and yielding from a coroutine.
[[noreturn]]
static void bootstrap() {
// Re-enable valgrind error reporting.
# if GTL_COROUTINE_HAVE_VALGRIND
VALGRIND_ENABLE_ERROR_REPORTING;
# endif
// Beware this function cannot be a member function, because the coroutine could have been moved while we were away.
// Moving the coroutine changes the "this" pointer, therefore the code "this->function()" would be invalid.
// Step 10:
// Set the new signal mask to be the same as the original signal mask which was active when the coroutine was created.
// This is required because in the first trampoline step we usually had at least the worker signal blocked.
if (sigprocmask(SIG_SETMASK, >l::coroutine::current->parent_signal_set, nullptr) != 0) {
std::terminate();
}
// Step 11:
// Load the passed startup information (the function) from the constructor into local (stack based) variables.
// This is not required as the coroutine is an raii class.
//lambda function = gtl::coroutine::current->function;
// Step 12:
// Save the current machine context for later restoring by the calling application.
if (sigsetjmp(gtl::coroutine::current->coroutine_context, 0) == 0) {
// Step 13:
// Restore the previously saved machine context of the coroutine to transfer execution control back to it.
siglongjmp(gtl::coroutine::current->parent_context, 1);
}
// The coroutine function call.
gtl::coroutine::current->function();
// Clear the signal raised flag to indicate the coroutine is finished.
gtl::coroutine::current->signal_raised = 0;
// Exit by jumping to the creator location.
siglongjmp(gtl::coroutine::current->parent_context, 1);
// Silence warning about function returning.
std::abort();
}
# endif
public:
/// @brief Get the unique identifier of the coroutine.
/// @return The unique identider.
id get_id() const {
return id(reinterpret_cast<unsigned long long int>(this->stack)) ;
}
/// @brief Checks whether the coroutine is joinable, i.e. unfinished.
/// @return True if the coroutine is unfinished, false otherwise.
bool joinable() {
return this->signal_raised;
}
/// @brief Switches to the coroutine's execution context and continues execution, i.e. runs the coroutine.
void join() {
GTL_COROUTINE_ASSERT(this->joinable(), "Coroutine must be joinable to be joined.");
// Store the currently running coroutine.
coroutine* parent_coroutine = gtl::coroutine::current;
// Set the now running coroutine.
gtl::coroutine::current = this;
# if !defined(_WIN32)
// Switch execution contexts.
if (sigsetjmp(gtl::coroutine::current->parent_context, 0) == 0) {
siglongjmp(gtl::coroutine::current->coroutine_context, 1);
}
# else
// If there is no parent coroutine then convert this thread to a fiber.
if (!gtl::coroutine::thread_fiber) {
gtl::coroutine::thread_fiber = ConvertThreadToFiber(nullptr);
}
// Save parent inside the coroutine to enable the yield function.
gtl::coroutine::current->parent_stack = (parent_coroutine != nullptr) ? parent_coroutine->stack : gtl::coroutine::thread_fiber;
// Switch execution contexts.
SwitchToFiber(gtl::coroutine::current->stack);
// Reset the parent stack.
gtl::coroutine::current->parent_stack = nullptr;
// If the current coroutine is null convert back to a thread.
if (!parent_coroutine) {
ConvertFiberToThread();
gtl::coroutine::thread_fiber = nullptr;
}
# endif
// Upon return reset the currently running coroutine.
gtl::coroutine::current = parent_coroutine;
}
/// @brief Yield from the coroutine, i.e. force a return to the parent execution context.
void yield() {
GTL_COROUTINE_ASSERT(gtl::coroutine::current == this, "This coroutine must be running to yield.");
# if !defined(_WIN32)
if (sigsetjmp(gtl::coroutine::current->coroutine_context, 0) == 0) {
siglongjmp(gtl::coroutine::current->parent_context, 1);
}
# else
SwitchToFiber(gtl::coroutine::current->parent_stack);
# endif
}
};
}
namespace gtl {
/// @brief The this_coroutine class cannot be instantiated and provides static functions to operate on the current coroutine.
namespace this_coroutine {
/// @brief Get a pointer to the currently running coroutine if there is one.
/// @return A pointer to the currently running coroutine or a nullptr if there isn't one.
[[maybe_unused]] static coroutine* get_self() {
return gtl::coroutine::current;
}
/// @brief Get the unique identifier of the currently running coroutine if there is one.
/// @return The unique identider of the currently running coroutine or a null id if there isn't one.
[[maybe_unused]] static gtl::coroutine::id get_id() {
if (get_self() == nullptr) {
return coroutine::id();
}
return get_self()->get_id();
}
/// @brief Yield from the currently running coroutine, i.e. force a return to the parent execution context.
[[maybe_unused]] static void yield() {
GTL_COROUTINE_ASSERT(get_self() != nullptr, "A coroutine must be running to yield.");
get_self()->yield();
}
/// @brief Sleep a coroutine for an amount of time, provided to match the std::thread api.
/// @param duration Length of time to sleep for.
template<typename number_of_ticks_type, typename period_type>
[[maybe_unused]] static void sleep_for(const std::chrono::duration<number_of_ticks_type, period_type>& duration) {
GTL_COROUTINE_ASSERT(get_self() != nullptr, "A coroutine must be running to sleep.");
std::this_thread::sleep_for(duration);
}
/// @brief Sleep a coroutine until a specific time, provided to match the std::thread api.
/// @param time_point Time to sleep until.
template<typename clock_type, typename duration_type>
[[maybe_unused]] static void sleep_until(const std::chrono::time_point<clock_type, duration_type>& time_point) {
GTL_COROUTINE_ASSERT(get_self() != nullptr, "A coroutine must be running to sleep.");
std::this_thread::sleep_until(time_point);
}
}
}
// Undo the siglongjmp override.
#ifdef __GLIBC__
# undef siglongjmp
#endif
#undef GTL_COROUTINE_HAVE_VALGRIND
#undef GTL_COROUTINE_ASSERT
#endif // GTL_EXECUTION_COROUTINE_HPP