/** * @file src/thread_safe.h * @brief Declarations for thread-safe data structures. */ #pragma once // standard includes #include #include #include #include #include #include #include #include // local includes #include "utility.h" namespace safe { namespace detail { // Advisory state for per-frame polls. Payloads and all state writes remain // protected by the owning mailbox's mutex, but observing an empty/running // mailbox must not block capture behind a descheduled producer or consumer. // Publish only after changing the payload; readers must still lock before // accessing it and cannot treat peek() as a reservation. class poll_state_t { public: bool running() const { return _state.load(std::memory_order_acquire) != state_e::stopped; } bool peek() const { return _state.load(std::memory_order_acquire) == state_e::ready; } void set_ready(bool ready) { _state.store(ready ? state_e::ready : state_e::empty, std::memory_order_release); } void stop() { _state.store(state_e::stopped, std::memory_order_release); } private: enum class state_e : std::uint8_t { stopped, empty, ready }; static_assert(std::atomic::is_always_lock_free); std::atomic _state {state_e::empty}; }; } // namespace detail template class event_t { public: using status_t = util::optional_t; template void raise(Args &&...args) { std::lock_guard lg {_lock}; if (!_poll_state.running()) { return; } if constexpr (std::is_same_v, status_t>) { _status = std::make_optional(std::forward(args)...); } else { _status = status_t {std::forward(args)...}; } _poll_state.set_ready((bool) _status); _generation.fetch_add(1, std::memory_order_release); _cv.notify_all(); } // pop and view should not be used interchangeably status_t pop() { std::unique_lock ul {_lock}; if (!_poll_state.running()) { return util::false_v; } while (!_status) { _cv.wait(ul); if (!_poll_state.running()) { return util::false_v; } } auto val = std::move(_status); _status = util::false_v; _poll_state.set_ready(false); return val; } // pop and view should not be used interchangeably template status_t pop(std::chrono::duration delay) { std::unique_lock ul {_lock, std::defer_lock}; if (delay <= decltype(delay)::zero()) { if (!peek() || !ul.try_lock()) { return util::false_v; } } else { ul.lock(); } // Another consumer may have won after peek(). Never enter a timed wait // for a nonpositive poll, even when it races with that consumer. if (!_poll_state.running()) { return util::false_v; } if (!_status) { if (delay <= decltype(delay)::zero() || !_cv.wait_for(ul, delay, [this] { return (bool) _status || !_poll_state.running(); }) || !_poll_state.running()) { return util::false_v; } } auto val = std::move(_status); _status = util::false_v; _poll_state.set_ready(false); return val; } // pop and view should not be used interchangeably status_t view() { std::unique_lock ul {_lock}; if (!_poll_state.running()) { return util::false_v; } while (!_status) { _cv.wait(ul); if (!_poll_state.running()) { return util::false_v; } } return _status; } // pop and view should not be used interchangeably template status_t view(std::chrono::duration delay) { std::unique_lock ul {_lock, std::defer_lock}; if (delay <= decltype(delay)::zero()) { if (!peek() || !ul.try_lock()) { return util::false_v; } } else { ul.lock(); } if (!_poll_state.running()) { return util::false_v; } while (!_status) { if (delay <= decltype(delay)::zero() || _cv.wait_for(ul, delay) == std::cv_status::timeout || !_poll_state.running()) { return util::false_v; } } return _status; } bool peek() { return _poll_state.peek(); } [[nodiscard]] std::uint64_t generation() const { return _generation.load(std::memory_order_acquire); } status_t view_if_newer(std::uint64_t &observed_generation) { if (generation() == observed_generation) { return util::false_v; } std::lock_guard lg {_lock}; const auto current_generation = generation(); if (!_poll_state.running() || !_status || observed_generation == current_generation) { return util::false_v; } observed_generation = current_generation; return _status; } void stop() { std::lock_guard lg {_lock}; _poll_state.stop(); _cv.notify_all(); } void reset() { std::lock_guard lg {_lock}; _status = util::false_v; _poll_state.set_ready(false); } [[nodiscard]] bool running() const { return _poll_state.running(); } private: detail::poll_state_t _poll_state; status_t _status {util::false_v}; std::atomic _generation {}; std::condition_variable _cv; std::mutex _lock; }; template class alarm_raw_t { public: using status_t = util::optional_t; void ring(const status_t &status) { std::lock_guard lg(_lock); _status = status; _rang = true; _cv.notify_one(); } void ring(status_t &&status) { std::lock_guard lg(_lock); _status = std::move(status); _rang = true; _cv.notify_one(); } template auto wait_for(const std::chrono::duration &rel_time) { std::unique_lock ul(_lock); return _cv.wait_for(ul, rel_time, [this]() { return _rang; }); } template auto wait_for(const std::chrono::duration &rel_time, Pred &&pred) { std::unique_lock ul(_lock); return _cv.wait_for(ul, rel_time, [this, &pred]() { return _rang || pred(); }); } template auto wait_until(const std::chrono::duration &rel_time) { std::unique_lock ul(_lock); return _cv.wait_until(ul, rel_time, [this]() { return _rang; }); } template auto wait_until(const std::chrono::duration &rel_time, Pred &&pred) { std::unique_lock ul(_lock); return _cv.wait_until(ul, rel_time, [this, &pred]() { return _rang || pred(); }); } auto wait() { std::unique_lock ul(_lock); _cv.wait(ul, [this]() { return _rang; }); } template auto wait(Pred &&pred) { std::unique_lock ul(_lock); _cv.wait(ul, [this, &pred]() { return _rang || pred(); }); } const status_t &status() const { return _status; } status_t &status() { return _status; } void reset() { _status = status_t {}; _rang = false; } private: std::mutex _lock; std::condition_variable _cv; status_t _status {util::false_v}; bool _rang {false}; }; template using alarm_t = std::shared_ptr>; template alarm_t make_alarm() { return std::make_shared>(); } template class queue_t { public: using status_t = util::optional_t; queue_t(std::uint32_t max_elements = 32): _max_elements {max_elements} { } /// Replace a matching pending item in place, preserving other producers' /// order. For independent snapshots whose older values may be discarded. template bool raise_latest(T value, Predicate matches) { std::lock_guard ul {_lock}; if (!_poll_state.running()) { return false; } for (auto &pending : _queue) { if (matches(pending)) { pending = std::move(value); return true; } } if (_queue.size() >= _max_elements) { return false; } _queue.emplace_back(std::move(value)); _poll_state.set_ready(true); _cv.notify_all(); return true; } template void raise(Args &&...args) { std::lock_guard ul {_lock}; if (!_poll_state.running()) { return; } if (_queue.size() == _max_elements) { _queue.clear(); _poll_state.set_ready(false); } _queue.emplace_back(std::forward(args)...); _poll_state.set_ready(true); _cv.notify_all(); } template bool try_raise(Args &&...args) { std::lock_guard ul {_lock}; if (!_poll_state.running() || _queue.size() >= _max_elements) { return false; } _queue.emplace_back(std::forward(args)...); _poll_state.set_ready(true); _cv.notify_all(); return true; } bool peek() { return _poll_state.peek(); } template bool wait_for_data(std::chrono::duration delay) { if (delay <= decltype(delay)::zero()) { return peek(); } std::unique_lock ul {_lock}; return _cv.wait_for(ul, delay, [this] { return !_queue.empty() || !_poll_state.running(); }) && _poll_state.running() && !_queue.empty(); } template status_t pop(std::chrono::duration delay) { std::unique_lock ul {_lock, std::defer_lock}; if (delay <= decltype(delay)::zero()) { if (!peek() || !ul.try_lock()) { return util::false_v; } } else { ul.lock(); } if (!_poll_state.running()) { return util::false_v; } while (_queue.empty()) { // A zero-duration condition-variable wait can still yield to the // scheduler on Windows. Keep nonblocking capture polls out of it. if (delay <= decltype(delay)::zero()) { return util::false_v; } if (_cv.wait_for(ul, delay) == std::cv_status::timeout || !_poll_state.running()) { return util::false_v; } } auto val = std::move(_queue.front()); _queue.erase(std::begin(_queue)); _poll_state.set_ready(!_queue.empty()); return val; } status_t pop() { std::unique_lock ul {_lock}; if (!_poll_state.running()) { return util::false_v; } while (_queue.empty()) { _cv.wait(ul); if (!_poll_state.running()) { return util::false_v; } } auto val = std::move(_queue.front()); _queue.erase(std::begin(_queue)); _poll_state.set_ready(!_queue.empty()); return val; } std::vector &unsafe() { return _queue; } void stop() { std::lock_guard lg {_lock}; _poll_state.stop(); _cv.notify_all(); } void reset() { std::lock_guard lg {_lock}; _queue.clear(); _poll_state.set_ready(false); } [[nodiscard]] bool running() const { return _poll_state.running(); } private: detail::poll_state_t _poll_state; std::uint32_t _max_elements; std::mutex _lock; std::condition_variable _cv; std::vector _queue; }; template class shared_t { public: using element_type = T; using construct_f = std::function; using destruct_f = std::function; struct ptr_t { shared_t *owner; ptr_t(): owner {nullptr} { } explicit ptr_t(shared_t *owner): owner {owner} { } ptr_t(ptr_t &&ptr) noexcept: owner {ptr.owner} { ptr.owner = nullptr; } ptr_t(const ptr_t &ptr) noexcept: owner {ptr.owner} { if (!owner) { return; } auto tmp = ptr.owner->ref(); tmp.owner = nullptr; } ptr_t &operator=(const ptr_t &ptr) noexcept { if (!ptr.owner) { release(); return *this; } return *this = std::move(*ptr.owner->ref()); } ptr_t &operator=(ptr_t &&ptr) noexcept { if (owner) { release(); } std::swap(owner, ptr.owner); return *this; } ~ptr_t() { if (owner) { release(); } } operator bool() const { return owner != nullptr; } void release() { std::lock_guard lg {owner->_lock}; if (!--owner->_count) { owner->_destruct(*get()); (*this)->~element_type(); } owner = nullptr; } element_type *get() const { return reinterpret_cast(owner->_object_buf.data()); } element_type *operator->() { return reinterpret_cast(owner->_object_buf.data()); } }; template shared_t(FC &&fc, FD &&fd): _construct {std::forward(fc)}, _destruct {std::forward(fd)} { } [[nodiscard]] ptr_t ref() { std::lock_guard lg {_lock}; if (!_count) { new (_object_buf.data()) element_type; if (_construct(*reinterpret_cast(_object_buf.data()))) { return ptr_t {nullptr}; } } ++_count; return ptr_t {this}; } private: construct_f _construct; destruct_f _destruct; std::array _object_buf; std::uint32_t _count; std::mutex _lock; }; template auto make_shared(F_Construct &&fc, F_Destruct &&fd) { return shared_t { std::forward(fc), std::forward(fd) }; } using signal_t = event_t; class mail_raw_t; using mail_t = std::shared_ptr; void cleanup(mail_raw_t *); template class post_t: public T { public: template post_t(mail_t mail, Args &&...args): T(std::forward(args)...), mail {std::move(mail)} { } mail_t mail; ~post_t() { cleanup(mail.get()); } }; template inline auto lock(const std::weak_ptr &wp) { return std::reinterpret_pointer_cast(wp.lock()); } class mail_raw_t: public std::enable_shared_from_this { public: template using event_t = std::shared_ptr>>; template using queue_t = std::shared_ptr>>; template event_t event(const std::string_view &id) { std::lock_guard lg {mutex}; auto it = id_to_post.find(id); if (it != std::end(id_to_post)) { if (auto post = lock>(it->second)) { return post; } id_to_post.erase(it); } auto post = std::make_shared::element_type>(shared_from_this()); id_to_post.emplace(std::pair> {std::string {id}, post}); return post; } template queue_t queue(const std::string_view &id) { std::lock_guard lg {mutex}; auto it = id_to_post.find(id); if (it != std::end(id_to_post)) { if (auto post = lock>(it->second)) { return post; } id_to_post.erase(it); } auto post = std::make_shared::element_type>(shared_from_this(), 32); id_to_post.emplace(std::pair> {std::string {id}, post}); return post; } void cleanup() { std::lock_guard lg {mutex}; for (auto it = std::begin(id_to_post); it != std::end(id_to_post); ++it) { auto &weak = it->second; if (weak.expired()) { id_to_post.erase(it); return; } } } std::mutex mutex; std::map, std::less<>> id_to_post; }; inline void cleanup(mail_raw_t *mail) { mail->cleanup(); } } // namespace safe