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<Fiber>, public UniqueID<Fiber> {
51public:
52 typedef void(FiberHook)(Fiber&);
53
60
67
69
71 typedef void (*Entry)(EntryArg);
72 static uintptr_t const ExitUnknown = (uintptr_t)-1;
73
74 explicit Fiber(Entry entry, EntryArg arg = EntryArg())
75 : UniqueID("zth::Fiber")
76 , m_state(Uninitialized)
77 , m_stateNext(Ready)
78 , m_entry(entry)
79 , m_entryArg(arg)
80 , m_contextAttr(&fiberEntry, this)
81 , m_context()
82 , m_fls()
83 , m_timeslice(Config::MinTimeslice())
84 , m_dtMax(Config::CheckTimesliceOverrun
85 ? TimeInterval(Config::TimesliceOverrunReportThreshold())
86 : TimeInterval())
87 {
88 zth_init();
90 zth_assert(m_entry);
91 zth_dbg(fiber, "[%s] New fiber %p", id_str(), normptr());
92 }
93
94 virtual ~Fiber() override
95 {
96 for(decltype(m_cleanup.begin()) it = m_cleanup.begin(); it != m_cleanup.end(); ++it)
97 it->first(*this, it->second);
98
99 if(state() > Uninitialized && state() < Dead)
100 kill();
101
102 zth_perf_event(*this);
103
105 zth_assert(!fls());
106 size_t stackSize_ __attribute__((unused)) = this->stackSize();
107 size_t stackUsage_ __attribute__((unused)) = this->stackUsage();
108 context_destroy(m_context);
109 m_context = nullptr;
110 zth_dbg(fiber, "[%s] Destructed. Stack usage: 0x%x of 0x%x, total CPU: %s",
111 id_str(), (unsigned int)stackUsage_, (unsigned int)stackSize_,
112 m_totalTime.str().c_str());
113 }
114
115 int setStackSize(size_t size) noexcept
116 {
117 if(state() != New)
118 return EPERM;
119
120 m_contextAttr.stackSize = size;
121 return 0;
122 }
123
124 size_t stackSize() const noexcept
125 {
126 return m_contextAttr.stackSize;
127 }
128
129 size_t stackUsage() const
130 {
132 }
133
134 Context* context() const noexcept
135 {
136 return m_context;
137 }
138
139 State state() const noexcept
140 {
141 return m_state;
142 }
143
144 void* fls() const noexcept
145 {
146 return m_fls;
147 }
148
149 void setFls(void* data = nullptr) noexcept
150 {
151 m_fls = data;
152 }
153
154 Timestamp const& runningSince() const noexcept
155 {
156 return m_startRun;
157 }
158
159 Timestamp const& stateEnd() const noexcept
160 {
161 return m_stateEnd;
162 }
163
164 TimeInterval const& totalTime() const noexcept
165 {
166 return m_totalTime;
167 }
168
169 void addCleanup(void (*f)(Fiber&, void*), void* arg)
170 {
171 m_cleanup.push_back(std::make_pair(f, arg));
172 }
173
174 int init(Timestamp const& now = Timestamp::now())
175 {
176 if(state() != New)
177 return EPERM;
178
179 zth_assert(!m_context);
180
181 zth_dbg(fiber, "[%s] Init", id_str());
182 int res = context_create(m_context, m_contextAttr);
183 if(res) {
184 // Oops.
185 kill();
186 return res;
187 }
188
189 setState(m_stateNext, now);
190 m_stateNext = Ready;
191 m_startRun = now;
192
193 return 0;
194 }
195
196 int run(Fiber& from, Timestamp now = Timestamp::now())
197 {
198 int res = 0;
199
200again:
201 switch(state()) {
202 case New:
203 // First call, do implicit init.
204 if((res = init(now)))
205 return res;
206
207 if(hookNew)
208 hookNew(*this);
209 goto again;
210
211 case Ready: {
212 zth_assert(&from != this);
213
214 // Update administration of the current fiber.
215 TimeInterval dt = now - from.m_startRun;
216 from.m_totalTime += dt;
217
218 if(from.state() == Running)
219 from.setState(Ready, now);
220
221 if(unlikely(zth_config(CheckTimesliceOverrun) && from.m_dtMax < dt)) {
222 perf_mark("timeslice overrun reported");
223 from.m_dtMax = dt;
224 log_color(
226 ZTH_DBG_PREFIX "Long timeslice by %s of %s\n",
227 from.id_str(), dt.str().c_str());
229 now = Timestamp::now();
230 }
231
232 // Hand over to this.
233 m_startRun = now;
234 setState(Running, now);
235 m_stateEnd = now + m_timeslice;
236
237 zth_dbg(fiber, "Switch from %s to %s after %s", from.id_str(), id_str(),
238 dt.str().c_str());
239 context_switch(from.context(), context());
240
241 // Ok, got back.
242 // Warning! This fiber might already be dead and destructed at this point!
243 // Only return here!
244 return 0;
245 }
246
247 case Running:
248 zth_assert(&from == this);
249 // No switch required.
250 return EAGAIN;
251
252 case Dead:
253 // Can't run dead fibers.
254 return EPERM;
255
256 case Uninitialized:
257 case Waiting:
258 case Suspended:
259 default:
260 zth_assert(false);
261 return EINVAL;
262 }
263 }
264
265 bool allowYield(Timestamp const& now = Timestamp::now()) const noexcept
266 {
267 return state() != Running || m_stateEnd < now;
268 }
269
270 void kill() noexcept
271 {
272 if(state() == Dead)
273 return;
274
275 zth_dbg(fiber, "[%s] Killed", id_str());
276 setState(Dead);
277 }
278
279 void nap(Timestamp const& sleepUntil = Timestamp::null()) noexcept
280 {
281 switch(state()) {
282 case New:
283 // Postpone actual sleep
284 zth_dbg(fiber, "[%s] Sleep upon startup", id_str());
285 m_stateNext = Waiting;
286 break;
287 case Ready:
288 case Running:
289 if(sleepUntil.isNull())
290 zth_dbg(fiber, "[%s] Sleep", id_str());
291 else
292 zth_dbg(fiber, "[%s] Sleep for %s", id_str(),
293 (sleepUntil - Timestamp::now()).str().c_str());
295 m_stateNext = Ready;
296 break;
297 case Suspended:
298 zth_dbg(fiber, "[%s] Schedule sleep after resume", id_str());
299 m_stateNext = Waiting;
300 break;
301 case Waiting:
302 case Uninitialized:
303 case Dead:
304 default:
305 return;
306 }
307
308 m_stateEnd = sleepUntil;
309 }
310
311 void wakeup() noexcept
312 {
313 if(likely(state() == Waiting)) {
314 setState(m_stateNext);
315 switch(state()) {
316 case Suspended:
317 zth_dbg(fiber, "[%s] Suspend after wakeup", id_str());
318 m_stateNext = Ready;
319 break;
320 case Uninitialized:
321 case New:
322 case Ready:
323 case Running:
324 case Waiting:
325 case Dead:
326 default:
327 zth_dbg(fiber, "[%s] Wakeup", id_str());
328 }
329 } else if(state() == Suspended && m_stateNext == Waiting) {
330 zth_dbg(fiber, "[%s] Set wakeup after suspend", id_str());
331 m_stateNext = Ready;
332 }
333 }
334
335 void suspend() noexcept
336 {
337 switch(state()) {
338 case New:
339 m_stateNext = New;
340 break;
341 case Running:
342 case Ready:
343 m_stateNext = Ready;
344 break;
345 case Waiting:
346 m_stateNext = Waiting;
347 // fall-through
348 case Suspended:
349 case Uninitialized:
350 case Dead:
351 default:
352 // Ignore.
353 return;
354 }
355
356 zth_dbg(fiber, "[%s] Suspend", id_str());
358 }
359
360 void resume() noexcept
361 {
362 if(state() != Suspended)
363 return;
364
365 zth_dbg(fiber, "[%s] Resume", id_str());
366 setState(m_stateNext);
367 if(state() == New)
368 m_stateNext = Ready;
369 }
370
371 string str() const
372 {
373 string res = format("%s", id_str());
374
375 switch(state()) {
376 case Uninitialized:
377 res += " Uninitialized";
378 break;
379 case New:
380 res += " New";
381 break;
382 case Ready:
383 res += " Ready";
384 break;
385 case Running:
386 res += " Running";
387 break;
388 case Waiting:
389 res += " Waiting";
390 if(!m_stateEnd.isNull())
391 res +=
392 format(" (%s remaining)",
393 Timestamp::now().timeTo(stateEnd()).str().c_str());
394 break;
395 case Suspended:
396 res += " Suspended";
397 break;
398 case Dead:
399 res += " Dead";
400 break;
401 default:;
402 }
403
404 res += format(" t=%s", m_totalTime.str().c_str());
405 return res;
406 }
407
408protected:
409 virtual void changedName(string const& name) override
410 {
411 zth_dbg(fiber, "[%s] Renamed to %s", id_str(), name.c_str());
412 }
413
414 void setState(State state, Timestamp const& t = Timestamp::now()) noexcept
415 {
416 if(m_state == state)
417 return;
418
419 m_state = state;
420
421 zth_perf_event(*this, m_state, t);
422
423 if(state == Dead && hookDead)
424 hookDead(*this);
425 }
426
427 static void fiberEntry(void* that) noexcept
428 {
429 zth_assert(that);
430 // cppcheck-suppress nullPointerRedundantCheck
431 static_cast<Fiber*>(that)->fiberEntry_();
432 }
433
434 void fiberEntry_() noexcept
435 {
436 zth_dbg(fiber, "[%s] Entry", id_str());
437
438 try {
439 m_entry(m_entryArg);
440 } catch(std::exception const& e) {
441# ifdef __cpp_exceptions
442 zth_dbg(fiber, "[%s] Uncaught exception; %s", id_str(), e.what());
443# endif
444 } catch(...) {
445 zth_dbg(fiber, "[%s] Uncaught exception", id_str());
446 }
447
448 zth_dbg(fiber, "[%s] Exit", id_str());
449 kill();
450 }
451
452private:
453 State m_state;
454 State m_stateNext;
455 Entry m_entry;
456 EntryArg m_entryArg;
457 ContextAttr m_contextAttr;
458 Context* m_context;
459 void* m_fls;
460 TimeInterval m_totalTime;
461 Timestamp m_startRun;
462 Timestamp m_stateEnd;
463 TimeInterval m_timeslice;
464 TimeInterval m_dtMax;
465 list_type<std::pair<void (*)(Fiber&, void*), void*> >::type m_cleanup;
466};
467
477class Runnable {
480protected:
481 constexpr Runnable() noexcept
482 : m_fiber()
483 {}
484
485public:
487
488 int run();
489
490 Fiber* fiber() const noexcept
491 {
492 return m_fiber;
493 }
494
495 operator Fiber&() const noexcept
496 {
497 // cppcheck-suppress constVariablePointer
498 Fiber* const f = fiber();
499 zth_assert(f);
500 // cppcheck-suppress nullPointerRedundantCheck
501 return *f;
502 }
503
504 char const* id_str() const
505 {
506 return fiber() ? fiber()->id_str() : "detached Runnable";
507 }
508
509protected:
510 virtual int fiberHook(Fiber& f)
511 {
512 f.addCleanup(&cleanup_, this);
513 m_fiber = &f;
514 return 0;
515 }
516
517 virtual void cleanup()
518 {
519 m_fiber = nullptr;
520 }
521
522 virtual void entry() = 0;
523
524private:
525 static void cleanup_(Fiber& UNUSED_PAR(f), void* that)
526 {
527 Runnable* r = static_cast<Runnable*>(that);
528 if(likely(r)) {
529 zth_assert(&f == r->m_fiber);
530 r->cleanup();
531 }
532 }
533
534 static void entry_(void* that)
535 {
536 if(likely(that))
537 static_cast<Runnable*>(that)->entry();
538 }
539
540private:
541 Fiber* m_fiber;
542};
543
544__attribute__((pure)) Fiber& currentFiber() noexcept;
545
550ZTH_EXPORT inline void* fls() noexcept
551{
552 return currentFiber().fls();
553}
554
562ZTH_EXPORT inline void setFls(void* data = nullptr) noexcept
563{
564 return currentFiber().setFls(data);
565}
566
567} // namespace zth
568
574EXTERN_C ZTH_EXPORT ZTH_INLINE void* zth_fls() noexcept
575{
576 return zth::fls();
577}
578
584EXTERN_C ZTH_EXPORT ZTH_INLINE void zth_setFls(void* data = nullptr) noexcept
585{
586 zth::setFls(data);
587}
588
589#else // !__cplusplus
590
591ZTH_EXPORT void* zth_fls();
592ZTH_EXPORT void* zth_setFls(void* data);
593
594#endif // __cplusplus
595
596EXTERN_C ZTH_EXPORT int main_fiber(int argc, char** argv);
597
598#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:139
Context * context() const noexcept
Definition fiber.h:134
Timestamp const & stateEnd() const noexcept
Definition fiber.h:159
void setState(State state, Timestamp const &t=Timestamp::now()) noexcept
Definition fiber.h:414
static FiberHook * hookDead
Hook to be called when a Fiber is destroyed.
Definition fiber.h:66
void wakeup() noexcept
Definition fiber.h:311
void() FiberHook(Fiber &)
Definition fiber.h:52
void setFls(void *data=nullptr) noexcept
Definition fiber.h:149
size_t stackSize() const noexcept
Definition fiber.h:124
void(* Entry)(EntryArg)
Definition fiber.h:71
int init(Timestamp const &now=Timestamp::now())
Definition fiber.h:174
bool allowYield(Timestamp const &now=Timestamp::now()) const noexcept
Definition fiber.h:265
virtual void changedName(string const &name) override
Definition fiber.h:409
static uintptr_t const ExitUnknown
Definition fiber.h:72
TimeInterval const & totalTime() const noexcept
Definition fiber.h:164
string str() const
Definition fiber.h:371
void fiberEntry_() noexcept
Definition fiber.h:434
void suspend() noexcept
Definition fiber.h:335
int setStackSize(size_t size) noexcept
Definition fiber.h:115
Timestamp const & runningSince() const noexcept
Definition fiber.h:154
@ Suspended
Definition fiber.h:68
@ Waiting
Definition fiber.h:68
@ Ready
Definition fiber.h:68
@ Running
Definition fiber.h:68
@ Uninitialized
Definition fiber.h:68
virtual ~Fiber() override
Definition fiber.h:94
size_t stackUsage() const
Definition fiber.h:129
Fiber(Entry entry, EntryArg arg=EntryArg())
Definition fiber.h:74
static FiberHook * hookNew
Hook to be called when a Fiber is created.
Definition fiber.h:59
int run(Fiber &from, Timestamp now=Timestamp::now())
Definition fiber.h:196
void resume() noexcept
Definition fiber.h:360
void nap(Timestamp const &sleepUntil=Timestamp::null()) noexcept
Definition fiber.h:279
ContextAttr::EntryArg EntryArg
Definition fiber.h:70
void addCleanup(void(*f)(Fiber &, void *), void *arg)
Definition fiber.h:169
void kill() noexcept
Definition fiber.h:270
static void fiberEntry(void *that) noexcept
Definition fiber.h:427
void * fls() const noexcept
Definition fiber.h:144
An abstract class, that can be started as a fiber.
Definition fiber.h:477
Fiber * fiber() const noexcept
Definition fiber.h:490
int run()
Definition fiber.cpp:18
virtual int fiberHook(Fiber &f)
Definition fiber.h:510
char const * id_str() const
Definition fiber.h:504
virtual void cleanup()
Definition fiber.h:517
virtual ~Runnable()=default
constexpr Runnable() noexcept
Definition fiber.h:481
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:715
void const * normptr() const noexcept
Definition util.h:773
virtual char const * id_str() const override
Definition util.h:809
string const & name() const noexcept
Definition util.h:783
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:574
void zth_setFls(void *data=nullptr) noexcept
Set the fiber-local storage.
Definition fiber.h:584
#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:550
void setFls(void *data=nullptr) noexcept
Set the fiber-local storage.
Definition fiber.h:562
Fiber & currentFiber() noexcept
Return the currently executing fiber.
Definition worker.h:399
fiber_type< F >::factory fiber(F f, char const *name=nullptr)
Create a new fiber.
Definition async.h:754
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:189
void log_color(int color, char const *fmt,...)
Logs a given printf()-like formatted string using an ANSI color code.
Definition util.h:272
#define ZTH_CLASS_NEW_DELETE(T)
Define new/delete operators for a class, which are allocator-aware.
Definition allocator.h:143
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:478
size_t context_stack_usage(Context *context) noexcept
Return the high water mark of the stack of the given context.
Definition context.cpp:139
The configuration of Zth.
Definition zth_config.h:26
size_t stackSize
Definition context.h:79
void * EntryArg
Definition context.h:70
static int const Print_perf
Definition config.h:129
std::list type using Config::Allocator::type.
Definition allocator.h:193
#define ZTH_DBG_PREFIX
Prefix for every zth_dbg() call.
Definition util.h:177
#define zth_assert(expr)
assert(), but better integrated in Zth.
Definition util.h:212
#define likely(expr)
Marks the given expression to likely be evaluated to true.
Definition util.h:40
#define ZTH_CLASS_NOCOPY(Class)
Definition util.h:229
#define unlikely(expr)
Marks the given expression to likely be evaluated to true.
Definition util.h:55