#pragma once #include #include #include #include #include #include #include namespace xrpl { /** Manages a set of objects performing asynchronous I/O. */ class io_list final { public: class work { template void destroy(); friend class io_list; io_list* ios_ = nullptr; public: virtual ~work() { destroy(); } /** Return the io_list associated with the work. Requirements: The call to io_list::emplace to create the work has already returned. */ io_list& ios() { return *ios_; } virtual void close() = 0; }; private: template void destroy(); std::mutex m_; std::size_t n_ = 0; bool closed_ = false; std::condition_variable cv_; boost::container::flat_map> map_; std::function f_; public: io_list() = default; /** Destroy the list. Effects: Closes the io_list if it was not previously closed. No finisher is invoked in this case. Blocks until all work is destroyed. */ ~io_list() { destroy(); } /** Return `true` if the list is closed. Thread Safety: Undefined result if called concurrently with close(). */ bool closed() const { return closed_; } /** Create associated work if not closed. Requirements: `std::is_base_of_v == true` Thread Safety: May be called concurrently. Effects: Atomically creates, inserts, and returns new work T, or returns nullptr if the io_list is closed, If the call succeeds and returns a new object, it is guaranteed that a subsequent call to close will invoke work::close on the object. */ template std::shared_ptr emplace(Args&&... args); /** Cancel active I/O. Thread Safety: May not be called concurrently. Effects: Associated work is closed. Finisher if provided, will be called when all associated work is destroyed. The finisher may be called from a foreign thread, or within the call to this function. Only the first call to close will set the finisher. No effect after the first call. */ template void close(Finisher&& f); void close() { close([] {}); } /** Block until the io_list stops. Effects: The caller is blocked until the io_list is closed and all associated work is destroyed. Thread safety: May be called concurrently. Preconditions: No call to io_context::run on any io_context used by work objects associated with this io_list exists in the caller's call stack. */ template void join(); }; //------------------------------------------------------------------------------ template void io_list::work::destroy() { if (!ios_) return; std::function f; { std::lock_guard lock(ios_->m_); ios_->map_.erase(this); if (--ios_->n_ == 0 && ios_->closed_) { std::swap(f, ios_->f_); ios_->cv_.notify_all(); } } if (f) f(); } template void io_list::destroy() { close(); join(); } template std::shared_ptr io_list::emplace(Args&&... args) { static_assert(std::is_base_of::value, "T must derive from io_list::work"); if (closed_) return nullptr; auto sp = std::make_shared(std::forward(args)...); decltype(sp) dead; std::lock_guard lock(m_); if (!closed_) { ++n_; sp->work::ios_ = this; map_.emplace(sp.get(), sp); } else { std::swap(sp, dead); } return sp; } template void io_list::close(Finisher&& f) { std::unique_lock lock(m_); if (closed_) return; closed_ = true; auto map = std::move(map_); if (!map.empty()) { f_ = std::forward(f); lock.unlock(); for (auto const& p : map) if (auto sp = p.second.lock()) sp->close(); } else { lock.unlock(); f(); } } template void io_list::join() { std::unique_lock lock(m_); cv_.wait(lock, [&] { return closed_ && n_ == 0; }); } } // namespace xrpl