LibSync: Abstract away Mutex implementation

This commit adds in-place pimpl to abstract away the implementation of
Mutex. It also adds policies to configure the type of mutex desired.

Because of the tight integration between mutex and condition variables
they also needed to be reworked and the changes have to be in one commit
to retain atomicity.

A win32 and pthread implemenation is provided to make sure the api
works with both.
This commit is contained in:
R-Goc 2025-10-30 18:32:03 +01:00 committed by Gregory Bertilson
parent 02bb892d7a
commit 50be9493d2
14 changed files with 543 additions and 101 deletions

View file

@ -271,7 +271,7 @@ struct ThreadData {
s_thread_data.remove(s_thread_id);
}
Sync::Mutex mutex;
Sync::RecursiveMutex mutex;
// Each thread has its own timers, notifiers and a wake pipe.
TimeoutSet timeouts;

View file

@ -12,7 +12,7 @@
namespace Audio {
static PulseAudioContext* s_pulse_audio_context;
static Sync::Mutex s_pulse_audio_context_mutex;
static Sync::RecursiveMutex s_pulse_audio_context_mutex;
ErrorOr<NonnullRefPtr<PulseAudioContext>> PulseAudioContext::the()
{

View file

@ -103,7 +103,7 @@ private:
void seek(AK::Duration timestamp, SeekCompletionHandler&&);
[[nodiscard]] Sync::MutexLocker take_lock() const { return Sync::MutexLocker(m_mutex); }
[[nodiscard]] Sync::MutexLocker<Sync::Mutex> take_lock() const { return Sync::MutexLocker(m_mutex); }
void wake() const { m_wait_condition.broadcast(); }
AudioDecoder const& decoder() const { return *m_decoder; }

View file

@ -100,7 +100,7 @@ private:
TimeRanges buffered_time_ranges() const;
[[nodiscard]] Sync::MutexLocker take_lock() const { return Sync::MutexLocker(m_mutex); }
[[nodiscard]] Sync::MutexLocker<Sync::Mutex> take_lock() const { return Sync::MutexLocker(m_mutex); }
void wake() const { m_wait_condition.broadcast(); }
private:

View file

@ -1,5 +1,7 @@
set(SOURCES
Mutex.cpp
)
ladybird_lib(LibSync sync EXPLICIT_SYMBOL_EXPORT)
if (WIN32)
set(SOURCES MutexWindows.cpp ConditionVariableWindows.cpp)
else()
set(SOURCES MutexPOSIX.cpp ConditionVariablePOSIX.cpp)
endif()
ladybird_lib(LibSync sync EXPLICIT_SYMBOL_EXPORT)

View file

@ -1,63 +1,69 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <filmroellchen@serenityos.org>.
* Copyright (c) 2025, Ryszard Goc <ryszardgoc@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Concepts.h>
#include <AK/Function.h>
#include <AK/Noncopyable.h>
#include <AK/Platform.h>
#include <LibSync/Export.h>
#include <LibSync/Mutex.h>
#include <pthread.h>
#include <sys/types.h>
#include <LibSync/Policy.h>
#if !defined(AK_OS_WINDOWS)
# include <pthread.h>
#endif
namespace Sync {
// A signaling condition variable that wraps over the pthread_cond_* APIs.
class ConditionVariable {
friend class Mutex;
// A signaling condition variable that wraps over the platform APIs.
// On posix it is a wrapper of pthread_cond_*.
// On Windows it wraps ConditionVariable
// TODO: Implement timed_wait()
template<typename MutexType>
requires Detail::IsIntraprocess<MutexType> && Detail::IsNonRecursive<MutexType>
class SYNC_API ConditionVariableBase {
AK_MAKE_NONCOPYABLE(ConditionVariableBase);
AK_MAKE_NONMOVABLE(ConditionVariableBase);
public:
ConditionVariable(Mutex& to_wait_on)
: m_to_wait_on(to_wait_on)
{
auto result = pthread_cond_init(&m_condition, nullptr);
VERIFY(result == 0);
}
ALWAYS_INLINE ~ConditionVariable()
{
auto result = pthread_cond_destroy(&m_condition);
VERIFY(result == 0);
}
ConditionVariableBase(MutexType& to_wait_on);
~ConditionVariableBase();
// As with pthread APIs, the mutex must be locked or undefined behavior ensues.
ALWAYS_INLINE void wait()
{
auto result = pthread_cond_wait(&m_condition, &m_to_wait_on.m_mutex);
VERIFY(result == 0);
}
// Condition variables are allowed spurious wakeups. As such waiting on a condition in a loop is preferred.
void wait();
ALWAYS_INLINE void wait_while(Function<bool()> condition)
{
while (condition())
wait();
}
// Release at least one of the threads waiting on this variable.
ALWAYS_INLINE void signal()
{
auto result = pthread_cond_signal(&m_condition);
VERIFY(result == 0);
}
void signal();
// Release all of the threads waiting on this variable.
ALWAYS_INLINE void broadcast()
{
auto result = pthread_cond_broadcast(&m_condition);
VERIFY(result == 0);
}
void broadcast();
private:
pthread_cond_t m_condition;
Mutex& m_to_wait_on;
#ifdef AK_OS_WINDOWS
using StorageType = void*;
#else
using StorageType = pthread_cond_t;
#endif
alignas(StorageType) unsigned char m_storage[sizeof(StorageType)];
MutexType& m_to_wait_on;
};
template<typename MutexType>
ConditionVariableBase(MutexType&) -> ConditionVariableBase<MutexType>;
using ConditionVariable = ConditionVariableBase<Mutex>;
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2021, kleines Filmröllchen <filmroellchen@serenityos.org>.
* Copyright (c) 2025, Ryszard Goc <ryszardgoc@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSync/ConditionVariable.h>
#include <LibSync/Export.h>
#include <LibSync/Mutex.h>
#include <pthread.h>
namespace Sync {
namespace {
ALWAYS_INLINE pthread_cond_t* to_impl(void* ptr)
{
return reinterpret_cast<pthread_cond_t*>(ptr);
}
}
template<typename MutexType>
requires Detail::IsIntraprocess<MutexType> && Detail::IsNonRecursive<MutexType>
ConditionVariableBase<MutexType>::ConditionVariableBase(MutexType& to_wait_on)
: m_to_wait_on(to_wait_on)
{
static_assert(sizeof(m_storage) == sizeof(pthread_cond_t));
int result = pthread_cond_init(to_impl(m_storage), nullptr);
VERIFY(result == 0);
}
template<typename MutexType>
requires Detail::IsIntraprocess<MutexType> && Detail::IsNonRecursive<MutexType>
ConditionVariableBase<MutexType>::~ConditionVariableBase()
{
int result = pthread_cond_destroy(to_impl(m_storage));
VERIFY(result == 0);
}
template<typename MutexType>
requires Detail::IsIntraprocess<MutexType> && Detail::IsNonRecursive<MutexType>
void ConditionVariableBase<MutexType>::wait()
{
int result = pthread_cond_wait(to_impl(m_storage), reinterpret_cast<pthread_mutex_t*>(m_to_wait_on.m_storage));
VERIFY(result == 0);
}
template<typename MutexType>
requires Detail::IsIntraprocess<MutexType> && Detail::IsNonRecursive<MutexType>
void ConditionVariableBase<MutexType>::signal()
{
int result = pthread_cond_signal(to_impl(m_storage));
VERIFY(result == 0);
}
template<typename MutexType>
requires Detail::IsIntraprocess<MutexType> && Detail::IsNonRecursive<MutexType>
void ConditionVariableBase<MutexType>::broadcast()
{
int result = pthread_cond_broadcast(to_impl(m_storage));
VERIFY(result == 0);
}
template class SYNC_API ConditionVariableBase<Mutex>;
}

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2025, Ryszard Goc <ryszardgoc@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/Error.h>
#include <AK/Format.h>
#include <AK/Windows.h>
#include <LibSync/ConditionVariable.h>
#include <LibSync/Mutex.h>
namespace Sync {
namespace {
ALWAYS_INLINE PCONDITION_VARIABLE to_impl(void* ptr)
{
return reinterpret_cast<PCONDITION_VARIABLE>(ptr);
}
}
template<typename MutexType>
requires Detail::IsIntraprocess<MutexType> && Detail::IsNonRecursive<MutexType>
ConditionVariableBase<MutexType>::ConditionVariableBase(MutexType& to_wait_on)
: m_to_wait_on(to_wait_on)
{
InitializeConditionVariable(to_impl(m_storage));
}
template<typename MutexType>
requires Detail::IsIntraprocess<MutexType> && Detail::IsNonRecursive<MutexType>
ConditionVariableBase<MutexType>::~ConditionVariableBase() = default;
template<>
void ConditionVariableBase<Mutex>::wait()
{
BOOL result = SleepConditionVariableSRW(to_impl(m_storage), reinterpret_cast<PSRWLOCK>(m_to_wait_on.m_storage), INFINITE, 0);
if (!result) {
warnln("SleepConditionVariableSRW failed with: {}", Error::from_windows_error());
VERIFY_NOT_REACHED();
}
}
template<typename MutexType>
requires Detail::IsIntraprocess<MutexType> && Detail::IsNonRecursive<MutexType>
void ConditionVariableBase<MutexType>::signal()
{
WakeConditionVariable(to_impl(m_storage));
}
template<typename MutexType>
requires Detail::IsIntraprocess<MutexType> && Detail::IsNonRecursive<MutexType>
void ConditionVariableBase<MutexType>::broadcast()
{
WakeAllConditionVariable(to_impl(m_storage));
}
template class SYNC_API ConditionVariableBase<Mutex>;
}

View file

@ -1,30 +0,0 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
* Copyright (c) 2025, Ryszard Goc <ryszardgoc@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibSync/Mutex.h>
#include <pthread.h>
namespace Sync {
Mutex::Mutex()
: m_lock_count(0)
{
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&m_mutex, &attr);
pthread_mutexattr_destroy(&attr);
}
Mutex::~Mutex()
{
VERIFY(m_lock_count == 0);
pthread_mutex_destroy(&m_mutex);
}
}

View file

@ -1,6 +1,7 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
* Copyright (c) 2025, Ryszard Goc <ryszardgoc@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -8,36 +9,81 @@
#pragma once
#include <AK/Assertions.h>
#include <AK/Concepts.h>
#include <AK/Noncopyable.h>
#include <AK/Platform.h>
#include <AK/Types.h>
#include <LibSync/Export.h>
#include <pthread.h>
#include <LibSync/Policy.h>
#if !defined(AK_OS_WINDOWS)
# include <pthread.h>
#endif
namespace Sync {
class SYNC_API Mutex {
AK_MAKE_NONCOPYABLE(Mutex);
AK_MAKE_NONMOVABLE(Mutex);
friend class ConditionVariable;
template<typename RecursivePolicy, typename InterprocessPolicy>
class MutexBase;
template<typename T>
requires Detail::IsIntraprocess<T> && Detail::IsNonRecursive<T>
class ConditionVariableBase;
template<typename RecursivePolicy, typename InterprocessPolicy>
class SYNC_API MutexBase {
AK_MAKE_NONCOPYABLE(MutexBase);
AK_MAKE_NONMOVABLE(MutexBase);
template<typename T>
requires Detail::IsIntraprocess<T> && Detail::IsNonRecursive<T>
friend class ConditionVariableBase;
public:
Mutex();
~Mutex();
using InterprocessPolicyType = InterprocessPolicy;
using RecursivePolicyType = RecursivePolicy;
MutexBase();
~MutexBase();
bool try_lock();
void lock();
void unlock();
private:
pthread_mutex_t m_mutex;
unsigned m_lock_count { 0 };
static consteval u64 storage_size()
{
#ifdef AK_OS_WINDOWS
if constexpr (IsSame<InterprocessPolicy, PolicyInterprocess>) {
// Size of a handle
return sizeof(void*);
}
if constexpr (IsSame<RecursivePolicy, PolicyRecursive>) {
// The size of a critical section. This is guaranteed
return 40;
}
// SRWLock is just a void*
return sizeof(void*);
#else
return sizeof(pthread_mutex_t);
#endif
}
alignas(void*) unsigned char m_storage[storage_size()];
};
using Mutex = MutexBase<PolicyNonRecursive, PolicyIntraprocess>;
using RecursiveMutex = MutexBase<PolicyRecursive, PolicyIntraprocess>;
using IPCMutex = MutexBase<PolicyNonRecursive, PolicyInterprocess>;
using IPCRecursiveMutex = MutexBase<PolicyRecursive, PolicyInterprocess>;
template<typename MutexType>
class [[nodiscard]] SYNC_API MutexLocker {
AK_MAKE_NONCOPYABLE(MutexLocker);
AK_MAKE_NONMOVABLE(MutexLocker);
public:
ALWAYS_INLINE explicit MutexLocker(Mutex& mutex)
ALWAYS_INLINE explicit MutexLocker(MutexType& mutex)
: m_mutex(mutex)
{
lock();
@ -50,23 +96,10 @@ public:
ALWAYS_INLINE void lock() { m_mutex.lock(); }
private:
Mutex& m_mutex;
MutexType& m_mutex;
};
ALWAYS_INLINE void Mutex::lock()
{
pthread_mutex_lock(&m_mutex);
m_lock_count++;
}
ALWAYS_INLINE void Mutex::unlock()
{
VERIFY(m_lock_count > 0);
// FIXME: We need to protect the lock count with the mutex itself.
// This may be bad because we're not *technically* unlocked yet,
// but we're not handling any errors from pthread_mutex_unlock anyways.
m_lock_count--;
pthread_mutex_unlock(&m_mutex);
}
template<typename MutexType>
MutexLocker(MutexType&) -> MutexLocker<MutexType>;
}

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) 2018-2021, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2021, kleines Filmröllchen <malu.bertsch@gmail.com>
* Copyright (c) 2025, Ryszard Goc <ryszardgoc@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/Concepts.h>
#include <AK/Diagnostics.h>
#include <AK/Error.h>
#include <AK/Format.h>
#include <AK/Platform.h>
#include <LibSync/Export.h>
#include <LibSync/Mutex.h>
#include <LibSync/Policy.h>
#include <pthread.h>
namespace Sync {
namespace {
ALWAYS_INLINE pthread_mutex_t* to_impl(void* ptr)
{
return reinterpret_cast<pthread_mutex_t*>(ptr);
}
}
template<typename R, typename I>
MutexBase<R, I>::~MutexBase()
{
int result = pthread_mutex_destroy(to_impl(m_storage));
if (result != 0) {
warnln("pthread_mutex_destroy failed with: {}", Error::from_errno(result));
VERIFY_NOT_REACHED();
}
}
template<typename R, typename I>
bool MutexBase<R, I>::try_lock()
{
int result = pthread_mutex_trylock(to_impl(m_storage));
if (result == 0)
return true;
if (result == EBUSY)
return false;
warnln("pthread_mutex_lock failed with: {}", Error::from_errno(result));
VERIFY_NOT_REACHED();
}
template<typename R, typename I>
void MutexBase<R, I>::lock()
{
int result = pthread_mutex_lock(to_impl(m_storage));
if (result != 0) {
warnln("pthread_mutex_lock failed with: {}", Error::from_errno(result));
VERIFY_NOT_REACHED();
}
}
template<typename R, typename I>
void MutexBase<R, I>::unlock()
{
int result = pthread_mutex_unlock(to_impl(m_storage));
if (result != 0) {
warnln("pthread_mutex_unlock failed with: {}", Error::from_errno(result));
VERIFY_NOT_REACHED();
}
}
template<typename RecursivePolicy, typename InterprocessPolicy>
MutexBase<RecursivePolicy, InterprocessPolicy>::MutexBase()
{
static_assert(sizeof(m_storage) == sizeof(pthread_mutex_t));
pthread_mutex_t* mutex_ptr = new (m_storage) pthread_mutex_t;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
if constexpr (IsSame<RecursivePolicy, PolicyRecursive>) {
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
} else {
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
}
if constexpr (IsSame<InterprocessPolicy, PolicyInterprocess>)
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
int result = pthread_mutex_init(mutex_ptr, &attr);
if (result != 0) {
warnln("pthread_mutex_init failed with: {}", Error::from_errno(result));
VERIFY_NOT_REACHED();
}
pthread_mutexattr_destroy(&attr);
}
template class SYNC_API MutexBase<PolicyNonRecursive, PolicyIntraprocess>;
template class SYNC_API MutexBase<PolicyRecursive, PolicyIntraprocess>;
template class SYNC_API MutexBase<PolicyNonRecursive, PolicyInterprocess>;
template class SYNC_API MutexBase<PolicyRecursive, PolicyInterprocess>;
}

View file

@ -12,7 +12,7 @@
namespace Sync {
template<typename T>
template<typename T, typename MutexType = Mutex>
class MutexProtected {
AK_MAKE_NONCOPYABLE(MutexProtected);
AK_MAKE_NONMOVABLE(MutexProtected);
@ -48,10 +48,10 @@ public:
}
private:
[[nodiscard]] ALWAYS_INLINE MutexLocker lock() { return MutexLocker(m_lock); }
[[nodiscard]] ALWAYS_INLINE MutexLocker<MutexType> lock() { return MutexLocker(m_lock); }
T m_value;
Mutex m_lock {};
MutexType m_lock {};
};
}

View file

@ -0,0 +1,169 @@
/*
* Copyright (c) 2025, Ryszard Goc <ryszardgoc@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/Concepts.h>
#include <AK/Error.h>
#include <AK/Format.h>
#include <AK/Windows.h>
#include <LibSync/Export.h>
#include <LibSync/Mutex.h>
namespace Sync {
template<>
Mutex::MutexBase()
{
static_assert(sizeof(m_storage) == sizeof(SRWLOCK));
PSRWLOCK pSRW = new (m_storage) SRWLOCK;
InitializeSRWLock(pSRW);
}
template<>
Mutex::~MutexBase() = default;
template<>
void Mutex::lock()
{
AcquireSRWLockExclusive(reinterpret_cast<PSRWLOCK>(m_storage));
}
template<>
bool Mutex::try_lock()
{
return TryAcquireSRWLockExclusive(reinterpret_cast<PSRWLOCK>(m_storage));
}
template<>
void Mutex::unlock()
{
ReleaseSRWLockExclusive(reinterpret_cast<PSRWLOCK>(m_storage));
}
template<>
RecursiveMutex::MutexBase()
{
static_assert(sizeof(m_storage) == sizeof(CRITICAL_SECTION));
LPCRITICAL_SECTION pCS = new (m_storage) CRITICAL_SECTION;
// TODO: Optimize this for our use case
InitializeCriticalSectionAndSpinCount(pCS, 4000);
}
template<>
RecursiveMutex::~MutexBase()
{
DeleteCriticalSection(reinterpret_cast<LPCRITICAL_SECTION>(m_storage));
}
template<>
void RecursiveMutex::lock()
{
EnterCriticalSection(reinterpret_cast<LPCRITICAL_SECTION>(m_storage));
}
template<>
bool RecursiveMutex::try_lock()
{
return TryEnterCriticalSection(reinterpret_cast<LPCRITICAL_SECTION>(m_storage));
}
template<>
void RecursiveMutex::unlock()
{
LeaveCriticalSection(reinterpret_cast<LPCRITICAL_SECTION>(m_storage));
}
namespace {
void init_ipc_mutex(void* storage)
{
PHANDLE pHandle = new (storage) HANDLE;
SECURITY_ATTRIBUTES sa = { .nLength = sizeof(SECURITY_ATTRIBUTES), .lpSecurityDescriptor = nullptr, .bInheritHandle = TRUE };
// TODO: If we want to send these over IPC then some other methods have to be exposed to construct a MutexBase from
// the duplicated HANDLE. Otherwise it can use handle inheritance. We might want to only make it inheritable as an option.
HANDLE handle = CreateMutexW(&sa, FALSE, nullptr);
if (!handle) {
warnln("Failed to create mutex object with: {}", Error::from_windows_error());
VERIFY_NOT_REACHED();
}
*pHandle = handle;
}
void destroy_ipc_mutex(void* storage)
{
CloseHandle(*reinterpret_cast<PHANDLE>(storage));
}
void lock_ipc_mutex(void* storage)
{
DWORD result = WaitForSingleObject(*reinterpret_cast<PHANDLE>(storage), INFINITE);
if (result != WAIT_OBJECT_0) {
warnln("Failed to acquire mutex: {}", Error::from_windows_error(result));
VERIFY_NOT_REACHED();
}
}
bool try_lock_ipc_mutex(void* storage)
{
DWORD result = WaitForSingleObject(*reinterpret_cast<PHANDLE>(storage), 0);
if (result == WAIT_OBJECT_0) {
return true;
}
if (result == WAIT_TIMEOUT) {
return false;
}
if (result == WAIT_ABANDONED)
VERIFY_NOT_REACHED();
warnln("Failed trying to acquire mutex: {}", Error::from_windows_error(result));
VERIFY_NOT_REACHED();
}
void unlock_ipc_mutex(void* storage)
{
BOOL result = ReleaseMutex(*reinterpret_cast<PHANDLE>(storage));
if (!result) {
warnln("Failed to release mutex: {}", Error::from_windows_error());
VERIFY_NOT_REACHED();
}
}
}
template<>
IPCMutex::MutexBase()
{
static_assert(sizeof(m_storage) == sizeof(HANDLE));
init_ipc_mutex(m_storage);
}
template<>
IPCMutex::~MutexBase() { destroy_ipc_mutex(m_storage); }
template<>
void IPCMutex::lock() { lock_ipc_mutex(m_storage); }
template<>
bool IPCMutex::try_lock() { return try_lock_ipc_mutex(m_storage); }
template<>
void IPCMutex::unlock() { unlock_ipc_mutex(m_storage); }
template<>
IPCRecursiveMutex::MutexBase()
{
static_assert(sizeof(m_storage) == sizeof(HANDLE));
init_ipc_mutex(m_storage);
}
template<>
IPCRecursiveMutex::~MutexBase() { destroy_ipc_mutex(m_storage); }
template<>
void IPCRecursiveMutex::lock() { lock_ipc_mutex(m_storage); }
template<>
bool IPCRecursiveMutex::try_lock() { return try_lock_ipc_mutex(m_storage); }
template<>
void IPCRecursiveMutex::unlock() { unlock_ipc_mutex(m_storage); }
template class SYNC_API MutexBase<PolicyNonRecursive, PolicyIntraprocess>;
template class SYNC_API MutexBase<PolicyRecursive, PolicyIntraprocess>;
template class SYNC_API MutexBase<PolicyNonRecursive, PolicyInterprocess>;
template class SYNC_API MutexBase<PolicyRecursive, PolicyInterprocess>;
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2025, Ryszard Goc <ryszardgoc@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Concepts.h>
namespace Sync {
struct PolicyNonRecursive { };
struct PolicyRecursive { };
struct PolicyIntraprocess { };
struct PolicyInterprocess { };
namespace Detail {
template<typename T>
concept IsIntraprocess = requires {
requires IsSame<typename T::InterprocessPolicyType, PolicyIntraprocess>;
};
template<typename T>
concept IsNonRecursive = requires {
requires IsSame<typename T::RecursivePolicyType, PolicyNonRecursive>;
};
}
}