@@ -194,6 +194,112 @@ class SharedMutex final {
194194 };
195195 std::unique_ptr<ErlNifRWLock, Deleter> m_handle;
196196};
197+
198+ // Condition variable. Used when threads must wait for a specific
199+ // condition to appear before continuing execution. Condition
200+ // variables must be used with associated mutexes.
201+ class ConditionVariable final {
202+ public:
203+ // Creates a condition variable.
204+ ConditionVariable () : m_handle{enif_cond_create (nullptr )} {
205+ if (!m_handle) {
206+ throw std::runtime_error (" failed to create cond" );
207+ }
208+ }
209+
210+ // Creates a ConditionVariable from an ErlNifCond handle.
211+ explicit ConditionVariable (ErlNifCond *handle) : m_handle{handle} {}
212+
213+ // Creates a condition variable.
214+ //
215+ // `name` is a string identifying the created condition variable. It is used
216+ // to identify the condition variable in planned future debug functionality.
217+ explicit ConditionVariable (const char *name)
218+ : m_handle{enif_cond_create (const_cast <char *>(name))} {
219+ if (!m_handle) {
220+ throw std::runtime_error (" failed to create cond" );
221+ }
222+ }
223+
224+ // Creates a condition variable.
225+ //
226+ // `name` is a string identifying the created condition variable. It is used
227+ // to identify the condition variable in planned future debug functionality.
228+ explicit ConditionVariable (const std::string &name)
229+ : m_handle{enif_cond_create (const_cast <char *>(name.c_str ()))} {
230+ if (!m_handle) {
231+ throw std::runtime_error (" failed to create cond" );
232+ }
233+ }
234+
235+ // Converts this ConditionVariable to a ErlNifConditionVariable handle.
236+ //
237+ // Ownership still belongs to this instance.
238+ operator ErlNifCond *() const & noexcept { return m_handle.get (); }
239+
240+ // Releases ownership of the ErlNifCond handle to the caller.
241+ //
242+ // This operation is only possible by:
243+ // ```
244+ // static_cast<ErlNifCond*>(std::move(rwlock))
245+ // ```
246+ explicit operator ErlNifCond *() && noexcept { return m_handle.release (); }
247+
248+ // Broadcasts on this condition variable. That is, if other threads are
249+ // waiting on the condition variable being broadcast on, all of them are
250+ // woken.
251+ //
252+ // This function is thread-safe.
253+ void notify_all () noexcept { enif_cond_broadcast (m_handle.get ()); }
254+
255+ // Signals on a condition variable. That is, if other threads are waiting on
256+ // the condition variable being signaled, one of them is woken.
257+ //
258+ // This function is thread-safe.
259+ void notify_one () noexcept { enif_cond_signal (m_handle.get ()); }
260+
261+ // Prefer the use of `wait(std::unique_lock<Mutex>&, Predicate)` over this
262+ // function.
263+ //
264+ // Waits on a condition variable. The calling thread is blocked until another
265+ // thread wakes it by signaling or broadcasting on the condition variable.
266+ // Before the calling thread is blocked, it unlocks the mutex passed as
267+ // argument. When the calling thread is woken, it locks the same mutex before
268+ // returning. That is, the mutex currently must be locked by the calling
269+ // thread when calling this function.
270+ //
271+ // `wait` can return even if no one has signaled or broadcast on the condition
272+ // variable. Code calling `wait` is always to be prepared for `wait` returning
273+ // even if the condition that the thread was waiting for has not occurred.
274+ // That is, when returning from `wait`, always check if the condition has
275+ // occurred, and if not call `wait` again.
276+ //
277+ // This function is thread-safe.
278+ void wait (std::unique_lock<Mutex> &lock) noexcept {
279+ enif_cond_wait (m_handle.get (), *lock.mutex ());
280+ }
281+
282+ // Waits on a condition variable. The calling thread is blocked until another
283+ // thread wakes it by signaling or broadcasting on the condition variable.
284+ // Before the calling thread is blocked, it unlocks the mutex passed as
285+ // argument. When the calling thread is woken, it locks the same mutex before
286+ // returning. That is, the mutex currently must be locked by the calling
287+ // thread when calling this function.
288+ //
289+ // This function is thread-safe.
290+ template <typename Predicate>
291+ void wait (std::unique_lock<Mutex> &lock, Predicate pred) {
292+ while (!pred ()) {
293+ enif_cond_wait (m_handle.get (), *lock.mutex ());
294+ }
295+ }
296+
297+ private:
298+ struct Deleter {
299+ void operator ()(ErlNifCond *handle) noexcept { enif_cond_destroy (handle); }
300+ };
301+ std::unique_ptr<ErlNifCond, Deleter> m_handle;
302+ };
197303} // namespace fine
198304
199305#endif
0 commit comments