lumiera_documentation/src/lib/sync.hpp
Ichthyostega 8e250839f4 Application: Adjustments to the Layer structure
During the last months, a lot of gradual changes to policies and guidelines
had been finalised and documented. Moreover, recent clarifications regarding
memory management in the render engine will lead to a clearer definition
of the »Vault« Layer's role, with some ''engine code'' expected to move down.

On this occasion, I reworked relevant sections in the documentation to
confirm the role that now falls to central parts of the overall architecture.
Notably I decided to rename those parts related to the application backbone
as »Vessel« — to underpin the more active and defining role they assumed.
2026-03-03 22:16:09 +01:00

286 lines
9.9 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
SYNC.hpp - generic helper for object based locking and synchronisation
Copyright (C)
2007, Christian Thaeter <ct@pipapo.org>
2008, Hermann Vosseler <Ichthyostega@web.de>
2023, Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
*/
/** @file sync.hpp
** Object Monitor based synchronisation.
** The actual locking, signalling and waiting is implemented by delegating to the
** a _mutex_ and for waiting also to a _condition variable_ as provided by the C++
** standard library. The purpose of the Sync baseclass is to provide a clear and simple
** „everyday“ concurrency coordination based on the *Object Monitor Pattern*. This
** pattern describes a way of dealing with synchronisation known to play well with
** scoping, encapsulation and responsibility for a single purpose. For performance
** critical code, other solutions (e.g. Atomics) might be preferable.
**
** # Usage
**
** A class becomes _lockable_ by inheriting from lib::Sync with the appropriate
** parametrisation. This causes any instance to inherit a monitor member (object),
** maintaining a dedicated a mutex and (optionally) a condition variable for waiting.
** The actual synchronisation is achieved by placing a guard object as local (stack)
** variable into a given scope (typically a member function body). This guard object
** of class lib::Sync::Lock accesses the enclosing object's monitor and automatically
** manages the locking and unlocking; optionally it may also be used to perform a
** [wait-on-condition] — the call to `Lock::wait(predicate)` will first check the
** predicate, and if it does not yield `true`, the thread will be put to sleep.
** It must be awakened from another thread by invoking `notify_one|all` and will
** then re-check the condition predicate. The `wait_for` variant allows to set
** a timeout to limit the sleep state, which implies however that the call may
** possibly return `false` in case the condition predicate is not (yet) fulfilled.
** @note
** - It is important to select a suitable parametrisation of the monitor.
** This is done by specifying one of the defined policy classes.
** - Be sure to pick the recursive mutex implementation when recursive calls
** of synchronised functions can't be avoided. (performance penalty)
** - You can't use the Lock#wait and Lock#notify functions unless you pick
** a parametrisation including a condition variable.
** - The "this" pointer is fed to the ctor of the Lock guard object. Thus
** you may use any object's monitor as appropriate, especially in cases
** when adding the monitor to a given class may cause size problems.
** - There is a special variant of the Lock guard called ClassLock, which
** can be used to lock based on a type, not an instance.
**
** [wait-on-condition]: https://en.cppreference.com/w/cpp/thread/condition_variable_any/wait
** @see mutex.h
** @see sync-locking-test.cpp
** @see sync-waiting-test.cpp
** @see \ref steam::control::DispatcherLoop "usage example: receiving session coomands"
** @see \ref subsystem-runner.hpp "usage example: subsystem start/shutdown"
*/
#ifndef LIB_SYNC_H
#define LIB_SYNC_H
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/util.hpp"
#include <mutex>
#include <condition_variable>
#include <chrono>
namespace lib {
/** Helpers and building blocks for Monitor based synchronisation */
namespace sync {
/* ========== policies to define required degree of locking functionality ============== */
template<class MTX>
struct Condition
: MTX
, std::condition_variable_any
{
template<class PRED>
void
wait (PRED&& predicate)
{
condVar().wait (mutex(), std::forward<PRED> (predicate));
}
/** @return `false` in case of timeout,
* `true` if predicate is fulfilled at return
*/
template<class REPR, class PERI, class PRED>
bool
wait_for (std::chrono::duration<REPR, PERI> const& timeout, PRED&& predicate)
{
return condVar().wait_for (mutex(), timeout, std::forward<PRED> (predicate));
}
MTX&
mutex()
{
return static_cast<MTX&> (*this);
}
std::condition_variable_any&
condVar()
{
return static_cast<std::condition_variable_any&> (*this);
}
};
struct NoLocking
{
void lock() { /* boo */ }
void unlock() noexcept { /* hoo */ }
};
/* ========== Object Monitor ============== */
/**
* Object Monitor for synchronisation and waiting.
* Implemented by a (wrapped) set of sync primitives,
* which are default constructible and noncopyable.
*/
template<class IMPL>
class Monitor
: IMPL
, util::NonCopyable
{
public:
void lock() { IMPL::lock(); }
void unlock() noexcept { IMPL::unlock(); }
void notify_one() noexcept { IMPL::notify_one(); }
void notify_all() noexcept { IMPL::notify_all(); }
template<class PRED>
void
wait (PRED&& predicate)
{
IMPL::wait (std::forward<PRED>(predicate));
}
template<class DUR, class PRED>
bool
wait_for (DUR const& timeout, PRED&& predicate)
{
return IMPL::wait_for (timeout, std::forward<PRED> (predicate));
}
};
using NonrecursiveLock_NoWait = std::mutex;
using RecursiveLock_NoWait = std::recursive_mutex;
using NonrecursiveLock_Waitable = Condition<std::mutex>;
using RecursiveLock_Waitable = Condition<std::recursive_mutex>;
} // namespace sync (helpers and building blocks)
/* Interface to be used by client code:
* Inherit from class Sync with a suitable Policy.
* Then use the embedded Lock class.
*/
/* ======= Policy classes ======= */
using sync::NonrecursiveLock_NoWait;
using sync::NonrecursiveLock_Waitable;
using sync::RecursiveLock_NoWait;
using sync::RecursiveLock_Waitable;
/*********************************************************************//**
* Facility for monitor object based locking.
* To be attached either on a per class base or per object base.
* Typically, the client class will inherit from this template (but it
* is possible to use it stand-alone, if inheriting isn't an option).
* The interface for clients to access the functionality is the embedded
* Lock template, which should be instantiated as an automatic variable
* within the scope to be protected.
*
* # Usage
* - for *locking*, just place an instant of the embedded Lock into the local
* scope to be protected. All lock instances within the same object share
* the monitor; thus any time, only one of them gets the mutex all other
* instances block on construction.
* - for *waiting* likewise place an instance of the Lock class (which gets
* you a mutex). Then invoke the wait method on that instance; this suspends
* the current thread and releases the mutex. To awake and check the condition,
* some other thread must invoke the Lock::notify() within the same object.
* The Lock::wait() call returns `true` when the condition was met, and
* `false` if awakened due to timeout. The call might throw in case of
* technical errors. In any case, when returning from the `wait()` call,
* the mutex has been re-acquired.
*/
template<class CONF = NonrecursiveLock_NoWait>
class Sync
{
using Monitor = sync::Monitor<CONF>;
mutable Monitor objectMonitor_;
public:
static Monitor&
getMonitor(Sync const* forThis)
{
REQUIRE (forThis);
return forThis->objectMonitor_;
}
/*****************************************//**
* scoped guard to control the actual locking.
*/
class Lock
: util::NonCopyable
{
Monitor& mon_;
public:
template<class X>
Lock(X* it) : mon_{getMonitor(it)}{ mon_.lock(); }
~Lock() { mon_.unlock(); }
void notify_one() { mon_.notify_one(); }
void notify_all() { mon_.notify_all(); }
template<class PRED>
void
wait (PRED&& predicate)
{
mon_.wait (std::forward<PRED>(predicate));
}
template<class DUR, class PRED>
bool
wait_for (DUR const& timeout, PRED&& predicate)
{
return mon_.wait_for (timeout, std::forward<PRED> (predicate));
}
/** convenience shortcut:
* Locks and immediately enters wait state on the given predicate
*/
template<class X, class PRED>
Lock(X* it, PRED&& predicate)
: mon_(getMonitor(it))
{
mon_.lock();
try {
mon_.wait (std::forward<PRED>(predicate));
}
catch(...)
{
mon_.unlock();
throw;
}
}
protected:
/** for creating a ClassLock */
Lock(Monitor& m)
: mon_{m}
{
mon_.lock();
}
/** subclass access to underlying sync primitives */
Monitor& accessMonitor() { return mon_; }
};
};
} // namespace lib::sync
#endif