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
20#ifdef __cplusplus
21# include <libzth/allocator.h>
22# include <libzth/config.h>
23# include <libzth/context.h>
24# include <libzth/init.h>
25# include <libzth/list.h>
26# include <libzth/perf.h>
27# include <libzth/time.h>
28# include <libzth/util.h>
29
30# include <errno.h>
31# include <exception>
32# include <utility>
33
34# if __cplusplus >= 201103L
35# include <functional>
36# endif
37
38namespace zth {
39
49class Fiber : public Listable, public UniqueID<Fiber>, public RefCounted {
52public:
53 typedef void(FiberHook)(Fiber&);
54
61
68
70
72 typedef void (*Entry)(EntryArg);
73 static uintptr_t const ExitUnknown = (uintptr_t)-1;
74
75 explicit Fiber(Entry entry, EntryArg arg = EntryArg())
76 : UniqueID("zth::Fiber")
77 , m_state(Uninitialized)
78 , m_stateNext(Ready)
79 , m_entry(entry)
80 , m_entryArg(arg)
81 , m_stackSize(Config::DefaultFiberStackSize)
82 , m_context()
83 , m_fls()
84 , m_timeslice(Config::MinTimeslice())
85 , m_dtMax(Config::CheckTimesliceOverrun
86 ? TimeInterval(Config::TimesliceOverrunReportThreshold())
87 : TimeInterval())
88 {
89 zth_init();
91 zth_assert(m_entry);
92 zth_dbg(fiber, "[%s] New fiber %p", id_str(), normptr());
93 }
94
95 virtual ~Fiber() noexcept override
96 {
97 m_exit.once(*this);
98 m_cleanup.once(*this);
99
100 if(state() > Uninitialized && state() < Dead)
101 kill();
102
103 zth_perf_event(*this);
104
106 zth_assert(!fls());
107 size_t stackSize_ __attribute__((unused)) = this->stackSize();
108 size_t stackUsage_ __attribute__((unused)) = this->stackUsage();
109 context_destroy(m_context);
110 m_context = nullptr;
111 zth_dbg(fiber, "[%s] Destructed. Stack usage: 0x%x of 0x%x, total CPU: %s",
112 id_str(), (unsigned int)stackUsage_, (unsigned int)stackSize_,
113 m_totalTime.str().c_str());
114 }
115
116 int setStackSize(size_t size) noexcept
117 {
118 if(state() != New)
119 return EPERM;
120
121 m_stackSize = size;
122 return 0;
123 }
124
125 size_t stackSize() const noexcept
126 {
127 return m_stackSize;
128 }
129
130 size_t stackUsage() const
131 {
133 }
134
135 Context* context() const noexcept
136 {
137 return m_context;
138 }
139
140 State state() const noexcept
141 {
142 return m_state;
143 }
144
145 void* fls() const noexcept
146 {
147 return m_fls;
148 }
149
150 void setFls(void* data = nullptr) noexcept
151 {
152 m_fls = data;
153 }
154
155 Timestamp const& runningSince() const noexcept
156 {
157 return m_startRun;
158 }
159
160 Timestamp const& stateEnd() const noexcept
161 {
162 return m_stateEnd;
163 }
164
165 TimeInterval const& totalTime() const noexcept
166 {
167 return m_totalTime;
168 }
169
171
173 {
174 m_exit.add(f, arg);
175 }
176
178 {
179 m_cleanup.add(f, arg);
180 }
181
182 int init(Timestamp const& now = Timestamp::now())
183 {
184 if(state() != New)
185 return EPERM;
186
187 zth_assert(!m_context);
188
189 zth_dbg(fiber, "[%s] Init", id_str());
190
191 ContextAttr attr(&fiberEntry, this, m_stackSize);
192 int res = context_create(m_context, attr);
193 if(res) {
194 // Oops.
195 kill();
196 return res;
197 }
198
199 setState(m_stateNext, now);
200 m_stateNext = Ready;
201 m_startRun = now;
202
203 return 0;
204 }
205
206 int run(Fiber& from, Timestamp now = Timestamp::now())
207 {
208 int res = 0;
209
210again:
211 switch(state()) {
212 case New:
213 // First call, do implicit init.
214 if((res = init(now)))
215 return res;
216
217 if(hookNew)
218 hookNew(*this);
219 goto again;
220
221 case Ready: {
222 zth_assert(&from != this);
223
224 // Update administration of the current fiber.
225 TimeInterval dt = now - from.m_startRun;
226 from.m_totalTime += dt;
227
228 if(from.state() == Running)
229 from.setState(Ready, now);
230
231 if(unlikely(zth_config(CheckTimesliceOverrun) && from.m_dtMax < dt)) {
232 perf_mark("timeslice overrun reported");
233 from.m_dtMax = dt;
234 log_color(
236 ZTH_DBG_PREFIX "Long timeslice by %s of %s\n",
237 from.id_str(), dt.str().c_str());
239 now = Timestamp::now();
240 }
241
242 // Hand over to this.
243 m_startRun = now;
244 setState(Running, now);
245 m_stateEnd = now + m_timeslice;
246
247 zth_dbg(fiber, "Switch from %s to %s after %s", from.id_str(), id_str(),
248 dt.str().c_str());
249 context_switch(from.context(), context());
250
251 // Ok, got back.
252 // Warning! This fiber might already be dead and destructed at this point!
253 // Only return here!
254 return 0;
255 }
256
257 case Running:
258 zth_assert(&from == this);
259 // No switch required.
260 return EAGAIN;
261
262 case Dead:
263 // Can't run dead fibers.
264 return EPERM;
265
266 case Uninitialized:
267 case Waiting:
268 case Suspended:
269 default:
270 zth_assert(false);
271 return EINVAL;
272 }
273 }
274
275 bool allowYield(Timestamp const& now = Timestamp::now()) const noexcept
276 {
277 return state() != Running || m_stateEnd < now;
278 }
279
280 void kill() noexcept
281 {
282 if(state() == Dead)
283 return;
284
285 zth_dbg(fiber, "[%s] Killed", id_str());
286 setState(Dead);
287 }
288
289 void nap(Timestamp const& sleepUntil = Timestamp::null()) noexcept
290 {
291 switch(state()) {
292 case New:
293 // Postpone actual sleep
294 zth_dbg(fiber, "[%s] Sleep upon startup", id_str());
295 m_stateNext = Waiting;
296 break;
297 case Ready:
298 case Running:
299 if(sleepUntil.isNull())
300 zth_dbg(fiber, "[%s] Sleep", id_str());
301 else
302 zth_dbg(fiber, "[%s] Sleep for %s", id_str(),
303 (sleepUntil - Timestamp::now()).str().c_str());
305 m_stateNext = Ready;
306 break;
307 case Suspended:
308 zth_dbg(fiber, "[%s] Schedule sleep after resume", id_str());
309 m_stateNext = Waiting;
310 break;
311 case Waiting:
312 case Uninitialized:
313 case Dead:
314 default:
315 return;
316 }
317
318 m_stateEnd = sleepUntil;
319 }
320
321 void wakeup() noexcept
322 {
323 if(likely(state() == Waiting)) {
324 setState(m_stateNext);
325 switch(state()) {
326 case Suspended:
327 zth_dbg(fiber, "[%s] Suspend after wakeup", id_str());
328 m_stateNext = Ready;
329 break;
330 case Uninitialized:
331 case New:
332 case Ready:
333 case Running:
334 case Waiting:
335 case Dead:
336 default:
337 zth_dbg(fiber, "[%s] Wakeup", id_str());
338 }
339 } else if(state() == Suspended && m_stateNext == Waiting) {
340 zth_dbg(fiber, "[%s] Set wakeup after suspend", id_str());
341 m_stateNext = Ready;
342 }
343 }
344
345 void suspend() noexcept
346 {
347 switch(state()) {
348 case New:
349 m_stateNext = New;
350 break;
351 case Running:
352 case Ready:
353 m_stateNext = Ready;
354 break;
355 case Waiting:
356 m_stateNext = Waiting;
357 // fall-through
358 case Suspended:
359 case Uninitialized:
360 case Dead:
361 default:
362 // Ignore.
363 return;
364 }
365
366 zth_dbg(fiber, "[%s] Suspend", id_str());
368 }
369
370 void resume() noexcept
371 {
372 if(state() != Suspended)
373 return;
374
375 zth_dbg(fiber, "[%s] Resume", id_str());
376 setState(m_stateNext);
377 if(state() == New)
378 m_stateNext = Ready;
379 }
380
381 string str() const
382 {
383 string res = format("%s", id_str());
384
385 switch(state()) {
386 case Uninitialized:
387 res += " Uninitialized";
388 break;
389 case New:
390 res += " New";
391 break;
392 case Ready:
393 res += " Ready";
394 break;
395 case Running:
396 res += " Running";
397 break;
398 case Waiting:
399 res += " Waiting";
400 if(!m_stateEnd.isNull())
401 res +=
402 format(" (%s remaining)",
403 Timestamp::now().timeTo(stateEnd()).str().c_str());
404 break;
405 case Suspended:
406 res += " Suspended";
407 break;
408 case Dead:
409 res += " Dead";
410 break;
411 default:;
412 }
413
414 res += format(" t=%s", m_totalTime.str().c_str());
415 return res;
416 }
417
418private:
419 virtual void changedName(string const& name) override
420 {
421 zth_dbg(fiber, "[%s] Renamed to %s", id_str(), name.c_str());
422 }
423
424protected:
425 void setState(State state, Timestamp const& t = Timestamp::now()) noexcept
426 {
427 if(m_state == state)
428 return;
429
430 m_state = state;
431
432 zth_perf_event(*this, m_state, t);
433
434 if(state == Dead && hookDead)
435 hookDead(*this);
436 }
437
438 static void fiberEntry(void* that) noexcept
439 {
440 zth_assert(that);
441 // cppcheck-suppress nullPointerRedundantCheck
442 static_cast<Fiber*>(that)->fiberEntry_();
443 }
444
445 void fiberEntry_() noexcept
446 {
447 zth_dbg(fiber, "[%s] Entry", id_str());
448
449 try {
450 m_entry(m_entryArg);
451 } catch(std::exception const& e) {
452# ifdef __cpp_exceptions
453 zth_dbg(fiber, "[%s] Uncaught exception; %s", id_str(), e.what());
454# endif
455 } catch(...) {
456 zth_dbg(fiber, "[%s] Uncaught exception", id_str());
457 }
458
459 zth_dbg(fiber, "[%s] Exit", id_str());
460 m_exit.once(*this);
461
462 kill();
463 }
464
465private:
466 State m_state;
467 State m_stateNext;
468 Entry m_entry;
469 EntryArg m_entryArg;
470 size_t m_stackSize;
471 Context* m_context;
472 void* m_fls;
473 TimeInterval m_totalTime;
474 Timestamp m_startRun;
475 Timestamp m_stateEnd;
476 TimeInterval m_timeslice;
477 TimeInterval m_dtMax;
478 Hook_type m_exit;
479 Hook_type m_cleanup;
480};
481
491class Runnable {
494protected:
495 constexpr Runnable() noexcept
496 : m_fiber()
497 {}
498
499public:
501
502 int run();
503
504 Fiber* fiber() const noexcept
505 {
506 return m_fiber;
507 }
508
509 operator Fiber&() const noexcept
510 {
511 // cppcheck-suppress constVariablePointer
512 Fiber* const f = fiber();
513 zth_assert(f);
514 // cppcheck-suppress nullPointerRedundantCheck
515 return *f;
516 }
517
518 char const* id_str() const
519 {
520 return fiber() ? fiber()->id_str() : "detached Runnable";
521 }
522
523protected:
524 virtual int fiberHook(Fiber& f)
525 {
526 f.atCleanup(&cleanup_, this);
527 m_fiber = &f;
528 return 0;
529 }
530
531 virtual void cleanup() noexcept
532 {
533 m_fiber = nullptr;
534 }
535
536 virtual void entry() = 0;
537
538private:
539 static void cleanup_(Fiber& UNUSED_PAR(f), void* that) noexcept
540 {
541 Runnable* r = static_cast<Runnable*>(that);
542 if(likely(r)) {
543 zth_assert(&f == r->m_fiber);
544 r->cleanup();
545 }
546 }
547
548 static void entry_(void* that)
549 {
550 if(likely(that))
551 static_cast<Runnable*>(that)->entry();
552 }
553
554private:
555 Fiber* m_fiber;
556};
557
558__attribute__((pure)) Fiber& currentFiber() noexcept;
559
564ZTH_EXPORT inline void* fls() noexcept
565{
566 return currentFiber().fls();
567}
568
576ZTH_EXPORT inline void setFls(void* data = nullptr) noexcept
577{
578 return currentFiber().setFls(data);
579}
580
581} // namespace zth
582
588EXTERN_C ZTH_EXPORT ZTH_INLINE void* zth_fls() noexcept
589{
590 return zth::fls();
591}
592
598EXTERN_C ZTH_EXPORT ZTH_INLINE void zth_setFls(void* data = nullptr) noexcept
599{
600 zth::setFls(data);
601}
602
603#else // !__cplusplus
604
605ZTH_EXPORT void* zth_fls();
606ZTH_EXPORT void* zth_setFls(void* data);
607
608#endif // __cplusplus
609
610EXTERN_C ZTH_EXPORT int main_fiber(int argc, char** argv);
611
612#endif // ZTH_FIBER_H
Save a backtrace.
Definition perf.h:49
void print(int color=-1) const
Definition perf.cpp:668
The fiber.
Definition fiber.h:49
State state() const noexcept
Definition fiber.h:140
Context * context() const noexcept
Definition fiber.h:135
Timestamp const & stateEnd() const noexcept
Definition fiber.h:160
void setState(State state, Timestamp const &t=Timestamp::now()) noexcept
Definition fiber.h:425
static FiberHook * hookDead
Hook to be called when a Fiber is destroyed.
Definition fiber.h:67
void wakeup() noexcept
Definition fiber.h:321
virtual ~Fiber() noexcept override
Definition fiber.h:95
void atCleanup(Hook_type::function_type f, Hook_type::arg_type arg=Hook_type::arg_type())
Definition fiber.h:177
void() FiberHook(Fiber &)
Definition fiber.h:53
void setFls(void *data=nullptr) noexcept
Definition fiber.h:150
size_t stackSize() const noexcept
Definition fiber.h:125
void(* Entry)(EntryArg)
Definition fiber.h:72
int init(Timestamp const &now=Timestamp::now())
Definition fiber.h:182
bool allowYield(Timestamp const &now=Timestamp::now()) const noexcept
Definition fiber.h:275
Hook< Fiber & > Hook_type
Definition fiber.h:170
void atExit(Hook_type::function_type f, Hook_type::arg_type arg=Hook_type::arg_type())
Definition fiber.h:172
static uintptr_t const ExitUnknown
Definition fiber.h:73
TimeInterval const & totalTime() const noexcept
Definition fiber.h:165
string str() const
Definition fiber.h:381
void fiberEntry_() noexcept
Definition fiber.h:445
void suspend() noexcept
Definition fiber.h:345
int setStackSize(size_t size) noexcept
Definition fiber.h:116
Timestamp const & runningSince() const noexcept
Definition fiber.h:155
@ Suspended
Definition fiber.h:69
@ Waiting
Definition fiber.h:69
@ Ready
Definition fiber.h:69
@ Running
Definition fiber.h:69
@ Uninitialized
Definition fiber.h:69
size_t stackUsage() const
Definition fiber.h:130
Fiber(Entry entry, EntryArg arg=EntryArg())
Definition fiber.h:75
static FiberHook * hookNew
Hook to be called when a Fiber is created.
Definition fiber.h:60
int run(Fiber &from, Timestamp now=Timestamp::now())
Definition fiber.h:206
void resume() noexcept
Definition fiber.h:370
void nap(Timestamp const &sleepUntil=Timestamp::null()) noexcept
Definition fiber.h:289
ContextAttr::EntryArg EntryArg
Definition fiber.h:71
void kill() noexcept
Definition fiber.h:280
static void fiberEntry(void *that) noexcept
Definition fiber.h:438
void * fls() const noexcept
Definition fiber.h:145
void add(function_type f, arg_type a=arg_type()) noexcept
Definition list.h:789
hookable_type::function_type function_type
Definition list.h:777
hookable_type::arg_type arg_type
Definition list.h:778
void once(type x) noexcept
Definition list.h:830
virtual char const * id_str() const noexcept override
Definition util.h:772
string const & name() const noexcept
Definition util.h:746
bool unused() noexcept
Definition util.h:960
An abstract class, that can be started as a fiber.
Definition fiber.h:491
Fiber * fiber() const noexcept
Definition fiber.h:504
int run()
Definition fiber.cpp:18
virtual int fiberHook(Fiber &f)
Definition fiber.h:524
char const * id_str() const
Definition fiber.h:518
virtual void cleanup() noexcept
Definition fiber.h:531
virtual ~Runnable()=default
constexpr Runnable() noexcept
Definition fiber.h:495
virtual void entry()=0
Convenient wrapper around struct timespec that contains a time interval.
Definition time.h:55
string str() const
Definition time.h:426
Convenient wrapper around struct timespec that contains an absolute timestamp.
Definition time.h:568
static Timestamp now()
Definition time.h:595
constexpr bool isNull() const noexcept
Definition time.h:720
static constexpr Timestamp null() noexcept
Definition time.h:715
Keeps track of a process-wide unique ID within the type T.
Definition util.h:860
void const * normptr() const noexcept
Definition util.h:916
int main_fiber(int argc, char **argv)
Definition main.cpp:11
void * zth_fls() noexcept
Return the fiber-local storage, as set by setFls().
Definition fiber.h:588
void zth_setFls(void *data=nullptr) noexcept
Set the fiber-local storage.
Definition fiber.h:598
#define zth_config(name)
Checks if the given zth::Config field is enabled.
Definition config.h:44
void * fls() noexcept
Return the fiber-local storage, as set by setFls().
Definition fiber.h:564
void setFls(void *data=nullptr) noexcept
Set the fiber-local storage.
Definition fiber.h:576
fiber_type< F >::fiber fiber(F &&f, Args &&... args)
Create and start a new fiber.
Definition async.h:1192
Fiber & currentFiber() noexcept
Return the currently executing fiber.
Definition worker.h:417
void perf_mark(char const *marker)
Put a string marker into the perf output.
Definition perf.h:326
#define zth_perf_event(...)
Construct a zth::PerfEvent with provided parameters, and forward it to the perf buffer for later proc...
Definition perf.h:294
#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:277
#define ZTH_CLASS_NEW_DELETE(T)
Define new/delete operators for a class, which are allocator-aware.
Definition allocator.h:159
void zth_init()
Perform one-time global initialization of the Zth library.
Definition init.cpp:25
#define is_default
Definition macros.h:212
#define ZTH_INLINE
Definition macros.h:129
#define UNUSED_PAR(name)
Definition macros.h:78
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:483
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:129
#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