manticore
Main thread executor
Loading...
Searching...
No Matches
manticore.hpp
Go to the documentation of this file.
1#ifndef MANTICORE_MANTICORE_HPP
2#define MANTICORE_MANTICORE_HPP
3
4#include <mutex>
5#include <condition_variable>
6#include <functional>
7#include <string>
8#include <stdexcept>
9
19namespace manticore {
20
33class Executor {
34 std::mutex run_lock;
35 std::condition_variable cv;
36
37 size_t nthreads;
38 size_t ncomplete;
39 std::string fallback_error;
40 std::string error_message;
41
42 enum class Status : char { FREE, PRIMED, FINISHED };
43 Status status;
44 std::function<void()> fun;
45
46 bool initialized = false;
47 bool done() const {
48 return ncomplete == nthreads;
49 }
50
51public:
58 void initialize(size_t n, std::string e) {
59 nthreads = n;
60 ncomplete = 0;
61 fallback_error = std::move(e);
62 error_message.clear();
63 status = Status::FREE;
64 initialized = true;
65 }
66
72 void initialize(size_t n) {
73 initialize(n, "failed main thread execution");
74 }
75
83 void finish_thread(bool notify = true) {
84 // Lock everything before bumping ncomplete to avoid problems with
85 // simultaneous writes. We use a lock rather than an atomic variable to
86 // ensure that the change propagates correctly to the main thread when
87 // it calls done() after notification, otherwise the order of events
88 // across multiple threads is not guaranteed.
89 {
90 std::lock_guard lck(run_lock);
91 ++ncomplete;
92 }
93
94 if (notify) {
95 cv.notify_all(); // possibly trigger loop exit in listen().
96 }
97 }
98
99public:
114 template<class Function_>
115 void run(Function_ f) {
116 if (!initialized) {
117 f();
118 return;
119 }
120
121 // Waiting until the main thread executor is free,
122 // and then assigning it a task.
123 std::unique_lock lk(run_lock);
124 cv.wait(lk, [&]{ return status == Status::FREE; });
125
126 fun = std::move(f);
127 status = Status::PRIMED;
128
129 // Notifying the main thread that there is a task. Only the
130 // main thread waits on PRIMED, so other works should not proceed.
131 lk.unlock();
132 cv.notify_all();
133
134 lk.lock();
135 cv.wait(lk, [&]{ return status == Status::FINISHED; });
136
137 // Making a copy of any error message so we can use it for throwing
138 // after the unlock. Also clearing the error message for the next thread.
139 auto errcopy = error_message;
140 error_message.clear();
141
142 // Unblocking other worker threads, if any are waiting.
143 status = Status::FREE;
144 lk.unlock();
145 cv.notify_all();
146
147 if (!errcopy.empty()) {
148 throw std::runtime_error(errcopy);
149 }
150 }
151
152public:
157 void listen() {
158 while (1) {
159 std::unique_lock lk(run_lock);
160
161 cv.wait(lk, [&]{ return status == Status::PRIMED || done(); });
162 if (done()) {
163 break;
164 }
165
166 try {
167 fun();
168 } catch (std::exception& x) {
169 // No throw, we need to make sure we notify the worker of (failed) completion.
170 error_message = x.what();
171 } catch (...) {
172 // For everything else.
173 error_message = fallback_error;
174 }
175
176 status = Status::FINISHED;
177
178 // Unlock before notifying, see example in https://en.cppreference.com/w/cpp/thread/condition_variable
179 lk.unlock();
180 cv.notify_all();
181 }
182
183 initialized = false;
184 }
185};
186
187}
188
189#endif
Execute arbitrary functions on the main thread.
Definition manticore.hpp:33
void listen()
Definition manticore.hpp:157
void run(Function_ f)
Definition manticore.hpp:115
void initialize(size_t n)
Definition manticore.hpp:72
void finish_thread(bool notify=true)
Definition manticore.hpp:83
void initialize(size_t n, std::string e)
Definition manticore.hpp:58
Defines manticore classes and functions.