diff --git a/include/asio/experimental/diagnostic_executor.hpp b/include/asio/experimental/diagnostic_executor.hpp new file mode 100644 index 0000000000..2caa60affa --- /dev/null +++ b/include/asio/experimental/diagnostic_executor.hpp @@ -0,0 +1,357 @@ +// +// experimental/diagnostic_executor.hpp +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// Copyright (c) 2026 Pinwhell +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef ASIO_EXPERIMENTAL_DIAGNOSTIC_EXECUTOR_HPP +#define ASIO_EXPERIMENTAL_DIAGNOSTIC_EXECUTOR_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1200) +# pragma once +#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "asio/detail/config.hpp" +#include "asio/detail/type_traits.hpp" +#include "asio/execution/blocking.hpp" +#include "asio/execution/executor.hpp" +#include "asio/execution_context.hpp" +#include "asio/query.hpp" +#include "asio/require.hpp" +#include "asio/prefer.hpp" +#include "asio/dispatch.hpp" +#include "asio/post.hpp" +#include "asio/defer.hpp" +#include "asio/bind_executor.hpp" + +#include "asio/detail/push_options.hpp" + +namespace asio { +namespace experimental { + +/// Default diagnostic policy that performs no action. +/** + * This policy provides a no-op implementation of the diagnostic hooks, ensuring + * that the diagnostic executor has zero overhead when no diagnostics are + * required. + */ +struct null_diagnostic_policy +{ + /// Hook called before work is submitted to the underlying executor. + template + static void on_submit(const Label&) noexcept + { + } +}; + +/// An executor adapter that transparently observes work submission. +/** + * The diagnostic_executor class template is used to wrap another executor and + * provide a point of observation for work submission. It forwards all + * operations to the underlying executor while invoking a diagnostic policy + * hook whenever work is submitted via @c execute, @c dispatch, @c post, or + * @c defer. + */ +template +class diagnostic_executor +{ +public: + /// The type of the underlying executor. + typedef InnerExecutor inner_executor_type; + + /// The type of the underlying executor. + typedef InnerExecutor nested_executor_type; + + /// The type of the diagnostic label. + typedef Label label_type; + + /// The type of the diagnostic policy. + typedef DiagnosticPolicy diagnostic_policy_type; + + /// Construct from an inner executor and a label. + template + diagnostic_executor(ASIO_MOVE_ARG(InnerEx) inner, ASIO_MOVE_ARG(Lbl) label) + : inner_(ASIO_MOVE_CAST(InnerEx)(inner)), + label_(ASIO_MOVE_CAST(Lbl)(label)) + { + } + + /// Copy constructor. + diagnostic_executor(const diagnostic_executor& other) noexcept + : inner_(other.inner_), + label_(other.label_) + { + } + + /// Move constructor. + diagnostic_executor(diagnostic_executor&& other) noexcept + : inner_(ASIO_MOVE_CAST(InnerExecutor)(other.inner_)), + label_(ASIO_MOVE_CAST(Label)(other.label_)) + { + } + + /// Assignment operator. + diagnostic_executor& operator=(const diagnostic_executor& other) noexcept + { + inner_ = other.inner_; + label_ = other.label_; + return *this; + } + + /// Move assignment operator. + diagnostic_executor& operator=(diagnostic_executor&& other) noexcept + { + inner_ = ASIO_MOVE_CAST(InnerExecutor)(other.inner_); + label_ = ASIO_MOVE_CAST(Label)(other.label_); + return *this; + } + + /// Compare two executors for equality. + /** + * Two diagnostic executors are equal if their underlying executors are equal + * and their labels are equal. + */ + friend bool operator==(const diagnostic_executor& a, + const diagnostic_executor& b) noexcept + { + return a.inner_ == b.inner_ && a.label_ == b.label_; + } + + /// Compare two executors for inequality. + friend bool operator!=(const diagnostic_executor& a, + const diagnostic_executor& b) noexcept + { + return !(a == b); + } + + /// Execution function to submit a function object for execution. + /** + * Invokes the diagnostic policy's @c on_submit hook before forwarding the + * function object to the underlying executor's @c execute function. + */ + template + void execute(ASIO_MOVE_ARG(Function) f) const + { + DiagnosticPolicy::on_submit(label_); + inner_.execute(ASIO_MOVE_CAST(Function)(f)); + } + +#if !defined(ASIO_NO_TS_EXECUTORS) + /// Obtain the underlying execution context. + execution_context& context() const noexcept + { + return inner_.context(); + } + + /// Inform the executor that it has some outstanding work to do. + void on_work_started() const noexcept + { + inner_.on_work_started(); + } + + /// Inform the executor that some work is no longer outstanding. + void on_work_finished() const noexcept + { + inner_.on_work_finished(); + } + + /// Request the underlying executor to invoke the given function object. + template + void dispatch(ASIO_MOVE_ARG(Function) f) const + { + DiagnosticPolicy::on_submit(label_); + asio::dispatch(inner_, ASIO_MOVE_CAST(Function)(f)); + } + + /// Request the underlying executor to invoke the given function object. + template + void dispatch(ASIO_MOVE_ARG(Function) f, const Allocator& a) const + { + DiagnosticPolicy::on_submit(label_); + inner_.dispatch(asio::bind_executor(*this, ASIO_MOVE_CAST(Function)(f)), a); + } + + /// Request the underlying executor to invoke the given function object. + template + void post(ASIO_MOVE_ARG(Function) f) const + { + DiagnosticPolicy::on_submit(label_); + asio::post(inner_, ASIO_MOVE_CAST(Function)(f)); + } + + /// Request the underlying executor to invoke the given function object. + template + void post(ASIO_MOVE_ARG(Function) f, const Allocator& a) const + { + DiagnosticPolicy::on_submit(label_); + inner_.post(asio::bind_executor(*this, ASIO_MOVE_CAST(Function)(f)), a); + } + + /// Request the underlying executor to invoke the given function object. + template + void defer(ASIO_MOVE_ARG(Function) f) const + { + DiagnosticPolicy::on_submit(label_); + asio::defer(inner_, ASIO_MOVE_CAST(Function)(f)); + } + + /// Request the underlying executor to invoke the given function object. + template + void defer(ASIO_MOVE_ARG(Function) f, const Allocator& a) const + { + DiagnosticPolicy::on_submit(label_); + inner_.defer(asio::bind_executor(*this, ASIO_MOVE_CAST(Function)(f)), a); + } +#endif // !defined(ASIO_NO_TS_EXECUTORS) + + /// Get the underlying executor. + ASIO_NODISCARD const InnerExecutor& get_inner_executor() const noexcept + { + return inner_; + } + + /// Forward a query to the underlying executor. + template + ASIO_NODISCARD auto query(ASIO_MOVE_ARG(Property) p) const + noexcept(asio::can_query::value && + asio::is_nothrow_query::value) + -> asio::query_result_t + { + return asio::query(inner_, ASIO_MOVE_CAST(Property)(p)); + } + + /// Forward a requirement to the underlying executor. + template + ASIO_NODISCARD auto require(ASIO_MOVE_ARG(Property) p) const + noexcept(asio::can_require::value && + asio::is_nothrow_require::value) + -> diagnostic_executor>, Label, DiagnosticPolicy> + { + return diagnostic_executor>, Label, DiagnosticPolicy>( + asio::require(inner_, ASIO_MOVE_CAST(Property)(p)), label_); + } + + /// Forward a preference to the underlying executor. + template + ASIO_NODISCARD auto prefer(ASIO_MOVE_ARG(Property) p) const + noexcept(asio::can_prefer::value && + asio::is_nothrow_prefer::value) + -> diagnostic_executor>, Label, DiagnosticPolicy> + { + return diagnostic_executor>, Label, DiagnosticPolicy>( + asio::prefer(inner_, ASIO_MOVE_CAST(Property)(p)), label_); + } + +private: + InnerExecutor inner_; + Label label_; +}; + +/// Create a diagnostic executor for the specified executor and label. +template +ASIO_NODISCARD inline diagnostic_executor, asio::decay_t