Zth (libzth)
fiber.h
Go to the documentation of this file.
1 #ifndef ZTH_FIBER_H
2 #define ZTH_FIBER_H
3 /*
4  * Zth (libzth), a cooperative userspace multitasking library.
5  * Copyright (C) 2019-2022 Jochem Rutgers
6  *
7  * This Source Code Form is subject to the terms of the Mozilla Public
8  * License, v. 2.0. If a copy of the MPL was not distributed with this
9  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
10  */
11 
21 #ifdef __cplusplus
22 # include <libzth/allocator.h>
23 # include <libzth/config.h>
24 # include <libzth/context.h>
25 # include <libzth/init.h>
26 # include <libzth/list.h>
27 # include <libzth/perf.h>
28 # include <libzth/time.h>
29 # include <libzth/util.h>
30 
31 # include <errno.h>
32 # include <exception>
33 # include <utility>
34 
35 # if __cplusplus >= 201103L
36 # include <functional>
37 # endif
38 
39 namespace zth {
40 
50 class Fiber
51  : public Listable<Fiber>
52  , public UniqueID<Fiber> {
54 public:
55  typedef void(FiberHook)(Fiber&);
56 
62  static FiberHook* hookNew;
63 
70 
72 
74  typedef void (*Entry)(EntryArg);
75  static uintptr_t const ExitUnknown = (uintptr_t)-1;
76 
77  explicit Fiber(Entry entry, EntryArg arg = EntryArg())
78  : UniqueID("zth::Fiber")
79  , m_state(Uninitialized)
80  , m_stateNext(Ready)
81  , m_entry(entry)
82  , m_entryArg(arg)
83  , m_contextAttr(&fiberEntry, this)
84  , m_context()
85  , m_fls()
86  , m_timeslice(Config::MinTimeslice_s())
87  , m_dtMax(Config::CheckTimesliceOverrun
88  ? Config::MinTimeslice_s()
89  * Config::TimesliceOverrunFactorReportThreshold
90  : 0)
91  {
92  zth_init();
93  setState(New);
94  zth_assert(m_entry);
95  zth_dbg(fiber, "[%s] New fiber %p", id_str(), normptr());
96  }
97 
98  virtual ~Fiber() override
99  {
100  for(decltype(m_cleanup.begin()) it = m_cleanup.begin(); it != m_cleanup.end(); ++it)
101  it->first(*this, it->second);
102 
103  if(state() > Uninitialized && state() < Dead)
104  kill();
105 
106  zth_perf_event(*this);
107 
109  zth_assert(!fls());
110  size_t stackSize_ __attribute__((unused)) = this->stackSize();
111  size_t stackUsage_ __attribute__((unused)) = this->stackUsage();
112  context_destroy(m_context);
113  m_context = nullptr;
114  zth_dbg(fiber, "[%s] Destructed. Stack usage: 0x%x of 0x%x, total CPU: %s",
115  id_str(), (unsigned int)stackUsage_, (unsigned int)stackSize_,
116  m_totalTime.str().c_str());
117  }
118 
119  int setStackSize(size_t size) noexcept
120  {
121  if(state() != New)
122  return EPERM;
123 
124  m_contextAttr.stackSize = size;
125  return 0;
126  }
127 
128  size_t stackSize() const noexcept
129  {
130  return m_contextAttr.stackSize;
131  }
132 
133  size_t stackUsage() const
134  {
135  return context_stack_usage(context());
136  }
137 
138  Context* context() const noexcept
139  {
140  return m_context;
141  }
142 
143  State state() const noexcept
144  {
145  return m_state;
146  }
147 
148  void* fls() const noexcept
149  {
150  return m_fls;
151  }
152 
153  void setFls(void* data = nullptr) noexcept
154  {
155  m_fls = data;
156  }
157 
158  Timestamp const& runningSince() const noexcept
159  {
160  return m_startRun;
161  }
162 
163  Timestamp const& stateEnd() const noexcept
164  {
165  return m_stateEnd;
166  }
167 
168  TimeInterval const& totalTime() const noexcept
169  {
170  return m_totalTime;
171  }
172 
173  void addCleanup(void (*f)(Fiber&, void*), void* arg)
174  {
175  m_cleanup.push_back(std::make_pair(f, arg));
176  }
177 
178  int init(Timestamp const& now = Timestamp::now())
179  {
180  if(state() != New)
181  return EPERM;
182 
183  zth_assert(!m_context);
184 
185  zth_dbg(fiber, "[%s] Init", id_str());
186  int res = context_create(m_context, m_contextAttr);
187  if(res) {
188  // Oops.
189  kill();
190  return res;
191  }
192 
193  setState(m_stateNext, now);
194  m_stateNext = Ready;
195  m_startRun = now;
196 
197  return 0;
198  }
199 
200  int run(Fiber& from, Timestamp now = Timestamp::now())
201  {
202  int res = 0;
203 
204 again:
205  switch(state()) {
206  case New:
207  // First call, do implicit init.
208  if((res = init(now)))
209  return res;
210 
211  if(hookNew)
212  hookNew(*this);
213  goto again;
214 
215  case Ready: {
216  zth_assert(&from != this);
217 
218  // Update administration of the current fiber.
219  TimeInterval dt = now - from.m_startRun;
220  from.m_totalTime += dt;
221 
222  if(from.state() == Running)
223  from.setState(Ready, now);
224 
225  if(unlikely(zth_config(CheckTimesliceOverrun) && from.m_dtMax < dt)) {
226  perf_mark("timeslice overrun reported");
227  from.m_dtMax = dt;
228  log_color(
230  ZTH_DBG_PREFIX "Long timeslice by %s of %s\n",
231  from.id_str(), dt.str().c_str());
233  now = Timestamp::now();
234  }
235 
236  // Hand over to this.
237  m_startRun = now;
238  setState(Running, now);
239  m_stateEnd = now + m_timeslice;
240 
241  zth_dbg(fiber, "Switch from %s to %s after %s", from.id_str(), id_str(),
242  dt.str().c_str());
243  context_switch(from.context(), context());
244 
245  // Ok, got back.
246  // Warning! This fiber might already be dead and destructed at this point!
247  // Only return here!
248  return 0;
249  }
250 
251  case Running:
252  zth_assert(&from == this);
253  // No switch required.
254  return EAGAIN;
255 
256  case Dead:
257  // Can't run dead fibers.
258  return EPERM;
259 
260  default:
261  zth_assert(false);
262  return EINVAL;
263  }
264  }
265 
266  bool allowYield(Timestamp const& now = Timestamp::now()) const noexcept
267  {
268  return state() != Running || m_stateEnd < now;
269  }
270 
271  void kill() noexcept
272  {
273  if(state() == Dead)
274  return;
275 
276  zth_dbg(fiber, "[%s] Killed", id_str());
277  setState(Dead);
278  }
279 
280  void nap(Timestamp const& sleepUntil = Timestamp::null()) noexcept
281  {
282  switch(state()) {
283  case New:
284  // Postpone actual sleep
285  zth_dbg(fiber, "[%s] Sleep upon startup", id_str());
286  m_stateNext = Waiting;
287  break;
288  case Ready:
289  case Running:
290  if(sleepUntil.isNull())
291  zth_dbg(fiber, "[%s] Sleep", id_str());
292  else
293  zth_dbg(fiber, "[%s] Sleep for %s", id_str(),
294  (sleepUntil - Timestamp::now()).str().c_str());
295  setState(Waiting);
296  m_stateNext = Ready;
297  break;
298  case Suspended:
299  zth_dbg(fiber, "[%s] Schedule sleep after resume", id_str());
300  m_stateNext = Waiting;
301  break;
302  case Waiting:
303  default:
304  return;
305  }
306 
307  m_stateEnd = sleepUntil;
308  }
309 
310  void wakeup() noexcept
311  {
312  if(likely(state() == Waiting)) {
313  setState(m_stateNext);
314  switch(state()) {
315  case Suspended:
316  zth_dbg(fiber, "[%s] Suspend after wakeup", id_str());
317  m_stateNext = Ready;
318  break;
319  default:
320  zth_dbg(fiber, "[%s] Wakeup", id_str());
321  }
322  } else if(state() == Suspended && m_stateNext == Waiting) {
323  zth_dbg(fiber, "[%s] Set wakeup after suspend", id_str());
324  m_stateNext = Ready;
325  }
326  }
327 
328  void suspend() noexcept
329  {
330  switch(state()) {
331  case New:
332  m_stateNext = New;
333  break;
334  case Running:
335  case Ready:
336  m_stateNext = Ready;
337  break;
338  case Waiting:
339  m_stateNext = Waiting;
340  case Suspended:
341  default:
342  // Ignore.
343  return;
344  }
345 
346  zth_dbg(fiber, "[%s] Suspend", id_str());
348  }
349 
350  void resume() noexcept
351  {
352  if(state() != Suspended)
353  return;
354 
355  zth_dbg(fiber, "[%s] Resume", id_str());
356  setState(m_stateNext);
357  if(state() == New)
358  m_stateNext = Ready;
359  }
360 
361  string str() const
362  {
363  string res = format("%s", id_str());
364 
365  switch(state()) {
366  case Uninitialized:
367  res += " Uninitialized";
368  break;
369  case New:
370  res += " New";
371  break;
372  case Ready:
373  res += " Ready";
374  break;
375  case Running:
376  res += " Running";
377  break;
378  case Waiting:
379  res += " Waiting";
380  if(!m_stateEnd.isNull())
381  res +=
382  format(" (%s remaining)",
383  Timestamp::now().timeTo(stateEnd()).str().c_str());
384  break;
385  case Suspended:
386  res += " Suspended";
387  break;
388  case Dead:
389  res += " Dead";
390  break;
391  }
392 
393  res += format(" t=%s", m_totalTime.str().c_str());
394  return res;
395  }
396 
397 protected:
398  virtual void changedName(string const& name) override
399  {
400  zth_dbg(fiber, "[%s] Renamed to %s", id_str(), name.c_str());
401  }
402 
403  void setState(State state, Timestamp const& t = Timestamp::now()) noexcept
404  {
405  if(m_state == state)
406  return;
407 
408  m_state = state;
409 
410  zth_perf_event(*this, m_state, t);
411 
412  if(state == Dead && hookDead)
413  hookDead(*this);
414  }
415 
416  static void fiberEntry(void* that) noexcept
417  {
418  zth_assert(that);
419  // cppcheck-suppress nullPointerRedundantCheck
420  static_cast<Fiber*>(that)->fiberEntry_();
421  }
422 
423  void fiberEntry_() noexcept
424  {
425  zth_dbg(fiber, "[%s] Entry", id_str());
426 
427  try {
428  m_entry(m_entryArg);
429  } catch(std::exception const& e) {
430 #ifdef __cpp_exceptions
431  zth_dbg(fiber, "[%s] Uncaught exception; %s", id_str(), e.what());
432 #endif
433  } catch(...) {
434  zth_dbg(fiber, "[%s] Uncaught exception", id_str());
435  }
436 
437  zth_dbg(fiber, "[%s] Exit", id_str());
438  kill();
439  }
440 
441 private:
442  State m_state;
443  State m_stateNext;
444  Entry m_entry;
445  EntryArg m_entryArg;
446  ContextAttr m_contextAttr;
447  Context* m_context;
448  void* m_fls;
449  TimeInterval m_totalTime;
450  Timestamp m_startRun;
451  Timestamp m_stateEnd;
452  TimeInterval m_timeslice;
453  TimeInterval m_dtMax;
454  list_type<std::pair<void (*)(Fiber&, void*), void*> >::type m_cleanup;
455 };
456 
466 class Runnable {
469 protected:
470  constexpr Runnable() noexcept
471  : m_fiber()
472  {}
473 
474 public:
476 
477  int run();
478 
479  Fiber* fiber() const noexcept
480  {
481  return m_fiber;
482  }
483 
484  operator Fiber&() const noexcept
485  {
486  zth_assert(fiber());
487  return *fiber();
488  }
489 
490  char const* id_str() const
491  {
492  return fiber() ? fiber()->id_str() : "detached Runnable";
493  }
494 
495 protected:
496  virtual int fiberHook(Fiber& f)
497  {
498  f.addCleanup(&cleanup_, this);
499  m_fiber = &f;
500  return 0;
501  }
502 
503  virtual void cleanup()
504  {
505  m_fiber = nullptr;
506  }
507 
508  virtual void entry() = 0;
509 
510 private:
511  static void cleanup_(Fiber& UNUSED_PAR(f), void* that)
512  {
513  Runnable* r = static_cast<Runnable*>(that);
514  if(likely(r)) {
515  zth_assert(&f == r->m_fiber);
516  r->cleanup();
517  }
518  }
519 
520  static void entry_(void* that)
521  {
522  if(likely(that))
523  static_cast<Runnable*>(that)->entry();
524  }
525 
526 private:
527  Fiber* m_fiber;
528 };
529 
530 __attribute__((pure)) Fiber& currentFiber() noexcept;
531 
536 ZTH_EXPORT inline void* fls() noexcept
537 {
538  return currentFiber().fls();
539 }
540 
548 ZTH_EXPORT inline void setFls(void* data = nullptr) noexcept
549 {
550  return currentFiber().setFls(data);
551 }
552 
553 } // namespace zth
554 
560 EXTERN_C ZTH_EXPORT ZTH_INLINE void* zth_fls() noexcept
561 {
562  return zth::fls();
563 }
564 
570 EXTERN_C ZTH_EXPORT ZTH_INLINE void zth_setFls(void* data = nullptr) noexcept
571 {
572  zth::setFls(data);
573 }
574 
575 #else // !__cplusplus
576 
577 ZTH_EXPORT void* zth_fls();
578 ZTH_EXPORT void* zth_setFls(void* data);
579 
580 #endif // __cplusplus
581 
582 EXTERN_C ZTH_EXPORT int main_fiber(int argc, char** argv);
583 
584 #endif // ZTH_FIBER_H
Save a backtrace.
Definition: perf.h:52
void print(int color=-1) const
Definition: perf.cpp:651
The fiber.
Definition: fiber.h:52
State state() const noexcept
Definition: fiber.h:143
void setState(State state, Timestamp const &t=Timestamp::now()) noexcept
Definition: fiber.h:403
static FiberHook * hookDead
Hook to be called when a Fiber is destroyed.
Definition: fiber.h:69
void wakeup() noexcept
Definition: fiber.h:310
void() FiberHook(Fiber &)
Definition: fiber.h:55
void setFls(void *data=nullptr) noexcept
Definition: fiber.h:153
void * fls() const noexcept
Definition: fiber.h:148
size_t stackSize() const noexcept
Definition: fiber.h:128
void(* Entry)(EntryArg)
Definition: fiber.h:74
int init(Timestamp const &now=Timestamp::now())
Definition: fiber.h:178
bool allowYield(Timestamp const &now=Timestamp::now()) const noexcept
Definition: fiber.h:266
virtual void changedName(string const &name) override
Definition: fiber.h:398
static uintptr_t const ExitUnknown
Definition: fiber.h:75
TimeInterval const & totalTime() const noexcept
Definition: fiber.h:168
Context * context() const noexcept
Definition: fiber.h:138
string str() const
Definition: fiber.h:361
void fiberEntry_() noexcept
Definition: fiber.h:423
void suspend() noexcept
Definition: fiber.h:328
int setStackSize(size_t size) noexcept
Definition: fiber.h:119
Timestamp const & stateEnd() const noexcept
Definition: fiber.h:163
@ Suspended
Definition: fiber.h:71
@ Dead
Definition: fiber.h:71
@ Waiting
Definition: fiber.h:71
@ New
Definition: fiber.h:71
@ Ready
Definition: fiber.h:71
@ Running
Definition: fiber.h:71
@ Uninitialized
Definition: fiber.h:71
virtual ~Fiber() override
Definition: fiber.h:98
size_t stackUsage() const
Definition: fiber.h:133
Fiber(Entry entry, EntryArg arg=EntryArg())
Definition: fiber.h:77
static FiberHook * hookNew
Hook to be called when a Fiber is created.
Definition: fiber.h:62
int run(Fiber &from, Timestamp now=Timestamp::now())
Definition: fiber.h:200
void resume() noexcept
Definition: fiber.h:350
void nap(Timestamp const &sleepUntil=Timestamp::null()) noexcept
Definition: fiber.h:280
ContextAttr::EntryArg EntryArg
Definition: fiber.h:73
void addCleanup(void(*f)(Fiber &, void *), void *arg)
Definition: fiber.h:173
Timestamp const & runningSince() const noexcept
Definition: fiber.h:158
void kill() noexcept
Definition: fiber.h:271
static void fiberEntry(void *that) noexcept
Definition: fiber.h:416
An abstract class, that can be started as a fiber.
Definition: fiber.h:466
int run()
Definition: fiber.cpp:18
virtual int fiberHook(Fiber &f)
Definition: fiber.h:496
char const * id_str() const
Definition: fiber.h:490
Fiber * fiber() const noexcept
Definition: fiber.h:479
virtual void cleanup()
Definition: fiber.h:503
virtual ~Runnable()=default
constexpr Runnable() noexcept
Definition: fiber.h:470
virtual void entry()=0
Convenient wrapper around struct timespec that contains a time interval.
Definition: time.h:52
string str() const
Definition: time.h:404
Convenient wrapper around struct timespec that contains an absolute timestamp.
Definition: time.h:527
static Timestamp now()
Definition: time.h:554
constexpr bool isNull() const noexcept
Definition: time.h:679
static constexpr Timestamp null() noexcept
Definition: time.h:674
Keeps track of a process-wide unique ID within the type T.
Definition: util.h:657
virtual char const * id_str() const override
Definition: util.h:751
string const & name() const noexcept
Definition: util.h:725
void const * normptr() const noexcept
Definition: util.h:715
int main_fiber(int argc, char **argv)
Definition: main.cpp:14
void * zth_fls() noexcept
Return the fiber-local storage, as set by setFls().
Definition: fiber.h:560
void zth_setFls(void *data=nullptr) noexcept
Set the fiber-local storage.
Definition: fiber.h:570
#define zth_config(name)
Checks if the given zth::Config field is enabled.
Definition: config.h:46
void setFls(void *data=nullptr) noexcept
Set the fiber-local storage.
Definition: fiber.h:548
Fiber & currentFiber() noexcept
Return the currently executing fiber.
Definition: worker.h:398
fiber_type< F >::factory fiber(F f, char const *name=nullptr)
Create a new fiber.
Definition: async.h:713
void * fls() noexcept
Return the fiber-local storage, as set by setFls().
Definition: fiber.h:536
constexpr auto entry
Guard that is only enabled upon entry of a state.
Definition: fsm14.h:2008
void perf_mark(char const *marker)
Put a string marker into the perf output.
Definition: perf.h:324
#define zth_perf_event(...)
Construct a zth::PerfEvent with provided parameters, and forward it to the perf buffer for later proc...
Definition: perf.h:292
#define zth_dbg(group, fmt, a...)
Debug printf()-like function.
Definition: util.h:210
void log_color(int color, char const *fmt,...)
Logs a given printf()-like formatted string using an ANSI color code.
Definition: util.h:297
#define ZTH_CLASS_NEW_DELETE(T)
Define new/delete operators for a class, which are allocator-aware.
Definition: allocator.h:114
void zth_init()
Perform one-time global initialization of the Zth library.
Definition: init.cpp:25
#define is_default
Definition: macros.h:205
#define ZTH_INLINE
Definition: macros.h:130
#define UNUSED_PAR(name)
Definition: macros.h:79
Definition: allocator.h:23
void context_switch(Context *from, Context *to) noexcept
Perform context switch.
Definition: context.cpp:116
void context_destroy(Context *context) noexcept
Destroy and cleanup a context.
Definition: context.cpp:99
int context_create(Context *&context, ContextAttr const &attr) noexcept
Create a context.
Definition: context.cpp:58
string format(char const *fmt,...)
Format like sprintf(), but save the result in an zth::string.
Definition: util.h:503
size_t context_stack_usage(Context *context) noexcept
Return the high water mark of the stack of the given context.
Definition: context.cpp:143
The configuration of Zth.
Definition: zth_config.h:22
size_t stackSize
Definition: context.h:80
void * EntryArg
Definition: context.h:71
static int const Print_perf
Definition: config.h:123
std::list type using Config::Allocator::type.
Definition: allocator.h:153
#define ZTH_DBG_PREFIX
Prefix for every zth_dbg() call.
Definition: util.h:198
#define zth_assert(expr)
assert(), but better integrated in Zth.
Definition: util.h:236
#define likely(expr)
Marks the given expression to likely be evaluated to true.
Definition: util.h:42
#define ZTH_CLASS_NOCOPY(Class)
Definition: util.h:254
#define unlikely(expr)
Marks the given expression to likely be evaluated to true.
Definition: util.h:56