// Copyright (c) Robin E.R. 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 #include #include #include #include #include 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 bool try_lock_for(const std::chrono::duration &rtime) { using clock = std::chrono::steady_clock; auto rt = std::chrono::duration_cast(rtime); if (std::ratio_greater()) ++rt; auto t = clock::now() + rt; return try_lock_until(t); } template bool try_lock_until(const std::chrono::time_point &atime) { auto s = std::chrono::time_point_cast(atime); auto ns = std::chrono::duration_cast(atime - s); timespec ts = { static_cast(s.time_since_epoch().count()), static_cast(ns.count())}; return !pthread_mutex_timedlock(&mutex, &ts); } // template // std::cv_status cond_wait_for(std::condition_variable &cond, const std::chrono::duration &rtime) // { // using clock = std::chrono::steady_clock; // auto rt = std::chrono::duration_cast(rtime); // if (std::ratio_greater()) // ++rt; // auto t = clock::now() + rt; // return cont_wait_until(cond,t); // } // template // std::cv_status cont_wait_until(std::condition_variable &cond,const std::chrono::time_point &atime) // { // auto s = std::chrono::time_point_cast(atime); // auto ns = std::chrono::duration_cast(atime - s); // auto now = Clock::now(); // auto sNow = std::chrono::time_point_cast(now); // auto nsNow = std::chrono::duration_cast(now - s); // timespec tsNow = { // static_cast(sNow.time_since_epoch().count()), // static_cast(nsNow.count())}; // (void)tsNow; // timespec ts = { // static_cast(s.time_since_epoch().count()), // static_cast(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; };