Zth (libzth)
Loading...
Searching...
No Matches
fiber.h
Go to the documentation of this file.
1#ifndef ZTH_FIBER_H
2#define ZTH_FIBER_H
3/*
4 * SPDX-FileCopyrightText: 2019-2026 Jochem Rutgers
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 */
8
18#include <libzth/macros.h>
19
24typedef struct {
25 void* p;
27
28#ifdef __cplusplus
29# include <libzth/allocator.h>
30# include <libzth/backtrace.h>
31# include <libzth/config.h>
32# include <libzth/context.h>
33# include <libzth/exception.h>
34# include <libzth/init.h>
35# include <libzth/list.h>
36# include <libzth/perf.h>
37# include <libzth/time.h>
38# include <libzth/util.h>
39
40# include <errno.h>
41# include <exception>
42# include <utility>
43
44# if __cplusplus >= 201103L
45# include <functional>
46# endif
47
48namespace zth {
49
50// See libzth/worker.h
51void outOfWork();
52
62class Fiber : public Listable, public UniqueID<Fiber>, public RefCounted {
65public:
66 typedef void(FiberHook)(Fiber&);
67
74
81
83
85 typedef void (*Entry)(EntryArg);
86 static uintptr_t const ExitUnknown = (uintptr_t)-1;
87
88 explicit Fiber(Entry entry, EntryArg arg = EntryArg())
89 : UniqueID("zth::Fiber")
90 , m_state(Uninitialized)
91 , m_stateNext(Ready)
92 , m_entry(entry)
93 , m_entryArg(arg)
94 , m_stackSize(Config::DefaultFiberStackSize)
95 , m_context()
96 , m_fls()
97 , m_timeslice(Config::MinTimeslice())
98 , m_dtMax(Config::CheckTimesliceOverrun
99 ? TimeInterval(Config::TimesliceOverrunReportThreshold())
100 : TimeInterval())
101 {
102 zth_init();
103 setState(New);
104 zth_assert(m_entry);
105 zth_dbg(fiber, "[%s] New fiber %p", id_str(), normptr());
106 }
107
108 virtual ~Fiber() noexcept override
109 {
110 m_exit.once(*this);
111 m_cleanup.once(*this);
112
113 if(state() > Uninitialized && state() < Dead)
114 kill();
115
117 zth_assert(!fls());
118 size_t stackSize_ __attribute__((unused)) = this->stackSize();
119 size_t stackUsage_ __attribute__((unused)) = this->stackUsage();
120 context_destroy(m_context);
121 m_context = nullptr;
122 zth_dbg(fiber, "[%s] Destructed. Stack usage: 0x%x of 0x%x, total CPU: %s",
123 id_str(), (unsigned int)stackUsage_, (unsigned int)stackSize_,
124 m_totalTime.str().c_str());
125 }
126
127 zth_fiber_t handle() const noexcept
128 {
129 // cppcheck-suppress cstyleCast
130 zth_fiber_t h = {(void*)this}; // NOLINT
131 return h;
132 }
133
134 static Fiber* fromHandle(zth_fiber_t const& h) noexcept
135 {
136 return (Fiber*)h.p; // NOLINT
137 }
138
139 int setStackSize(size_t size) noexcept
140 {
141 if(state() != New)
142 return EPERM;
143
144 m_stackSize = size;
145 return 0;
146 }
147
148 size_t stackSize() const noexcept
149 {
150 return m_stackSize;
151 }
152
153 size_t stackUsage() const
154 {
156 }
157
158 Context* context() const noexcept
159 {
160 return m_context;
161 }
162
163 State state() const noexcept
164 {
165 return m_state;
166 }
167
168 void* fls() const noexcept
169 {
170 return m_fls;
171 }
172
173 void setFls(void* data = nullptr) noexcept
174 {
175 m_fls = data;
176 }
177
178 Timestamp const& runningSince() const noexcept
179 {
180 return m_startRun;
181 }
182
183 Timestamp const& stateEnd() const noexcept
184 {
185 return m_stateEnd;
186 }
187
188 TimeInterval const& totalTime() const noexcept
189 {
190 return m_totalTime;
191 }
192
194
196 {
197 m_exit.add(f, arg);
198 }
199
201 {
202 m_cleanup.add(f, arg);
203 }
204
206 {
207 if(state() != New)
208 return EPERM;
209
210 zth_assert(!m_context);
211
212 zth_dbg(fiber, "[%s] Init", id_str());
213
214 ContextAttr attr(&fiberEntry, this, m_stackSize);
215 int res = context_create(m_context, attr);
216 if(res) {
217 // Oops.
218 kill();
219 return res;
220 }
221
222 setState(m_stateNext, now);
223 m_stateNext = Ready;
224 m_startRun = now;
225
226 return 0;
227 }
228
230 {
231 int res = 0;
232
233again:
234 switch(state()) {
235 case New:
236 // First call, do implicit init.
237 if((res = init(now)))
238 return res;
239
240 if(hookNew)
241 hookNew(*this);
242 goto again;
243
244 case Cancel:
245 case Ready: {
246 zth_assert(&from != this);
247
248 // Update administration of the current fiber.
249 TimeInterval dt = now - from.m_startRun;
250 from.m_totalTime += dt;
251
252 if(from.state() == Running)
253 from.setState(Ready, now);
254
255 if(unlikely(zth_config(CheckTimesliceOverrun) && from.m_dtMax < dt)) {
256 zth_perf_mark("timeslice overrun reported");
257 from.m_dtMax = dt;
258 log_color(
260 ZTH_DBG_PREFIX "Long timeslice by %s of %s\n",
261 from.id_str(), dt.str().c_str());
264 }
265
266 // Hand over to this.
267 m_startRun = now;
268 if(likely(state() != Cancel))
270 m_stateEnd = now + m_timeslice;
271
272 zth_dbg(fiber, "Switch from %s to %s after %s", from.id_str(), id_str(),
273 dt.str().c_str());
274 context_switch(from.context(), context());
275
276 // Ok, got back to the *from* fiber.
277 // Warning! The *this* fiber might already be dead and destructed at this
278 // point!
279
280 if(unlikely(from.state() == Cancel))
281 from.cancelled();
282
283 return 0;
284 }
285
286 case Running:
287 zth_assert(&from == this);
288 // No switch required.
289 return EAGAIN;
290
291 case Dead:
292 // Can't run dead fibers.
293 return EPERM;
294
295 case Uninitialized:
296 case Waiting:
297 case Suspended:
298 default:
299 zth_assert(false);
300 return EINVAL;
301 }
302 }
303
304 bool allowYield(Timestamp const& now = Timestamp::now()) const noexcept
305 {
306 return state() != Running || m_stateEnd < now;
307 }
308
309 void kill() noexcept
310 {
311 if(state() == Dead)
312 return;
313
314 zth_dbg(fiber, "[%s] Killed", id_str());
315 setState(Dead);
316 }
317
318 void cancel()
319 {
320 if(state() == Cancel)
321 return;
322
323 zth_dbg(fiber, "[%s] Cancel", id_str());
324
325 bool running = state() == Running;
326 if(state() != Dead)
328
329 if(running)
330 cancelled();
331 }
332
333protected:
335 {
336 zth_dbg(fiber, "[%s] Cancelled", id_str());
337# ifdef __cpp_exceptions
339# else
340 kill();
341 outOfWork();
342# endif
343 }
344
345public:
346 void nap(Timestamp const& sleepUntil = Timestamp::null()) noexcept
347 {
348 switch(state()) {
349 case New:
350 // Postpone actual sleep
351 zth_dbg(fiber, "[%s] Sleep upon startup", id_str());
352 m_stateNext = Waiting;
353 break;
354 case Ready:
355 case Running:
356 if(sleepUntil.isNull())
357 zth_dbg(fiber, "[%s] Sleep", id_str());
358 else
359 zth_dbg(fiber, "[%s] Sleep for %s", id_str(),
360 (sleepUntil - Timestamp::now()).str().c_str());
362 m_stateNext = Ready;
363 break;
364 case Suspended:
365 zth_dbg(fiber, "[%s] Schedule sleep after resume", id_str());
366 m_stateNext = Waiting;
367 break;
368 case Cancel:
369 case Waiting:
370 case Uninitialized:
371 case Dead:
372 default:
373 return;
374 }
375
376 m_stateEnd = sleepUntil;
377 }
378
379 void wakeup() noexcept
380 {
381 if(likely(state() == Waiting)) {
382 setState(m_stateNext);
383 switch(state()) {
384 case Suspended:
385 zth_dbg(fiber, "[%s] Suspend after wakeup", id_str());
386 m_stateNext = Ready;
387 break;
388 case Uninitialized:
389 case New:
390 case Ready:
391 case Running:
392 case Waiting:
393 case Cancel:
394 case Dead:
395 default:
396 zth_dbg(fiber, "[%s] Wakeup", id_str());
397 }
398 } else if(state() == Suspended && m_stateNext == Waiting) {
399 zth_dbg(fiber, "[%s] Set wakeup after suspend", id_str());
400 m_stateNext = Ready;
401 }
402 }
403
404 void suspend() noexcept
405 {
406 switch(state()) {
407 case New:
408 m_stateNext = New;
409 break;
410 case Running:
411 case Ready:
412 m_stateNext = Ready;
413 break;
414 case Waiting:
415 m_stateNext = Waiting;
416 // fall-through
417 case Suspended:
418 case Uninitialized:
419 case Cancel:
420 case Dead:
421 default:
422 // Ignore.
423 return;
424 }
425
426 zth_dbg(fiber, "[%s] Suspend", id_str());
428 }
429
430 void resume() noexcept
431 {
432 if(state() != Suspended)
433 return;
434
435 zth_dbg(fiber, "[%s] Resume", id_str());
436 setState(m_stateNext);
437 if(state() == New)
438 m_stateNext = Ready;
439 }
440
441 string str() const
442 {
443 string res = format("%s", id_str());
444
445 switch(state()) {
446 case Uninitialized:
447 res += " Uninitialized";
448 break;
449 case New:
450 res += " New";
451 break;
452 case Ready:
453 res += " Ready";
454 break;
455 case Running:
456 res += " Running";
457 break;
458 case Waiting:
459 res += " Waiting";
460 if(!m_stateEnd.isNull())
461 res +=
462 format(" (%s remaining)",
463 Timestamp::now().timeTo(stateEnd()).str().c_str());
464 break;
465 case Suspended:
466 res += " Suspended";
467 break;
468 case Cancel:
469 res += " Cancel";
470 break;
471 case Dead:
472 res += " Dead";
473 break;
474 default:;
475 }
476
477 res += format(" t=%s", m_totalTime.str().c_str());
478 return res;
479 }
480
481private:
482 virtual void changedName(string const& name) override
483 {
484 zth_dbg(fiber, "[%s] Renamed to %s", id_str(), name.c_str());
485
487 perf_fiber(*this);
488 }
489
490protected:
491 void setState(State state, Timestamp const& t = Timestamp::now()) noexcept
492 {
493 if(m_state == state)
494 return;
495
496 m_state = state;
497
499 perf_fiber_state(*this, m_state, t);
500
501 if(state == Dead && hookDead)
502 hookDead(*this);
503 }
504
505 static void fiberEntry(void* that) noexcept
506 {
507 zth_assert(that);
508 // cppcheck-suppress nullPointerRedundantCheck
509 static_cast<Fiber*>(that)->fiberEntry_();
510 }
511
512 void fiberEntry_() noexcept
513 {
514 zth_dbg(fiber, "[%s] Entry", id_str());
515
516 try {
517 m_entry(m_entryArg);
518 } catch(std::exception const& e) {
519# ifdef __cpp_exceptions
520 zth_dbg(fiber, "[%s] Uncaught exception; %s", id_str(), e.what());
521# endif
522 } catch(...) {
523 zth_dbg(fiber, "[%s] Uncaught exception", id_str());
524 }
525
526 zth_dbg(fiber, "[%s] Exit", id_str());
527 m_exit.once(*this);
528
529 kill();
530 }
531
532private:
533 State m_state;
534 State m_stateNext;
535 Entry m_entry;
536 EntryArg m_entryArg;
537 size_t m_stackSize;
538 Context* m_context;
539 void* m_fls;
540 TimeInterval m_totalTime;
541 Timestamp m_startRun;
542 Timestamp m_stateEnd;
543 TimeInterval m_timeslice;
544 TimeInterval m_dtMax;
545 Hook_type m_exit;
546 Hook_type m_cleanup;
547};
548
558class Runnable {
561protected:
562 constexpr Runnable() noexcept
563 : m_fiber()
564 {}
565
566public:
568
569 int run();
570
571 Fiber* fiber() const noexcept
572 {
573 return m_fiber;
574 }
575
576 operator Fiber&() const noexcept
577 {
578 // cppcheck-suppress constVariablePointer
579 Fiber* const f = fiber();
580 zth_assert(f);
581 // cppcheck-suppress nullPointerRedundantCheck
582 return *f;
583 }
584
585 char const* id_str() const
586 {
587 return fiber() ? fiber()->id_str() : "detached Runnable";
588 }
589
590protected:
591 virtual int fiberHook(Fiber& f)
592 {
593 f.atCleanup(&cleanup_, this);
594 m_fiber = &f;
595 return 0;
596 }
597
598 virtual void cleanup() noexcept
599 {
600 m_fiber = nullptr;
601 }
602
603 virtual void entry() = 0;
604
605private:
606 static void cleanup_(Fiber& UNUSED_PAR(f), void* that) noexcept
607 {
608 Runnable* r = static_cast<Runnable*>(that);
609 if(likely(r)) {
610 zth_assert(&f == r->m_fiber);
611 r->cleanup();
612 }
613 }
614
615 static void entry_(void* that)
616 {
617 if(likely(that))
618 static_cast<Runnable*>(that)->entry();
619 }
620
621private:
622 Fiber* m_fiber;
623};
624
625__attribute__((pure)) Fiber& currentFiber() noexcept;
626
631ZTH_EXPORT inline void* fls() noexcept
632{
633 return currentFiber().fls();
634}
635
643ZTH_EXPORT inline void setFls(void* data = nullptr) noexcept
644{
645 return currentFiber().setFls(data);
646}
647
648} // namespace zth
649
655EXTERN_C ZTH_EXPORT ZTH_INLINE void* zth_fls() noexcept
656{
657 return zth::fls();
658}
659
665EXTERN_C ZTH_EXPORT ZTH_INLINE void zth_setFls(void* data = nullptr) noexcept
666{
667 zth::setFls(data);
668}
669
670#else // !__cplusplus
671
672ZTH_EXPORT void* zth_fls();
673ZTH_EXPORT void* zth_setFls(void* data);
674
675#endif // __cplusplus
676
677EXTERN_C ZTH_EXPORT int main_fiber(int argc, char** argv);
678
679#endif // ZTH_FIBER_H
The fiber.
Definition fiber.h:62
State state() const noexcept
Definition fiber.h:163
Context * context() const noexcept
Definition fiber.h:158
Timestamp const & stateEnd() const noexcept
Definition fiber.h:183
void setState(State state, Timestamp const &t=Timestamp::now()) noexcept
Definition fiber.h:491
static FiberHook * hookDead
Hook to be called when a Fiber is destroyed.
Definition fiber.h:80
void cancelled()
Definition fiber.h:334
static Fiber * fromHandle(zth_fiber_t const &h) noexcept
Definition fiber.h:134
void wakeup() noexcept
Definition fiber.h:379
virtual ~Fiber() noexcept override
Definition fiber.h:108
void atCleanup(Hook_type::function_type f, Hook_type::arg_type arg=Hook_type::arg_type())
Definition fiber.h:200
void() FiberHook(Fiber &)
Definition fiber.h:66
void setFls(void *data=nullptr) noexcept
Definition fiber.h:173
void cancel()
Definition fiber.h:318
size_t stackSize() const noexcept
Definition fiber.h:148
void(* Entry)(EntryArg)
Definition fiber.h:85
int init(Timestamp const &now=Timestamp::now())
Definition fiber.h:205
bool allowYield(Timestamp const &now=Timestamp::now()) const noexcept
Definition fiber.h:304
Hook< Fiber & > Hook_type
Definition fiber.h:193
void atExit(Hook_type::function_type f, Hook_type::arg_type arg=Hook_type::arg_type())
Definition fiber.h:195
static uintptr_t const ExitUnknown
Definition fiber.h:86
TimeInterval const & totalTime() const noexcept
Definition fiber.h:188
string str() const
Definition fiber.h:441
void fiberEntry_() noexcept
Definition fiber.h:512
void suspend() noexcept
Definition fiber.h:404
zth_fiber_t handle() const noexcept
Definition fiber.h:127
int setStackSize(size_t size) noexcept
Definition fiber.h:139
Timestamp const & runningSince() const noexcept
Definition fiber.h:178
@ Suspended
Definition fiber.h:82
@ Waiting
Definition fiber.h:82
@ Cancel
Definition fiber.h:82
@ Ready
Definition fiber.h:82
@ Running
Definition fiber.h:82
@ Uninitialized
Definition fiber.h:82
size_t stackUsage() const
Definition fiber.h:153
Fiber(Entry entry, EntryArg arg=EntryArg())
Definition fiber.h:88
static FiberHook * hookNew
Hook to be called when a Fiber is created.
Definition fiber.h:73
int run(Fiber &from, Timestamp now=Timestamp::now())
Definition fiber.h:229
void resume() noexcept
Definition fiber.h:430
void nap(Timestamp const &sleepUntil=Timestamp::null()) noexcept
Definition fiber.h:346
ContextAttr::EntryArg EntryArg
Definition fiber.h:84
void kill() noexcept
Definition fiber.h:309
static void fiberEntry(void *that) noexcept
Definition fiber.h:505
void * fls() const noexcept
Definition fiber.h:168
void add(function_type f, arg_type a=arg_type()) noexcept
Definition list.h:791
hookable_type::function_type function_type
Definition list.h:779
hookable_type::arg_type arg_type
Definition list.h:780
void once(type x) noexcept
Definition list.h:832
virtual char const * id_str() const noexcept override
Definition util.h:787
string const & name() const noexcept
Definition util.h:761
bool unused() noexcept
Definition util.h:975
An abstract class, that can be started as a fiber.
Definition fiber.h:558
Fiber * fiber() const noexcept
Definition fiber.h:571
int run()
Definition fiber.cpp:18
virtual int fiberHook(Fiber &f)
Definition fiber.h:591
char const * id_str() const
Definition fiber.h:585
virtual void cleanup() noexcept
Definition fiber.h:598
virtual ~Runnable()=default
constexpr Runnable() noexcept
Definition fiber.h:562
virtual void entry()=0
Convenient wrapper around struct timespec that contains a time interval.
Definition time.h:82
string str() const
Definition time.h:488
Convenient wrapper around struct timespec that contains an absolute timestamp.
Definition time.h:629
static Timestamp now()
Definition time.h:656
constexpr bool isNull() const noexcept
Definition time.h:780
static constexpr Timestamp null() noexcept
Definition time.h:775
Keeps track of a process-wide unique ID within the type T.
Definition util.h:875
void const * normptr() const noexcept
Definition util.h:931
int main_fiber(int argc, char **argv)
void * zth_fls() noexcept
Return the fiber-local storage, as set by setFls().
Definition fiber.h:655
void zth_setFls(void *data=nullptr) noexcept
Set the fiber-local storage.
Definition fiber.h:665
#define zth_perf_mark(marker)
Put a string marker into the perf output.
Definition perf.h:437
#define zth_config(name)
Checks if the given zth::Config field is enabled.
Definition config.h:46
void outOfWork()
Force a context switch.
Definition worker.h:486
void * fls() noexcept
Return the fiber-local storage, as set by setFls().
Definition fiber.h:631
void setFls(void *data=nullptr) noexcept
Set the fiber-local storage.
Definition fiber.h:643
fiber_type< F >::fiber fiber(F &&f, Args &&... args)
Create and start a new fiber.
Definition async.h:1221
Fiber & currentFiber() noexcept
Return the currently executing fiber.
Definition worker.h:427
#define zth_dbg(group, fmt, a...)
Debug printf()-like function.
Definition util.h:194
void log_color(int color, char const *fmt,...)
Logs a given printf()-like formatted string using an ANSI color code.
Definition util.h:292
impl::PickBacktrace ::type Backtrace
Save a backtrace.
Definition backtrace.h:168
#define ZTH_CLASS_NEW_DELETE(T)
Define new/delete operators for a class, which are allocator-aware.
Definition allocator.h:160
void zth_init()
Perform one-time global initialization of the Zth library.
Definition init.cpp:38
#define zth_throw(...)
Definition macros.h:376
#define is_default
Definition macros.h:223
#define ZTH_INLINE
Definition macros.h:139
#define UNUSED_PAR(name)
Definition macros.h:79
void perf_fiber_state(Fiber &f, int state=-1, Timestamp const &t=Timestamp()) noexcept
Record the current fiber state.
Definition perf.cpp:528
void now(struct timespec &ts)
Returns the current timestamp.
Definition time.h:59
void context_switch(Context *from, Context *to) noexcept
Perform context switch.
Definition context.cpp:112
void context_destroy(Context *context) noexcept
Destroy and cleanup a context.
Definition context.cpp:95
int context_create(Context *&context, ContextAttr const &attr) noexcept
Create a context.
Definition context.cpp:55
string format(char const *fmt,...)
Format like sprintf(), but save the result in an zth::string.
Definition util.h:498
void perf_fiber(Fiber &f) noexcept
Write fiber ID/name to the perf buffer.
Definition perf.cpp:500
size_t context_stack_usage(Context *context) noexcept
Return the high water mark of the stack of the given context.
Definition context.cpp:128
The configuration of Zth.
Definition zth_config.h:26
void * EntryArg
Definition context.h:70
static int const Print_perf
Definition config.h:158
static bool const EnablePerfEvent
Enable (but not necessarily record) perf.
Definition config.h:265
Exception thrown when a fiber is cancelled.
Definition exception.h:52
Opaque fiber handle type.
Definition fiber.h:24
void * p
Definition fiber.h:25
#define ZTH_DBG_PREFIX
Prefix for every zth_dbg() call.
Definition util.h:182
#define zth_assert(expr)
assert(), but better integrated in Zth.
Definition util.h:217
#define likely(expr)
Marks the given expression to likely be evaluated to true.
Definition util.h:45
#define ZTH_CLASS_NOCOPY(Class)
Definition util.h:234
#define unlikely(expr)
Marks the given expression to likely be evaluated to true.
Definition util.h:60