Files
op-pedal/src/inverting_mutex.hpp
T
2023-04-20 15:27:18 -04:00

194 lines
6.3 KiB
C++

// Copyright (c) 2022 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/// @brief A mutex that handles priority-inversion.
/// The thread priority of a thread that holds the mutex is boosted to the highest priority of waiting threads, thereby avoiding priority inversion.
///
#ifdef WIN32
static_assert("Fix me!");
/// Windows has no such concept. The strategy will probably be to boost the priority of worker threads from
/// Nice(2) to something realtime, or work out a non-locking alternative.
/// Currently, the principle problem is the LV2 Worker thread (Pipedal project), which runs at nice(2) priority, which may cause priority inversions on
/// the realtime thread. Of some concern would be threads of BalancedConvolution (ToobAmp project). Longer convolution sections run below the
/// priority of the ALSA threads on linux, while shorter sections run above the priority of the ALSA thread. The convolution threads run
/// at high priority anyway, so priority inversion probably isn't a problem, even on Windows.
#endif
#include <pthread.h>
#include <stdexcept>
#include <string.h>
#include <chrono>
#include <ratio>
#include <condition_variable>
class inverting_mutex
{
public:
using native_handle_type = pthread_mutex_t *;
inverting_mutex()
{
pthread_mutexattr_t mta;
int rc = pthread_mutexattr_init(&mta);
if (rc != 0)
throw_system_error(rc);
rc = pthread_mutexattr_setprotocol(&mta, PTHREAD_PRIO_INHERIT);
if (rc != 0)
throw_system_error(rc);
rc = pthread_mutex_init(&mutex, &mta);
if (rc != 0)
throw_system_error(rc);
}
~inverting_mutex()
{
pthread_mutex_destroy(&mutex);
}
inverting_mutex(const inverting_mutex &) = delete;
inverting_mutex &operator=(const inverting_mutex &) = delete;
void
lock()
{
int e = pthread_mutex_lock(&mutex);
// EINVAL, EAGAIN, EBUSY, EINVAL, EDEADLK(may)
if (e)
throw_system_error(e);
}
bool
try_lock() noexcept
{
// XXX EINVAL, EAGAIN, EBUSY
int rc = pthread_mutex_trylock(&mutex);
switch (rc)
{
case 0:
return true;
case EBUSY:
return false;
default:
throw_system_error(rc);
return false;
}
}
template <class Rep, class Period>
bool
try_lock_for(const std::chrono::duration<Rep, Period> &rtime)
{
using clock = std::chrono::steady_clock;
auto rt = std::chrono::duration_cast<clock::duration>(rtime);
if (std::ratio_greater<clock::period, Period>())
++rt;
auto t = clock::now() + rt;
return try_lock_until(t);
}
template <class Clock, class Duration>
bool
try_lock_until(const std::chrono::time_point<Clock, Duration> &atime)
{
auto s = std::chrono::time_point_cast<std::chrono::seconds>(atime);
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(atime - s);
timespec ts = {
static_cast<std::time_t>(s.time_since_epoch().count()),
static_cast<long>(ns.count())};
return !pthread_mutex_timedlock(&mutex, &ts);
}
// template <class Rep, class Period>
// std::cv_status cond_wait_for(std::condition_variable &cond, const std::chrono::duration<Rep, Period> &rtime)
// {
// using clock = std::chrono::steady_clock;
// auto rt = std::chrono::duration_cast<clock::duration>(rtime);
// if (std::ratio_greater<clock::period, Period>())
// ++rt;
// auto t = clock::now() + rt;
// return cont_wait_until(cond,t);
// }
// template <class Clock, class Duration>
// std::cv_status cont_wait_until(std::condition_variable &cond,const std::chrono::time_point<Clock, Duration> &atime)
// {
// auto s = std::chrono::time_point_cast<std::chrono::seconds>(atime);
// auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(atime - s);
// auto now = Clock::now();
// auto sNow = std::chrono::time_point_cast<std::chrono::seconds>(now);
// auto nsNow = std::chrono::duration_cast<std::chrono::nanoseconds>(now - s);
// timespec tsNow = {
// static_cast<std::time_t>(sNow.time_since_epoch().count()),
// static_cast<long>(nsNow.count())};
// (void)tsNow;
// timespec ts = {
// static_cast<std::time_t>(s.time_since_epoch().count()),
// static_cast<long>(ns.count())};
// int rc = pthread_cond_timedwait(cond.native_handle(),this->native_handle(),&ts);
// switch (rc)
// {
// case 0:
// return std::cv_status::no_timeout;
// case ETIMEDOUT:
// return std::cv_status::timeout;
// default:
// throw_system_error(rc);
// return std::cv_status::timeout;
// }
// }
void
unlock()
{
// XXX EINVAL, EAGAIN, EBUSY
int rc = pthread_mutex_unlock(&mutex);
if (rc != 0)
{
throw_system_error(rc);
}
}
native_handle_type
native_handle() noexcept
{
return &mutex;
}
private:
void throw_system_error(int e)
{
throw std::logic_error(strerror(e));
}
pthread_mutex_t mutex;
};