Files
rippled/include/xrpl/server/detail/io_list.h
Bart 34ef577604 refactor: Replace include guards by '#pragma once' (#6322)
This change replaces all include guards in the `src/` and `include/` directories by `#pragma once`.
2026-02-04 09:50:21 -05:00

245 lines
4.8 KiB
C++

#pragma once
#include <boost/container/flat_map.hpp>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <type_traits>
#include <utility>
namespace xrpl {
/** Manages a set of objects performing asynchronous I/O. */
class io_list final
{
public:
class work
{
template <class = void>
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 <class = void>
void
destroy();
std::mutex m_;
std::size_t n_ = 0;
bool closed_ = false;
std::condition_variable cv_;
boost::container::flat_map<work*, std::weak_ptr<work>> map_;
std::function<void(void)> 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<work, T> == 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 <class T, class... Args>
std::shared_ptr<T>
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 <class Finisher>
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 <class = void>
void
join();
};
//------------------------------------------------------------------------------
template <class>
void
io_list::work::destroy()
{
if (!ios_)
return;
std::function<void(void)> 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 <class>
void
io_list::destroy()
{
close();
join();
}
template <class T, class... Args>
std::shared_ptr<T>
io_list::emplace(Args&&... args)
{
static_assert(std::is_base_of<work, T>::value, "T must derive from io_list::work");
if (closed_)
return nullptr;
auto sp = std::make_shared<T>(std::forward<Args>(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 <class Finisher>
void
io_list::close(Finisher&& f)
{
std::unique_lock<std::mutex> lock(m_);
if (closed_)
return;
closed_ = true;
auto map = std::move(map_);
if (!map.empty())
{
f_ = std::forward<Finisher>(f);
lock.unlock();
for (auto const& p : map)
if (auto sp = p.second.lock())
sp->close();
}
else
{
lock.unlock();
f();
}
}
template <class>
void
io_list::join()
{
std::unique_lock<std::mutex> lock(m_);
cv_.wait(lock, [&] { return closed_ && n_ == 0; });
}
} // namespace xrpl