AIStatefulTask ‐ Asynchronous, Stateful Task Scheduler library.

Threads-like task objects evolving through user-defined states.

TaskCounterGate.h
1#pragma once
2
3#include "utils/macros.h"
4#include <mutex>
5#include <atomic>
6#include <condition_variable>
7#include <exception>
8#include "debug.h"
9
10namespace statefultask {
11
12// This class is intended to detect at program termination when all tasks (that call `increment`
13// in initialization_impl and `decrement` in finish_impl) are finished.
14//
15// It is only safe when no thread calls `increment` anymore once `wait` has been called.
16// Therefore, the thread that calls `wait` must make sure that no new tasks (that use this object)
17// will be created and/or run anymore.
18//
19// Tasks that need to be waited for at program termination should call `increment` as soon as
20// possible but no sooner than that it is guaranteed that they will also be run and execute
21// initialize_impl. Tasks that do not restart (by calling run() from the callback) can call
22// `increment` from their constructor and `decrement` from their destructor.
23//
24// If a task might be restarted, or when they are created elsewhere and run later, should
25// call `increment` from their initialization_impl. Because every class has an initialization_impl
26// and finish_impl that are called anyway; it is best to always use those to call increment
27// and decrement whenever possible.
28//
29// Note that there is a race condition when calling increment() from initialization_impl when
30// a task is not run in immediate mode (ie, from the thread pool): such tasks might already be
31// created and added to the thread pool; making it possible for them to start and call initialization_impl
32// after `wait` has already been called.
33//
35{
36 using counter_type = uint32_t;
37 static constexpr counter_type not_waiting_magic = 0x10000; // Should be larger than the maximum number of simultaneous running tasks (that use this TaskCounterGate).
38 static constexpr counter_type count_mask = not_waiting_magic - 1;
39 std::mutex m_counter_is_zero_mutex; // Mutex used for the condition variable.
40 std::condition_variable m_counter_is_zero; // Used to wait until m_counter became zero.
41 std::atomic<counter_type> m_counter{not_waiting_magic}; // Count is set to a value larger than zero in order to stop decrement
42 // from calling wakeup() unless wait() has already been entered by another thread.
43 void wakeup();
44
45 [[gnu::always_inline]] bool is_waiting() const
46 {
47 return !(m_counter & not_waiting_magic);
48 }
49
50 public:
51 // Call from initialize_impl().
52 void increment()
53 {
54 // If increment() is called after wait() was already called we can't run this task;
55 // it would be possible that the waiting thread already left wait().
56 if (is_waiting())
57 {
58 Dout(dc::warning, "TaskCounterGate::increment called after wait [" << this << "]");
59 throw std::exception();
60 }
61 m_counter.fetch_add(1, std::memory_order::relaxed);
62 }
63
64 // Call from finish_impl().
65 void decrement()
66 {
67 counter_type previous_value = m_counter.fetch_sub(1, std::memory_order::relaxed);
68 // Call increment() / decrement() in pairs.
69 ASSERT(previous_value != 0);
70 if (AI_UNLIKELY(previous_value == 1)) // Unlikely because normally the not_waiting_magic will be set.
71 wakeup();
72 }
73
74 // Block until all remaining tasks finished / called decrement.
75 void wait();
76};
77
78} // namespace statefultask
Definition: TaskCounterGate.h:35
Tasks defined by the library project are put into this namespace.
Definition: AIStatefulTask.h:857