/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License Version 2.0 with LLVM Exceptions * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * https://llvm.org/LICENSE.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include namespace unifex { class inplace_stop_source; class inplace_stop_token; template class inplace_stop_callback; class inplace_stop_callback_base { public: void execute() noexcept { this->execute_(this); } #ifndef NDEBUG char const* type_name() const noexcept { return type_name_; } #endif protected: using execute_fn = void(inplace_stop_callback_base* cb) noexcept; #ifndef NDEBUG explicit inplace_stop_callback_base(inplace_stop_source* source, execute_fn* execute, char const* type_name) noexcept : source_(source), execute_(execute), type_name_(type_name) {} #else explicit inplace_stop_callback_base(inplace_stop_source* source, execute_fn* execute) noexcept : source_(source), execute_(execute) {} #endif void register_callback() noexcept; friend inplace_stop_source; inplace_stop_source* source_; execute_fn* execute_; inplace_stop_callback_base* next_ = nullptr; inplace_stop_callback_base** prevPtr_ = nullptr; bool* removedDuringCallback_ = nullptr; std::atomic callbackCompleted_{false}; #ifndef NDEBUG char const* type_name_ = nullptr; #endif }; class inplace_stop_source { public: inplace_stop_source() noexcept = default; ~inplace_stop_source(); inplace_stop_source(const inplace_stop_source&) = delete; inplace_stop_source(inplace_stop_source&&) = delete; inplace_stop_source& operator=(inplace_stop_source&&) = delete; inplace_stop_source& operator=(const inplace_stop_source&) = delete; bool request_stop() noexcept; inplace_stop_token get_token() noexcept; bool stop_requested() const noexcept { return (state_.load(std::memory_order_acquire) & stop_requested_flag) != 0; } private: friend inplace_stop_token; friend inplace_stop_callback_base; template friend class inplace_stop_callback; std::uint8_t lock() noexcept; void unlock(std::uint8_t oldState) noexcept; bool try_lock_unless_stop_requested(bool setStopRequested) noexcept; bool try_add_callback(inplace_stop_callback_base* callback) noexcept; void remove_callback(inplace_stop_callback_base* callback) noexcept; static constexpr std::uint8_t stop_requested_flag = 1; static constexpr std::uint8_t locked_flag = 2; std::atomic state_{0}; inplace_stop_callback_base* callbacks_ = nullptr; std::thread::id notifyingThreadId_; }; class inplace_stop_token { public: template using callback_type = inplace_stop_callback; inplace_stop_token() noexcept : source_(nullptr) {} inplace_stop_token(const inplace_stop_token& other) noexcept = default; inplace_stop_token(inplace_stop_token&& other) noexcept : source_(std::exchange(other.source_, {})) {} inplace_stop_token& operator=(const inplace_stop_token& other) noexcept = default; inplace_stop_token& operator=(inplace_stop_token&& other) noexcept { source_ = std::exchange(other.source_, nullptr); return *this; } bool stop_requested() const noexcept { return source_ != nullptr && source_->stop_requested(); } bool stop_possible() const noexcept { return source_ != nullptr; } void swap(inplace_stop_token& other) noexcept { std::swap(source_, other.source_); } friend bool operator==(const inplace_stop_token& a, const inplace_stop_token& b) noexcept { return a.source_ == b.source_; } friend bool operator!=(const inplace_stop_token& a, const inplace_stop_token& b) noexcept { return !(a == b); } private: friend inplace_stop_source; template friend class inplace_stop_callback; explicit inplace_stop_token(inplace_stop_source* source) noexcept : source_(source) {} inplace_stop_source* source_; }; inline inplace_stop_token inplace_stop_source::get_token() noexcept { return inplace_stop_token{this}; } template class inplace_stop_callback final : private inplace_stop_callback_base { public: template(typename T = F) (requires convertible_to) explicit inplace_stop_callback(inplace_stop_token token, T&& func) noexcept( std::is_nothrow_constructible_v) #ifndef NDEBUG : inplace_stop_callback_base(token.source_, &inplace_stop_callback::execute_impl, unifex::type_id().name()) #else : inplace_stop_callback_base(token.source_, &inplace_stop_callback::execute_impl) #endif , func_((T&&) func) { this->register_callback(); } ~inplace_stop_callback() { if (source_ != nullptr) { source_->remove_callback(this); } } private: static void execute_impl(inplace_stop_callback_base* cb) noexcept { auto& self = *static_cast(cb); self.func_(); } UNIFEX_NO_UNIQUE_ADDRESS F func_; }; inline void inplace_stop_callback_base::register_callback() noexcept { if (source_ != nullptr) { if (!source_->try_add_callback(this)) { source_ = nullptr; // Callback not registered because stop_requested() was true. // Execute inline here. execute(); } } } namespace detail { struct forward_stop_request_to_inplace_stop_source { inplace_stop_source& source; forward_stop_request_to_inplace_stop_source(inplace_stop_source& s) noexcept : source(s) {} void operator()() const noexcept { source.request_stop(); } }; } // namespace detail // Helper class for adapting an incoming StopToken type to an // inplace_stop_token. template class inplace_stop_token_adapter { public: inplace_stop_token subscribe(StopToken stoken) noexcept { const bool stopPossible = stoken.stop_possible(); callback_.construct(std::move(stoken), source_); return stopPossible ? source_.get_token() : inplace_stop_token{}; } void unsubscribe() noexcept { callback_.destruct(); } private: using stop_callback = typename StopToken::template callback_type; inplace_stop_source source_; UNIFEX_NO_UNIQUE_ADDRESS manual_lifetime callback_; }; template<> class inplace_stop_token_adapter { public: inplace_stop_token subscribe(inplace_stop_token stoken) noexcept { return stoken; } void unsubscribe() noexcept {} }; template class inplace_stop_token_adapter>> { public: inplace_stop_token subscribe(StopToken) noexcept { return inplace_stop_token{}; } void unsubscribe() noexcept {} }; /// \cond namespace detail { template struct inplace_stop_token_adapter_subscription { inplace_stop_token subscribe(StopToken stoken) noexcept { isSubscribed_ = true; return stopTokenAdapter_.subscribe(std::move(stoken)); } void unsubscribe() noexcept { if (isSubscribed_) { isSubscribed_ = false; stopTokenAdapter_.unsubscribe(); } } ~inplace_stop_token_adapter_subscription() { unsubscribe(); } private: bool isSubscribed_ = false; UNIFEX_NO_UNIQUE_ADDRESS inplace_stop_token_adapter stopTokenAdapter_{}; }; } // namespace detail /// \endcond } // namespace unifex #include