Lely core libraries  2.3.4
fiber.hpp
Go to the documentation of this file.
1 // NOLINT(legal/copyright)
39 #ifndef LELY_UTIL_FIBER_HPP_
40 #define LELY_UTIL_FIBER_HPP_
41 
43 #include <lely/util/error.hpp>
44 #include <lely/util/fiber.h>
45 
46 #include <utility>
47 
48 namespace lely {
49 namespace util {
50 
55 enum class FiberFlag {
60  SAVE_MASK = FIBER_SAVE_MASK,
62  SAVE_FENV = FIBER_SAVE_FENV,
67  SAVE_ERROR = FIBER_SAVE_ERROR,
72  SAVE_ALL = FIBER_SAVE_ALL,
78  GUARD_STACK = FIBER_GUARD_STACK
79 };
80 
81 constexpr FiberFlag
82 operator~(FiberFlag rhs) {
83  return static_cast<FiberFlag>(~static_cast<int>(rhs));
84 }
85 
86 constexpr FiberFlag
87 operator&(FiberFlag lhs, FiberFlag rhs) {
88  return static_cast<FiberFlag>(static_cast<int>(lhs) & static_cast<int>(rhs));
89 }
90 
91 constexpr FiberFlag
92 operator^(FiberFlag lhs, FiberFlag rhs) {
93  return static_cast<FiberFlag>(static_cast<int>(lhs) ^ static_cast<int>(rhs));
94 }
95 
96 constexpr FiberFlag
97 operator|(FiberFlag lhs, FiberFlag rhs) {
98  return static_cast<FiberFlag>(static_cast<int>(lhs) | static_cast<int>(rhs));
99 }
100 
101 inline FiberFlag&
102 operator&=(FiberFlag& lhs, FiberFlag rhs) {
103  return lhs = lhs & rhs;
104 }
105 
106 inline FiberFlag&
107 operator^=(FiberFlag& lhs, FiberFlag rhs) {
108  return lhs = lhs ^ rhs;
109 }
110 
111 inline FiberFlag&
112 operator|=(FiberFlag& lhs, FiberFlag rhs) {
113  return lhs = lhs | rhs;
114 }
115 
116 #if __cpp_exceptions
117 
118 class Fiber;
119 
120 namespace detail {
121 
122 struct FiberData {
123  bool terminated{false};
124  bool unwind{false};
125  ::std::exception_ptr eptr{nullptr};
126 };
127 
128 } // namespace detail
129 
135 class FiberThread {
136  friend class Fiber;
137 
138  public:
140  FiberThread() : FiberThread(static_cast<FiberFlag>(0)) {}
141 
149  explicit FiberThread(FiberFlag flags) {
150  if (fiber_thrd_init(static_cast<int>(flags)) == -1)
151  throw_errc("FiberThread");
152  }
153 
164  FiberThread(FiberFlag flags, bool& already) {
165  int result = fiber_thrd_init(static_cast<int>(flags));
166  if (result == -1) throw_errc("FiberThread");
167  already = result != 0;
168  }
169 
170  FiberThread(const FiberThread&) = delete;
171  FiberThread(FiberThread&&) = delete;
172 
173  FiberThread& operator=(const FiberThread&) = delete;
174  FiberThread& operator=(FiberThread&&) = delete;
175 
180  ~FiberThread() { fiber_thrd_fini(); }
181 
182  private:
183  static detail::FiberData*
184  data_() noexcept {
185 #if LELY_NO_THREADS
186  static detail::FiberData data_;
187 #else
188  static thread_local detail::FiberData data_;
189 #endif
190  return &data_;
191  }
192 };
193 
195 class Fiber {
196  public:
202  Fiber() noexcept = default;
203 
204  Fiber(const Fiber&) = delete;
205 
211  Fiber(Fiber&& other) noexcept { swap(other); }
212 
213  explicit Fiber(fiber_t* fiber) noexcept : fiber_(fiber) {}
214 
216  template <class F, class = typename ::std::enable_if<!::std::is_same<
217  typename ::std::decay<F>::type, Fiber>::value>::type>
218  explicit Fiber(F&& f) : Fiber(::std::forward<F>(f), 0) {}
219 
221  template <class F>
222  Fiber(F&& f, FiberFlag flags) : Fiber(::std::forward<F>(f), flags, 0) {}
223 
225  template <class F>
226  Fiber(F&& f, ::std::size_t stack_size)
227  : Fiber(::std::forward<F>(f), static_cast<FiberFlag>(0), stack_size) {}
228 
242  template <class F, class = typename ::std::enable_if<compat::is_invocable_r<
243  Fiber, F, Fiber&&>::value>::type>
244  Fiber(F&& f, FiberFlag flags, ::std::size_t stack_size) {
245  fiber_ = fiber_create(
246  &func_<decltype(f)>, static_cast<void*>(::std::addressof(f)),
247  static_cast<int>(flags), sizeof(detail::FiberData), stack_size);
248  if (!fiber_) throw_errc("Fiber");
249  auto data = data_(fiber_);
250  // The default constructor for detail::FiberData does not throw.
251  new (data) detail::FiberData();
252  // The first call to func_() is guaranteed to not throw.
253  *this = ::std::move(*this).resume();
254  // Handle the exception thrown by the fiber.
255  auto eptr = data->eptr;
256  if (eptr) {
257  this->~Fiber();
258  ::std::rethrow_exception(eptr);
259  }
260  }
261 
262  Fiber& operator=(const Fiber&) = delete;
263 
271  Fiber&
272  operator=(Fiber&& other) noexcept {
273  swap(other);
274  return *this;
275  }
276 
291  ~Fiber() {
292  if (fiber_) {
293  auto data = data_(fiber_);
294  if (data) {
295  if (!data->terminated) {
296  data->unwind = true;
297  *this = ::std::move(*this).resume();
298  }
299  data->~FiberData();
300  fiber_destroy(fiber_);
301  }
302  }
303  }
304 
306  explicit operator bool() const noexcept { return fiber_ != nullptr; }
307 
308  operator fiber_t*() && noexcept {
309  fiber_t* tmp = fiber_;
310  fiber_ = nullptr;
311  return tmp;
312  }
313 
327  Fiber
328  resume() && {
329  return resume_(Fiber(fiber_resume(::std::move(*this))));
330  }
331 
346  template <class F>
347  Fiber
348  resume_with(F&& f) && {
349  auto func = [](fiber_t* fiber, void* arg) noexcept {
350  Fiber f(fiber);
351  try {
352  f = (*static_cast<F*>(arg))(::std::move(f));
353  } catch (...) {
354  auto data = data_();
355  if (!data) data = FiberThread::data_();
356  data->eptr = ::std::current_exception();
357  }
358  return static_cast<fiber_t*>(::std::move(f));
359  };
360  auto arg = static_cast<void*>(::std::addressof(f));
361  return resume_(Fiber(fiber_resume_with(::std::move(*this), func, arg)));
362  }
363 
365  void
366  swap(Fiber& other) noexcept {
367  using ::std::swap;
368  swap(fiber_, other.fiber_);
369  }
370 
371  private:
372  template <class F>
373  static fiber_t* func_(fiber_t* fiber, void* arg) noexcept;
374 
375  static detail::FiberData*
376  data_(fiber_t* fiber = nullptr) noexcept {
377  return static_cast<detail::FiberData*>(fiber_data(fiber));
378  }
379 
380  Fiber resume_(Fiber&& f);
381 
382  fiber_t* fiber_{nullptr};
383 };
384 
389 class fiber_unwind {
390  friend class Fiber;
391 
392  public:
393  fiber_unwind() noexcept = default;
394 
395  explicit fiber_unwind(Fiber&& f) noexcept : f_(::std::move(f)) {}
396 
397  private:
398  Fiber f_;
399 };
400 
401 template <class F>
402 fiber_t*
403 Fiber::func_(fiber_t* fiber, void* arg) noexcept {
404  auto data = data_();
405  try {
406  // Copy the function to be executed to the stack for later reference.
407  using F_ = typename ::std::decay<F>::type;
408  F_ func{::std::forward<F_>(static_cast<F>(*static_cast<F_*>(arg)))};
409  // Return to the constructor.
410  Fiber f(fiber_resume(fiber));
411  fiber = nullptr;
412  try {
413  // Rethrow the exception thrown the function passed to resume_with(), if
414  // any.
415  if (data->eptr) ::std::rethrow_exception(data->eptr);
416  // Invoke the function, unless the fiber is being destroyed.
417  if (!data->unwind) f = func(::std::move(f));
418  } catch (fiber_unwind& e) {
419  f = ::std::move(e.f_);
420  }
421  data->terminated = true;
422  return ::std::move(f);
423  } catch (...) {
424  if (fiber) {
425  // An exception was thrown while copying the function. Store it and return
426  // to the constructor of the fiber.
427  data->eptr = ::std::current_exception();
428  return fiber;
429  }
430  // Similar to threads, exceptions thrown by a fiber function will result in
431  // termination.
432  ::std::terminate();
433  }
434 }
435 
436 inline Fiber
437 Fiber::resume_(Fiber&& f) {
438  auto data = data_();
439  if (!data) data = FiberThread::data_();
440  if (data) {
441  // Store the exception thrown by the function passed to resume_with(), if
442  // any, and clear it, so it does not get thrown again on the next call to
443  // resume().
444  auto eptr = data->eptr;
445  data->eptr = nullptr;
446  // Termination takes precedence over any exception.
447  if (data->unwind) throw fiber_unwind(::std::move(f));
448  if (eptr) ::std::rethrow_exception(eptr);
449  }
450  return ::std::move(f);
451 }
452 
453 #endif // __cpp_exceptions
454 
455 } // namespace util
456 } // namespace lely
457 
458 #endif // !LELY_UTIL_FIBER_HPP_
FIBER_SAVE_ALL
#define FIBER_SAVE_ALL
A combination of those flags in FIBER_SAVE_MASK, FIBER_SAVE_FENV and FIBER_SAVE_ERROR that are suppor...
Definition: fiber.h:68
fiber.h
fiber
A fiber.
Definition: fiber.c:130
type_traits.hpp
lely::util::FiberFlag
FiberFlag
Specifies which properties of the calling environment are saved or restored by a fiber when it is sus...
Definition: fiber.hpp:55
fiber_create
fiber_t * fiber_create(fiber_func_t *func, void *arg, int flags, size_t data_size, size_t stack_size)
Creates a new fiber, allocates a stack and sets up a calling environment to begin executing the speci...
Definition: fiber.c:260
FIBER_SAVE_MASK
#define FIBER_SAVE_MASK
A flag specifying a fiber to save and restore the signal mask (only supported on POSIX platforms).
Definition: fiber.h:47
fiber_thrd_init
int fiber_thrd_init(int flags)
Initializes the fiber associated with the calling thread.
Definition: fiber.c:210
lely::util::throw_errc
void throw_errc(int errc=get_errc())
Throws an std::system_error exception corresponding to the specified or current (thread-specific) nat...
Definition: error.hpp:73
FIBER_GUARD_STACK
#define FIBER_GUARD_STACK
A flag specifying a fiber to add a guard page when allocating the stack frame so that the kernel gene...
Definition: fiber.h:85
fiber_resume_with
fiber_t * fiber_resume_with(fiber_t *fiber, fiber_func_t *func, void *arg)
Suspends the calling fiber and resumes the specified fiber, optionally executing a function before re...
Definition: fiber.c:430
FIBER_SAVE_FENV
#define FIBER_SAVE_FENV
A flag specifying a fiber to save and restore the floating-point environment.
Definition: fiber.h:52
fiber_resume
fiber_t * fiber_resume(fiber_t *fiber)
Equivalent to fiber_resume_with(fiber, NULL, NULL).
Definition: fiber.c:424
fiber_data
void * fiber_data(const fiber_t *fiber)
Returns a pointer to the data region of the specified fiber, or of the calling fiber if fiber is NULL...
Definition: fiber.c:416
fiber_destroy
void fiber_destroy(fiber_t *fiber)
Destroys the specified fiber.
Definition: fiber.c:395
FIBER_SAVE_ERROR
#define FIBER_SAVE_ERROR
A flag specifying a fiber to save and restore the error values (i.e., errno and GetLastError() on Win...
Definition: fiber.h:58
fiber_thrd_fini
void fiber_thrd_fini(void)
Finalizes the fiber associated with the calling thread.
Definition: fiber.c:242
error.hpp