Lely core libraries  2.2.5
fiber.hpp
Go to the documentation of this file.
1 // NOLINT(legal/copyright)
40 #ifndef LELY_UTIL_FIBER_HPP_
41 #define LELY_UTIL_FIBER_HPP_
42 
44 #include <lely/util/error.hpp>
45 #include <lely/util/fiber.h>
46 
47 #include <utility>
48 
49 namespace lely {
50 namespace util {
51 
56 enum class FiberFlag {
61  SAVE_MASK = FIBER_SAVE_MASK,
63  SAVE_FENV = FIBER_SAVE_FENV,
68  SAVE_ERROR = FIBER_SAVE_ERROR,
73  SAVE_ALL = FIBER_SAVE_ALL,
79  GUARD_STACK = FIBER_GUARD_STACK
80 };
81 
82 constexpr FiberFlag
83 operator~(FiberFlag rhs) {
84  return static_cast<FiberFlag>(~static_cast<int>(rhs));
85 }
86 
87 constexpr FiberFlag 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 class Fiber;
117 
118 namespace detail {
119 
120 struct FiberData {
121  bool terminated{false};
122  bool unwind{false};
123  ::std::exception_ptr eptr{nullptr};
124 };
125 
126 } // namespace detail
127 
133 class FiberThread {
134  friend class Fiber;
135 
136  public:
138  FiberThread() : FiberThread(static_cast<FiberFlag>(0)) {}
139 
147  explicit FiberThread(FiberFlag flags) {
148  if (fiber_thrd_init(static_cast<int>(flags)) == -1)
149  throw_errc("FiberThread");
150  }
151 
162  FiberThread(FiberFlag flags, bool& already) {
163  int result = fiber_thrd_init(static_cast<int>(flags));
164  if (result == -1) throw_errc("FiberThread");
165  already = result != 0;
166  }
167 
168  FiberThread(const FiberThread&) = delete;
169  FiberThread(FiberThread&&) = delete;
170 
171  FiberThread& operator=(const FiberThread&) = delete;
172  FiberThread& operator=(FiberThread&&) = delete;
173 
179 
180  private:
181  static detail::FiberData*
182  data_() noexcept {
183 #if LELY_NO_THREADS
184  static detail::FiberData data_;
185 #else
186  static thread_local detail::FiberData data_;
187 #endif
188  return &data_;
189  }
190 };
191 
193 class Fiber {
194  public:
200  Fiber() noexcept = default;
201 
202  Fiber(const Fiber&) = delete;
203 
209  Fiber(Fiber&& other) noexcept { swap(other); }
210 
211  explicit Fiber(fiber_t* fiber) noexcept : fiber_(fiber) {}
212 
214  template <class F, class = typename ::std::enable_if<!::std::is_same<
215  typename ::std::decay<F>::type, Fiber>::value>::type>
216  explicit Fiber(F&& f) : Fiber(::std::forward<F>(f), 0) {}
217 
219  template <class F>
220  Fiber(F&& f, FiberFlag flags) : Fiber(::std::forward<F>(f), flags, 0) {}
221 
223  template <class F>
224  Fiber(F&& f, ::std::size_t stack_size)
225  : Fiber(::std::forward<F>(f), static_cast<FiberFlag>(0), stack_size) {}
226 
240  template <class F, class = typename ::std::enable_if<compat::is_invocable_r<
241  Fiber, F, Fiber&&>::value>::type>
242  Fiber(F&& f, FiberFlag flags, ::std::size_t stack_size) {
243  fiber_ = fiber_create(
244  &func_<decltype(f)>, static_cast<void*>(::std::addressof(f)),
245  static_cast<int>(flags), sizeof(detail::FiberData), stack_size);
246  if (!fiber_) throw_errc("Fiber");
247  auto data = data_(fiber_);
248  // The default constructor for detail::FiberData does not throw.
249  new (data) detail::FiberData();
250  // The first call to func_() is guaranteed to not throw.
251  *this = ::std::move(*this).resume();
252  // Handle the exception thrown by the fiber.
253  auto eptr = data->eptr;
254  if (eptr) {
255  this->~Fiber();
256  ::std::rethrow_exception(eptr);
257  }
258  }
259 
260  Fiber& operator=(const Fiber&) = delete;
261 
269  Fiber&
270  operator=(Fiber&& other) noexcept {
271  swap(other);
272  return *this;
273  }
274 
289  ~Fiber() {
290  if (fiber_) {
291  auto data = data_(fiber_);
292  if (data) {
293  if (!data->terminated) {
294  data->unwind = true;
295  *this = ::std::move(*this).resume();
296  }
297  data->~FiberData();
298  fiber_destroy(fiber_);
299  }
300  }
301  }
302 
304  explicit operator bool() const noexcept { return fiber_ != nullptr; }
305 
306  operator fiber_t*() && noexcept {
307  fiber_t* tmp = fiber_;
308  fiber_ = nullptr;
309  return tmp;
310  }
311 
325  Fiber
326  resume() && {
327  return resume_(Fiber(fiber_resume(::std::move(*this))));
328  }
329 
344  template <class F>
345  Fiber
346  resume_with(F&& f) && {
347  auto func = [](fiber_t * fiber, void* arg) noexcept {
348  Fiber f(fiber);
349  try {
350  f = (*static_cast<F*>(arg))(::std::move(f));
351  } catch (...) {
352  auto data = data_();
353  if (!data) data = FiberThread::data_();
354  data->eptr = ::std::current_exception();
355  }
356  return static_cast<fiber_t*>(::std::move(f));
357  };
358  auto arg = static_cast<void*>(::std::addressof(f));
359  return resume_(Fiber(fiber_resume_with(::std::move(*this), func, arg)));
360  }
361 
363  void
364  swap(Fiber& other) noexcept {
365  using ::std::swap;
366  swap(fiber_, other.fiber_);
367  }
368 
369  private:
370  template <class F>
371  static fiber_t* func_(fiber_t* fiber, void* arg) noexcept;
372 
373  static detail::FiberData*
374  data_(fiber_t* fiber = nullptr) noexcept {
375  return static_cast<detail::FiberData*>(fiber_data(fiber));
376  }
377 
378  Fiber resume_(Fiber&& f);
379 
380  fiber_t* fiber_{nullptr};
381 };
382 
388  friend class Fiber;
389 
390  public:
391  fiber_unwind() noexcept = default;
392 
393  explicit fiber_unwind(Fiber&& f) noexcept : f_(::std::move(f)) {}
394 
395  private:
396  Fiber f_;
397 };
398 
399 template <class F>
400 fiber_t*
401 Fiber::func_(fiber_t* fiber, void* arg) noexcept {
402  auto data = data_();
403  try {
404  // Copy the function to be executed to the stack for later reference.
405  using F_ = typename ::std::decay<F>::type;
406  F_ func{::std::forward<F_>(static_cast<F>(*static_cast<F_*>(arg)))};
407  // Return to the constructor.
408  Fiber f(fiber_resume(fiber));
409  fiber = nullptr;
410  try {
411  // Rethrow the exception thrown the function passed to resume_with(), if
412  // any.
413  if (data->eptr) ::std::rethrow_exception(data->eptr);
414  // Invoke the function, unless the fiber is being destroyed.
415  if (!data->unwind) f = func(::std::move(f));
416  } catch (fiber_unwind& e) {
417  f = ::std::move(e.f_);
418  }
419  data->terminated = true;
420  return ::std::move(f);
421  } catch (...) {
422  if (fiber) {
423  // An exception was thrown while copying the function. Store it and return
424  // to the constructor of the fiber.
425  data->eptr = ::std::current_exception();
426  return fiber;
427  }
428  // Similar to threads, exceptions thrown by a fiber function will result in
429  // termination.
430  ::std::terminate();
431  }
432 }
433 
434 inline Fiber
435 Fiber::resume_(Fiber&& f) {
436  auto data = data_();
437  if (!data) data = FiberThread::data_();
438  if (data) {
439  // Store the exception thrown by the function passed to resume_with(), if
440  // any, and clear it, so it does not get thrown again on the next call to
441  // resume().
442  auto eptr = data->eptr;
443  data->eptr = nullptr;
444  // Termination takes precedence over any exception.
445  if (data->unwind) throw fiber_unwind(::std::move(f));
446  if (eptr) ::std::rethrow_exception(eptr);
447  }
448  return ::std::move(f);
449 }
450 
451 } // namespace util
452 } // namespace lely
453 
454 #endif // !LELY_UTIL_FIBER_HPP_
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-sjlj.c:152
fiber
Definition: fiber-sjlj.c:66
type_traits.hpp
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-sjlj.c:314
fiber_destroy
void fiber_destroy(fiber_t *fiber)
Destroys the specified fiber.
Definition: fiber-sjlj.c:299
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:56
lely::util::Fiber::Fiber
Fiber(F &&f, FiberFlag flags, ::std::size_t stack_size)
Constructs a fiber with a newly allocated stack.
Definition: fiber.hpp:242
lely::util::Fiber::swap
void swap(Fiber &other) noexcept
Swaps the states of *this and other.
Definition: fiber.hpp:364
fiber_thrd_fini
void fiber_thrd_fini(void)
Finalizes the fiber associated with the calling thread.
Definition: fiber-sjlj.c:135
lely::util::Fiber::resume_with
Fiber resume_with(F &&f) &&
Suspends the calling fiber and resumes *this, but calls f(other) in the resumed fiber as if called by...
Definition: fiber.hpp:346
lely::util::Fiber::Fiber
Fiber(Fiber &&other) noexcept
Moves the state of other to *this.
Definition: fiber.hpp:209
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_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:77
lely::util::detail::FiberData
Definition: fiber.hpp:120
lely::util::Fiber
A fiber.
Definition: fiber.hpp:193
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
lely::util::FiberThread
Convenience class providing a RAII-style mechanism to ensure the fiber associated with the calling th...
Definition: fiber.hpp:133
lely::util::FiberThread::FiberThread
FiberThread(FiberFlag flags)
Initializes the fiber associated with the calling thread, if it was not already initialized.
Definition: fiber.hpp:147
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
lely::util::fiber_unwind
The exception used by lely::util::Fiber::~Fiber() to terminate the callable object and unwind its sta...
Definition: fiber.hpp:387
lely::compat::is_invocable_r
Determines whether F can be invoked with the arguments Args... to yield a result that is convertable ...
Definition: type_traits.hpp:215
fiber_resume
fiber_t * fiber_resume(fiber_t *fiber)
Equivalent to fiber_resume_with(fiber, NULL, NULL).
Definition: fiber-sjlj.c:322
lely::util::Fiber::Fiber
Fiber(F &&f, FiberFlag flags)
Equivalent to Fiber::Fiber(f, flags, 0).
Definition: fiber.hpp:220
lely::util::Fiber::Fiber
Fiber(F &&f)
Equivalent to Fiber::Fiber(f, static_cast<FiberFlag>(0), 0).
Definition: fiber.hpp:216
lely::util::Fiber::~Fiber
~Fiber()
Destroys a Fiber instance.
Definition: fiber.hpp:289
lely::util::Fiber::resume
Fiber resume() &&
Suspends the calling fiber and resumes *this.
Definition: fiber.hpp:326
lely::util::Fiber::operator=
Fiber & operator=(Fiber &&other) noexcept
Moves the state of other to *this.
Definition: fiber.hpp:270
lely::util::FiberThread::~FiberThread
~FiberThread()
Finalizes the fiber associated with the calling thread, unless another instance of this class is stil...
Definition: fiber.hpp:178
fiber.h
lely::util::FiberThread::FiberThread
FiberThread()
Equivalent to #FiberThread(static_cast<FiberFlag>(0)).
Definition: fiber.hpp:138
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-sjlj.c:334
lely::util::Fiber::Fiber
Fiber(F &&f, ::std::size_t stack_size)
Equivalent to Fiber::Fiber(f, static_cast<FiberFlag>(0), stack_size).
Definition: fiber.hpp:224
lely::util::FiberThread::FiberThread
FiberThread(FiberFlag flags, bool &already)
Initializes the fiber associated with the calling thread, if it was not already initialized.
Definition: fiber.hpp:162
fiber_thrd_init
int fiber_thrd_init(int flags)
Initializes the fiber associated with the calling thread.
Definition: fiber-sjlj.c:108
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:65
error.hpp
FIBER_SAVE_FENV
#define FIBER_SAVE_FENV
A flag specifying a fiber to save and restore the floating-point environment.
Definition: fiber.h:52
lely::util::Fiber::Fiber
Fiber() noexcept=default
Creates an invalid fiber.