diff --git a/include/deprecated/dmn-limit-blockingqueue.hpp b/include/deprecated/dmn-limit-blockingqueue.hpp index 79563d97..acdb1bdf 100644 --- a/include/deprecated/dmn-limit-blockingqueue.hpp +++ b/include/deprecated/dmn-limit-blockingqueue.hpp @@ -62,89 +62,79 @@ class Dmn_Limit_BlockingQueue : private Dmn_BlockingQueue { operator=(Dmn_Limit_BlockingQueue &&obj) = delete; /** - * @brief The method will pop and return front item from the queue or the - * caller is blocked waiting if the queue is empty. + * @brief Pop and return the front item, blocking until one is available. * - * @return front item of the queue + * @return The front item of the queue. */ auto pop() -> T; /** - * @brief Return true or false if the m_size is zero. + * @brief Return @c true if the queue contains no items. * - * @return True if m_size is 0 or false otherwise. + * @return @c true when @c m_size is 0, @c false otherwise. */ auto empty() -> bool; /** - * @brief The method will pop and return front item from the queue or the - * std::nullopt if the queue is empty. + * @brief Pop and return the front item without blocking. * - * @return optional item from the front of the queue + * @return An optional containing the front item, or @c std::nullopt if + * the queue is empty. */ auto popNoWait() -> std::optional; /** - * @brief The method will push the item into queue using move semantics - * unless noexcept is false. The caller is blocked waiting if the - * queue is full. + * @brief Enqueue an rvalue item, preferring move semantics. * - * @param item The item to be pushed into queue + * Blocks if the queue is at maximum capacity until space becomes available. + * + * @param item The item to enqueue (moved when the move constructor is + * noexcept). */ void push(T &&item); /** - * @brief The method will push the item into queue using move semantics if - * move is true. The caller is blocked waiting if the queue is full. + * @brief Enqueue an lvalue item, optionally using move semantics. + * + * Blocks if the queue is at maximum capacity until space becomes available. * - * @param item The item to be pushed into queue - * @param move True if use move semantic or false otherwise + * @param item The item to enqueue. + * @param move If @c true, move @p item into the queue; otherwise copy it. */ void push(T &item, bool move = true); /** - * @brief The method returns the number of items held in the queue now. + * @brief Return a snapshot of the current number of items in the queue. * - * @return The number of items held in the queue now + * @return The number of items currently held in the queue. */ auto size() -> size_t; /** - * @brief The method will put the client on blocking wait until - * the queue is empty, it returns number of items that - * were passed through the queue in total. + * @brief Block until the queue is empty and return the total number of items + * that have passed through it. * - * @return The number of items that were passed through the queue - * in total + * @return The cumulative number of items that have been pushed and popped. */ auto waitForEmpty() -> uint64_t override; private: /** - * @brief The method will pop front item from the queue and return it - * or block waiting for item if the queue is empty and wait is - * true. + * @brief Internal pop helper that optionally blocks waiting for an item. * - * @param wait The caller is blocked waiting for item if queue is empty - * and wait is true, otherwise returning std::nullopt + * @param wait If @c true, block until an item is available; if @c false, + * return @c std::nullopt immediately when the queue is empty. * - * @return optional value from front item of the queue + * @return An optional containing the front item, or @c std::nullopt. */ auto popOptional(bool wait) -> std::optional override; private: - /** - * data members for constructor to instantiate the object. - */ - size_t m_max_capacity{1}; - - /** - * data members for internal logic. - */ - size_t m_size{0}; - std::mutex m_mutex{}; - std::condition_variable m_pop_cond{}; - std::condition_variable m_push_cond{}; + size_t m_max_capacity{1}; ///< Maximum number of items the queue may hold. + size_t m_size{0}; ///< Current number of items in the queue. + std::mutex m_mutex{}; ///< Protects all access to @c m_size and the underlying storage. + std::condition_variable m_pop_cond{}; ///< Signalled when a new item is available for consumers. + std::condition_variable m_push_cond{}; ///< Signalled when a slot becomes available for producers. }; // class Dmn_Limit_BlockingQueue template diff --git a/include/dmn-blockingqueue-lf.hpp b/include/dmn-blockingqueue-lf.hpp index 55ea76dd..b912ef77 100644 --- a/include/dmn-blockingqueue-lf.hpp +++ b/include/dmn-blockingqueue-lf.hpp @@ -137,19 +137,27 @@ class Dmn_BlockingQueue_Lf * sure that we do not have a large number of retired nodes waiting to be * freed. */ - static constexpr uint64_t s_epochTimeScale{500}; - static constexpr uint64_t s_epochIdScale{2}; - static constexpr uint32_t s_epochDataSize{50}; - + static constexpr uint64_t s_epochTimeScale{ + 500}; ///< API-call interval between epoch advances. + static constexpr uint64_t s_epochIdScale{ + 2}; ///< Consecutive epoch IDs mapped to a single bucket. + static constexpr uint32_t s_epochDataSize{ + 50}; ///< Number of epoch reclamation buckets. + + /** @brief Atomically updated epoch metadata (cache-line aligned to avoid + * false sharing). */ struct alignas(16) EpochData { - uint64_t m_in_flight_total{}; - uint64_t m_id{}; + uint64_t m_in_flight_total{}; ///< Global in-flight API-call count when the + ///< current epoch started. + uint64_t m_id{}; ///< Monotonically increasing epoch identifier. }; + /** @brief Internal linked-list node holding a stored item. */ struct Node { - T m_data{}; - std::atomic m_next{nullptr}; - std::atomic m_retired_next{nullptr}; + T m_data{}; ///< The stored queue element. + std::atomic m_next{nullptr}; ///< Next node in the live queue chain. + std::atomic m_retired_next{ + nullptr}; ///< Next node in the epoch retired list. }; public: @@ -157,9 +165,14 @@ class Dmn_BlockingQueue_Lf using Dmn_BlockingQueue, T>::pop; using Dmn_BlockingQueue, T>::push; + /** @brief Default constructor: initialises the sentinel head/tail node and + * epoch bookkeeping structures. */ Dmn_BlockingQueue_Lf(); + + /** @brief Construct and populate the queue from an initializer list. */ Dmn_BlockingQueue_Lf(std::initializer_list list); + /** @brief Destroy the queue; triggers shutdown and reclaims all nodes. */ virtual ~Dmn_BlockingQueue_Lf() noexcept; Dmn_BlockingQueue_Lf(const Dmn_BlockingQueue_Lf &obj) = delete; @@ -327,25 +340,29 @@ class Dmn_BlockingQueue_Lf } /** - * @brief The method frees a chain of nodes. + * @brief Free all nodes in a forward-linked list via @c m_next pointers. * - * @param head The head pointer to a chain of nodes. + * @param head Pointer to the first node in the chain to free. + * @return Number of nodes freed. */ auto freeNodeList(Node *head) -> uint64_t; /** - * @brief The method frees a chain of retired nodes. + * @brief Free all nodes in a retired-node list linked via + * @c m_retired_next pointers. * - * @param head The head pointer to a chain of retired nodes. + * @param head Pointer to the first node in the retired chain to free. + * @return Number of nodes freed. */ auto freeRetiredNodeList(Node *head) -> uint64_t; /** - * @brief The method returns a node into epoch based reclamation - * blocks, and free it later. + * @brief Defer deletion of @p node by placing it on the epoch's retired + * list; the node is freed when the epoch's in-flight count reaches + * zero. * - * @param epochIndex Index to the epoch block to retire the node. - * @param node Pointer to the node to be free. + * @param epochIndex Index of the epoch bucket to which @p node is retired. + * @param node Pointer to the node to retire. */ void retireNode(uint64_t epochIndex, Node *node); diff --git a/include/dmn-blockingqueue-mt.hpp b/include/dmn-blockingqueue-mt.hpp index 659df9e1..e0f1c50b 100644 --- a/include/dmn-blockingqueue-mt.hpp +++ b/include/dmn-blockingqueue-mt.hpp @@ -177,10 +177,12 @@ class Dmn_BlockingQueue_Mt protected: /** - * @brief Return true if the queue is stop (m_shutdown_flag is true), or false - * otherwise. The method is delegation of Dmn_Inflight_Guard module. + * @brief Return @c true if the queue has been shut down + * (@c m_shutdown_flag is set), @c false otherwise. * - * @return true or false that the queue is shutdown. + * Implements the @c Dmn_Inflight_Guard closed-state predicate. + * + * @return @c true when the queue is shut down. */ auto isInflightGuardClosed() -> bool override; diff --git a/include/dmn-debug.hpp b/include/dmn-debug.hpp index 3a187584..707a09c9 100644 --- a/include/dmn-debug.hpp +++ b/include/dmn-debug.hpp @@ -1,5 +1,5 @@ /** - * Copyright © 2025 Chee Bin HOH. All rights reserved. + * Copyright © 2025 Chee Bin HOH. All rights reserved. * * @file include/dmn-debug.hpp * @brief Lightweight debug-print macro controlled by the preprocessor. diff --git a/include/dmn-dmesg.hpp b/include/dmn-dmesg.hpp index a164a97f..4dc2cf63 100644 --- a/include/dmn-dmesg.hpp +++ b/include/dmn-dmesg.hpp @@ -254,29 +254,29 @@ class Dmn_DMesg : public Dmn_Pub { Dmn_DMesgHandler &operator=(Dmn_DMesgHandler &&obj) = delete; /** - * @brief The method returns yes or not if the handler is in conflict + * @brief Check whether this handler is currently in a conflict state. * - * @param topic the topic to check if it is in conflict, or "" any topic. + * @param topic The topic to check, or an empty string to check any topic. * - * @return True or False if the handler is in conflict state from last - * written message + * @return @c true if the handler is in conflict for the given topic (or for + * any topic when @p topic is empty), @c false otherwise. */ auto isInConflict(std::string_view topic = "") -> bool; /** - * @brief The method returns running counter of the topic. + * @brief Return the current running counter for the given topic. * - * @param topic The topic + * @param topic The topic whose running counter is queried. * - * @return The running counter of the topic + * @return The running counter value for @p topic. */ auto getTopicRunningCounter(std::string_view topic) -> uint64_t; /** - * @brief The method sets running counter of the topic. + * @brief Set the running counter for the given topic. * - * @param topic The topic - * @param runningCounter The running counter to be set for the topic + * @param topic The topic whose running counter is to be updated. + * @param runningCounter The new counter value to assign. */ void setTopicRunningCounter(std::string_view topic, uint64_t runningCounter); @@ -399,19 +399,19 @@ class Dmn_DMesg : public Dmn_Pub { protected: /** - * @brief The method returns running counter of the topic. + * @brief Return the running counter for the given topic (internal helper). * - * @param topic The topic + * @param topic The topic whose running counter is queried. * - * @return The running counter of the topic + * @return The running counter value for @p topic. */ auto getTopicRunningCounterInternal(std::string_view topic) -> uint64_t; /** - * @brief The method sets running counter of the topic. + * @brief Set the running counter for the given topic (internal helper). * - * @param topic The topic - * @param runningCounter The running counter to be set for the topic + * @param topic The topic whose running counter is to be updated. + * @param runningCounter The new counter value to assign. */ void setTopicRunningCounterInternal(std::string_view topic, uint64_t runningCounter); diff --git a/include/dmn-dmesgnet.hpp b/include/dmn-dmesgnet.hpp index 538191fa..7123d817 100644 --- a/include/dmn-dmesgnet.hpp +++ b/include/dmn-dmesgnet.hpp @@ -75,8 +75,15 @@ namespace dmn { +/** @brief Heartbeat period in nanoseconds (1 second). */ #define DMN_DMESGNET_HEARTBEAT_IN_NS (1000000000) + +/** @brief Maximum number of consecutive heartbeat cycles to wait for a master + * acknowledgement before declaring self as master. */ #define DMN_DMESGNET_MASTERPENDING_MAX_COUNTER (3) + +/** @brief Maximum number of consecutive heartbeat cycles to wait for a master + * synchronisation confirmation before triggering a re-election. */ #define DMN_DMESGNET_MASTERSYNC_MAX_COUNTER (5) class Dmn_DMesgNet : public Dmn_DMesg { @@ -121,45 +128,80 @@ class Dmn_DMesgNet : public Dmn_DMesg { */ void reconciliateDMesgPbSys(const dmn::DMesgPb &dmesgpb_other); + /** + * @brief Return the internal per-topic last-published message cache. + * + * Overrides @c Dmn_DMesg::getLastTopicCacheInternal() to return the + * network layer's own cache (@c m_topic_last_dmesgpb) rather than the + * base-class cache, so that heartbeat and reconciliation logic use the + * correct view. + * + * @return Reference to the network layer's topic-to-last-message map. + */ auto getLastTopicCacheInternal() -> std::unordered_map & override; private: + /** @brief Create and start the background thread that reads from + * @c m_input_handler and republishes inbound messages locally. */ void createInputHandlerProc(); + + /** @brief Register the internal publish-subscriber handler that forwards + * outbound local messages to @c m_output_handler. */ void createSubscriptHandler(); + + /** @brief Create and start the periodic heartbeat timer thread. */ void createTimerProc(); + + /** @brief Drain and send any messages queued in the outbound pending + * buffer (@c m_topic_outbound_pending_dmesgpb). */ void sendPendingOutboundQueueMessage(); /** * Constructor-initialized members. */ - std::string m_name{}; - std::shared_ptr> m_input_handler{}; - std::shared_ptr> m_output_handler{}; + std::string m_name{}; ///< Instance name. + std::shared_ptr> + m_input_handler{}; ///< Inbound I/O handler. + std::shared_ptr> + m_output_handler{}; ///< Outbound I/O handler. /** * Internal runtime state and helper objects. */ - std::unique_ptr m_input_proc{}; - Dmn_DMesg::HandlerType m_subscript_handler{}; - Dmn_DMesg::HandlerType m_write_handler{}; - Dmn_DMesg::HandlerType m_sys_handler{}; - std::unique_ptr> m_timer_proc{}; - - dmn::DMesgPb m_sys{}; - long long m_master_pending_counter{}; - long long m_master_sync_pending_counter{}; - struct timeval m_last_remote_master_timestamp{}; - std::unordered_map m_topic_last_dmesgpb{}; + std::unique_ptr + m_input_proc{}; ///< Thread reading from @c m_input_handler. + Dmn_DMesg::HandlerType + m_subscript_handler{}; ///< Subscriber handler for local messages. + Dmn_DMesg::HandlerType m_write_handler{}; ///< Handler for write operations. + Dmn_DMesg::HandlerType + m_sys_handler{}; ///< Handler for system (heartbeat) messages. + std::unique_ptr> + m_timer_proc{}; ///< Heartbeat timer. + + dmn::DMesgPb + m_sys{}; ///< Local system DMesgPb (node identity, neighbor list). + long long m_master_pending_counter{}; ///< Cycles spent waiting for a master + ///< acknowledgement. + long long m_master_sync_pending_counter{}; ///< Cycles spent waiting for + ///< master synchronisation. + struct timeval + m_last_remote_master_timestamp{}; ///< Timestamp of the last received + ///< master heartbeat. + std::unordered_map + m_topic_last_dmesgpb{}; ///< Last published message per topic. std::unordered_map> - m_topic_outbound_pending_dmesgpb{}; + m_topic_outbound_pending_dmesgpb{}; ///< Outbound messages waiting to be + ///< sent. - std::atomic_flag m_ready{}; // the DmesgNet is ready (master selection) + std::atomic_flag m_ready{}; ///< Set once master election has completed and + ///< this node is ready. - bool m_is_master{}; - long long m_number_of_neighbor{}; + bool m_is_master{}; ///< True when this node is the elected master. + long long m_number_of_neighbor{}; ///< Number of known neighbor nodes + ///< (excluding self). - std::atomic m_shutdown{}; + std::atomic m_shutdown{}; ///< True once shutdown has been requested. }; // class Dmn_DMesgNet } // namespace dmn diff --git a/include/dmn-io.hpp b/include/dmn-io.hpp index ef834ea5..2d0ecd21 100644 --- a/include/dmn-io.hpp +++ b/include/dmn-io.hpp @@ -43,6 +43,15 @@ namespace dmn { +/** + * @brief Transport-agnostic interface for reading and writing values of type T. + * + * @tparam T The item type exchanged through this interface. + * + * Concrete implementations may represent files, pipes, network sockets, + * message queues, or other data sources and sinks. See the file-level + * documentation for the full semantics of each method. + */ template class Dmn_Io { public: virtual ~Dmn_Io() noexcept { shutdown(); } @@ -100,8 +109,8 @@ template class Dmn_Io { virtual void write(T &&item) = 0; /** - * @brief Shutdown the io RAII and prevent it from further use to - * faciliate object teardown. + * @brief Shut down the I/O object and prevent further use to facilitate + * object teardown. */ virtual void shutdown() {} }; diff --git a/include/dmn-pipe.hpp b/include/dmn-pipe.hpp index fe44d1a4..6709029e 100644 --- a/include/dmn-pipe.hpp +++ b/include/dmn-pipe.hpp @@ -37,7 +37,7 @@ * and invokes the provided task with the item (moved where possible). * - read(count, timeout) and readAndProcss(fn, count, timeout) function * behaves like it counterpart without count and timeout but with the - * following blocking behevior + * following blocking behavior * 1. If the pipe already contains >= count items, it returns exactly * `count` items immediately. * 2. If the pipe contains 0 items, it blocks: @@ -95,9 +95,27 @@ class Dmn_Pipe : public Dmn_Io, private QueueType, private Dmn_Proc { public: using Dmn_Io::write; + /** + * @brief Construct a Dmn_Pipe and optionally start a background processing + * thread. + * + * @param name Human-readable name forwarded to the underlying @c Dmn_Proc. + * @param fn Optional processing task invoked for each item dequeued by + * the background thread. If empty, no background thread is + * started and items must be consumed via read() or + * readAndProcess(). + * @param count Number of items to dequeue per background-thread iteration. + * Defaults to 1. + * @param timeout Timeout in microseconds passed to each pop call in the + * background loop. 0 means wait indefinitely. + */ explicit Dmn_Pipe(std::string_view name, Dmn_Pipe::Task fn = {}, size_t count = 1, long timeout = 0); + /** + * @brief Destroy the pipe, stopping any background processing thread and + * releasing resources. + */ virtual ~Dmn_Pipe() noexcept; Dmn_Pipe(const Dmn_Pipe &obj) = delete; diff --git a/include/dmn-proc.hpp b/include/dmn-proc.hpp index c665f526..8afec46c 100644 --- a/include/dmn-proc.hpp +++ b/include/dmn-proc.hpp @@ -51,12 +51,20 @@ #include /** - * More generic macro to wrap pthread_cleanup_push + * @brief Macro wrapper around @c pthread_cleanup_push. + * + * Registers a cleanup handler to be called when the current thread is + * cancelled or when @c DMN_PROC_CLEANUP_POP is executed. Arguments are + * forwarded verbatim to @c pthread_cleanup_push. */ #define DMN_PROC_CLEANUP_PUSH(...) pthread_cleanup_push(__VA_ARGS__) /** - * More generic macro to wrap pthread_cleanup_pop + * @brief Macro wrapper around @c pthread_cleanup_pop. + * + * Pops the most recently pushed cleanup handler. If the argument is + * non-zero, the handler is also executed. Arguments are forwarded + * verbatim to @c pthread_cleanup_pop. */ #define DMN_PROC_CLEANUP_POP(...) pthread_cleanup_pop(__VA_ARGS__) @@ -75,10 +83,8 @@ namespace dmn { void cleanupFuncToUnlockPthreadMutex(void *arg); /** - * Dmn_Proc - * - * A small RAII-style wrapper around pthreads that runs a user-provided task - * (std::function) in a new thread. + * @brief A small RAII-style wrapper around pthreads that runs a user-provided + * task (@c std::function) in a new thread. * * Behaviour details: * - Construct with an optional name and/or task. The name is stored for @@ -101,7 +107,15 @@ void cleanupFuncToUnlockPthreadMutex(void *arg); * are cancellation points). */ class Dmn_Proc { - enum class State { kUnknown, kNew, kReady, kRunning }; + /** + * @brief Lifecycle state of a @c Dmn_Proc instance. + */ + enum class State { + kUnknown, ///< Invalid / post-destruction state. + kNew, ///< Constructed but no task assigned yet. + kReady, ///< Task assigned; ready to be started via exec(). + kRunning, ///< Thread is running; task is executing. + }; public: using Task = std::function; diff --git a/include/dmn-pub-sub.hpp b/include/dmn-pub-sub.hpp index 5143e983..6212b1b0 100644 --- a/include/dmn-pub-sub.hpp +++ b/include/dmn-pub-sub.hpp @@ -6,7 +6,7 @@ * * Overview * -------- - * - This header provides a small, efficient publish/subscribe adaptar classes: + * - This header provides a small, efficient publish/subscribe adaptor classes: * * Dmn_Pub publishes items of type T. * * Dmn_Pub::Dmn_Sub is the subscriber interface that receives items. * @@ -29,7 +29,7 @@ * * Threading and execution model * ----------------------------- - * - The Dmn_Pub is derivade from Dmn_Async. Each Dmn_Pub object has its + * - The Dmn_Pub is derived from Dmn_Async. Each Dmn_Pub object has its * own singleton asynchronous execution context as provided by Dmn_Async. * - publish(const T&) schedules a delivery task in the publisher's async * context. That task (publishInternal) performs buffering and schedules per- diff --git a/include/dmn-runtime-task.hpp b/include/dmn-runtime-task.hpp index 9eaafa64..573b4b53 100644 --- a/include/dmn-runtime-task.hpp +++ b/include/dmn-runtime-task.hpp @@ -26,11 +26,14 @@ struct Dmn_Runtime_Task { * propagation for @c Dmn_Runtime_Task coroutines. */ struct promise_type { + /** @brief Return the @c Dmn_Runtime_Task handle that wraps this promise. */ Dmn_Runtime_Task get_return_object() { return Dmn_Runtime_Task{ std::coroutine_handle::from_promise(*this)}; } + /** @brief Suspend the coroutine immediately on entry so the scheduler + * controls when it first runs. */ std::suspend_always initial_suspend() { return {}; } /** @@ -38,21 +41,30 @@ struct Dmn_Runtime_Task { * (if any) when this coroutine finishes. */ struct FinalAwaiter { + /** @brief Never ready — always suspend to allow continuation transfer. */ bool await_ready() const noexcept { return false; } + /** @brief Resume the stored continuation (if any) via symmetric transfer. + */ void await_suspend(std::coroutine_handle h) const noexcept { if (h && h.promise().m_continuation) { h.promise().m_continuation.resume(); } } + /** @brief No-op: the coroutine frame is already finished at this point. + */ void await_resume() const noexcept {} }; + /** @brief Return the custom final awaiter that chains the continuation. */ FinalAwaiter final_suspend() noexcept { return {}; } + /** @brief Capture any exception thrown by the coroutine body for later + * rethrowing via @c Awaiter::await_resume(). */ void unhandled_exception() { m_except = std::current_exception(); } + /** @brief Coroutines that return @c void reach this on co_return. */ void return_void() {} std::coroutine_handle<> @@ -79,6 +91,8 @@ struct Dmn_Runtime_Task { std::coroutine_handle m_handle; ///< Handle to the task coroutine being awaited. + /** @brief Return @c true when the task has already completed (or the + * handle is null), so no suspension is needed. */ bool await_ready() const noexcept { return !m_handle || m_handle.done(); } std::coroutine_handle<> @@ -94,6 +108,8 @@ struct Dmn_Runtime_Task { return std::noop_coroutine(); } + /** @brief Resume after task completion; rethrow any exception stored in + * the promise. */ void await_resume() { if (m_handle) { auto &promise = m_handle.promise(); @@ -104,14 +120,20 @@ struct Dmn_Runtime_Task { } }; + /** @brief Return an @c Awaiter that suspends the caller until this task + * finishes (non-const overload). */ [[nodiscard]] Awaiter operator co_await() noexcept { return Awaiter{m_handle}; } + /** @brief Return an @c Awaiter that suspends the caller until this task + * finishes (const overload). */ [[nodiscard]] Awaiter operator co_await() const noexcept { return Awaiter{m_handle}; } + /** @brief Destroy the coroutine frame if this task still owns a valid + * handle. */ ~Dmn_Runtime_Task() noexcept { if (m_handle) { m_handle.destroy(); diff --git a/include/dmn-runtime.hpp b/include/dmn-runtime.hpp index c56eaf35..a274867d 100644 --- a/include/dmn-runtime.hpp +++ b/include/dmn-runtime.hpp @@ -577,9 +577,14 @@ void Dmn_Runtime_Manager::addTimedJob( } /** - * @brief The method will execute the job in runtime coroutine context. + * @brief Add the given job to the coroutine scheduler context so it can be + * picked up and executed as a coroutine task. * - * @param job The job to be run in the runtime context + * Must be called from within the singleton asynchronous thread context + * (asserted via @c isRunInAsyncThread()). + * + * @param job The runtime job to schedule; its @c m_fnc is invoked to produce + * the coroutine task that will be driven by the scheduler. */ template