You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
4.8 KiB
C++
170 lines
4.8 KiB
C++
/*
|
|
* posix_semaphore.hpp
|
|
*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2019 yohhoy
|
|
*
|
|
* 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.
|
|
*/
|
|
#ifndef POSIX_SEMAPHORE_HPP_
|
|
#define POSIX_SEMAPHORE_HPP_
|
|
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <chrono>
|
|
#include <system_error>
|
|
#include <type_traits>
|
|
// POSIX semaphore
|
|
#include <limits.h> // SEM_VALUE_MAX
|
|
#include <semaphore.h>
|
|
#include <time.h> // timespec struct
|
|
|
|
|
|
namespace yamc {
|
|
|
|
/*
|
|
* Semaphores in C++20 Standard Library for POSIX-compatible platform
|
|
*
|
|
* - yamc::posix::counting_semaphore<least_max_value>
|
|
* - yamc::posix::binary_semaphore
|
|
*
|
|
* This implementation use POSIX unnamed semaphore.
|
|
* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/semaphore.h.html
|
|
*/
|
|
namespace posix {
|
|
|
|
namespace detail {
|
|
|
|
template<typename Clock, typename Duration>
|
|
inline
|
|
std::chrono::time_point<std::chrono::system_clock, Duration>
|
|
to_system_timepoint(const std::chrono::time_point<Clock, Duration>& tp)
|
|
{
|
|
return std::chrono::system_clock::now() + (tp - Clock::now());
|
|
}
|
|
|
|
template<typename Duration>
|
|
inline
|
|
std::chrono::time_point<std::chrono::system_clock, Duration>
|
|
to_system_timepoint(const std::chrono::time_point<std::chrono::system_clock, Duration>& tp)
|
|
{
|
|
return tp;
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
|
|
template <std::ptrdiff_t least_max_value = SEM_VALUE_MAX>
|
|
class counting_semaphore {
|
|
::sem_t sem_;
|
|
|
|
static void throw_errno(const char* what_arg)
|
|
{
|
|
#ifndef __ANDROID__
|
|
throw std::system_error(std::error_code(errno, std::generic_category()), what_arg);
|
|
#endif
|
|
}
|
|
|
|
bool do_try_acquirewait(const std::chrono::system_clock::time_point& tp)
|
|
{
|
|
using namespace std::chrono;
|
|
// convert C++ system_clock to POSIX struct timespec
|
|
struct ::timespec abs_timeout;
|
|
abs_timeout.tv_sec = system_clock::to_time_t(tp);
|
|
abs_timeout.tv_nsec = (long)(duration_cast<nanoseconds>(tp.time_since_epoch()).count() % 1000000000);
|
|
|
|
errno = 0;
|
|
if (::sem_timedwait(&sem_, &abs_timeout) == 0) {
|
|
return true;
|
|
} else if (errno != ETIMEDOUT) {
|
|
throw_errno("sem_timedwait");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public:
|
|
static constexpr std::ptrdiff_t max() noexcept
|
|
{
|
|
static_assert(0 <= least_max_value, "least_max_value shall be non-negative");
|
|
static_assert(least_max_value <= SEM_VALUE_MAX, "least_max_value shall be less than or equal to SEM_VALUE_MAX");
|
|
return least_max_value;
|
|
}
|
|
|
|
/*constexpr*/ explicit counting_semaphore(std::ptrdiff_t desired)
|
|
{
|
|
assert(0 <= desired && desired <= max());
|
|
::sem_init(&sem_, 0, (unsigned int)desired);
|
|
// counting_semaphore constructor throws nothing.
|
|
}
|
|
|
|
~counting_semaphore()
|
|
{
|
|
::sem_destroy(&sem_);
|
|
}
|
|
|
|
counting_semaphore(const counting_semaphore&) = delete;
|
|
counting_semaphore& operator=(const counting_semaphore&) = delete;
|
|
|
|
void release(std::ptrdiff_t update = 1)
|
|
{
|
|
errno = 0;
|
|
while (0 < update--) {
|
|
if (::sem_post(&sem_) != 0) {
|
|
throw_errno("sem_post");
|
|
}
|
|
}
|
|
}
|
|
|
|
void acquire()
|
|
{
|
|
errno = 0;
|
|
if (::sem_wait(&sem_) != 0) {
|
|
throw_errno("sem_wait");
|
|
}
|
|
}
|
|
|
|
bool try_acquire() noexcept
|
|
{
|
|
return (::sem_trywait(&sem_) == 0);
|
|
}
|
|
|
|
template<class Rep, class Period>
|
|
bool try_acquire_for(const std::chrono::duration<Rep, Period>& rel_time)
|
|
{
|
|
// C++ Standard says '_for'-suffixed timeout function shall use steady clock,
|
|
// but we use system_clock to convert from time_point to legacy time_t.
|
|
const auto tp = std::chrono::system_clock::now() + rel_time;
|
|
return do_try_acquirewait(tp);
|
|
}
|
|
|
|
template<class Clock, class Duration>
|
|
bool try_acquire_until(const std::chrono::time_point<Clock, Duration>& abs_time)
|
|
{
|
|
return do_try_acquirewait(detail::to_system_timepoint(abs_time));
|
|
}
|
|
};
|
|
|
|
using binary_semaphore = counting_semaphore<1>;
|
|
|
|
} // namespace posix
|
|
} // namespace yamc
|
|
|
|
#endif
|