VLink  2.0.0
A high-performance communication middleware
schedule.h
浏览该文件的文档.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file schedule.h
26  * @brief Fluent task-scheduling wrapper used by @c MessageLoop::exec_task() and family.
27  *
28  * @details
29  * @c vlink::Schedule is a non-instantiable utility struct that wraps a user callable in a
30  * @c Config envelope and produces a @c Status (or @c RetStatus for bool-returning callbacks)
31  * RAII handle. The handle lets callers attach continuation callbacks in a fluent style,
32  * including success/failure branches, exception handling, and scheduling/execution timeout
33  * hooks.
34  *
35  * Categories of options carried by @c Schedule::Config:
36  *
37  * | Field | Purpose |
38  * | ------------------------ | ---------------------------------------------------------------------- |
39  * | @c delay_ms | Wait before posting via a one-shot Timer. |
40  * | @c priority | Dispatch priority for priority-aware message loops. |
41  * | @c schedule_timeout_ms | Maximum queue-wait budget after the delay. Triggered once when the |
42  * | | task is dequeued. Drops the task on expiry. |
43  * | @c execution_timeout_ms | Maximum execution budget per callback in the chain. Reported once |
44  * | | after each callback returns; never interrupts a running callback. |
45  *
46  * Status surface exposed to the caller:
47  *
48  * @verbatim
49  * exec_task() -> Status / RetStatus
50  * +--> on_schedule_timeout(cb)
51  * +--> on_execution_timeout(cb)
52  * +--> on_catch(cb)
53  * +--> [RetStatus] on_then(cb)
54  * +--> [RetStatus] on_else(cb)
55  * @endverbatim
56  *
57  * @note
58  * - @c Status is move-only and may be returned by value while it is being configured.
59  * - The wrapped task is not posted to its dispatcher until the configuration handle is committed
60  * (when it is destroyed, i.e. the end of the fluent expression). This guarantees that every
61  * continuation attached through the chain is registered before the task runs, so a fast
62  * dispatcher can never win a race against the caller and drop a continuation.
63  * - Callers that store the handle instead of consuming it inline must call @c dispatch() to post
64  * the task immediately; otherwise it is posted only when the stored handle is destroyed.
65  * - All continuation callbacks run on the dispatcher thread that ran the wrapped task.
66  *
67  * @par Example
68  * @code
69  * vlink::MessageLoop loop;
70  * loop.async_run();
71  *
72  * loop.exec_task(vlink::Schedule::Config{100},
73  * [] { do_work(); })
74  * .on_execution_timeout([] { VLOG_W("slow"); });
75  *
76  * loop.exec_task(vlink::Schedule::Config{},
77  * []() -> bool { return try_connect(); })
78  * .on_then([] { start_session(); })
79  * .on_else([] { schedule_retry(); });
80  * @endcode
81  */
82 
83 #pragma once
84 
85 #include <atomic>
86 #include <cstdint>
87 #include <memory>
88 #include <mutex>
89 #include <vector>
90 
91 #include "./functional.h"
92 #include "./macros.h"
93 
94 namespace vlink {
95 
96 class MessageLoop;
97 
98 /**
99  * @struct Schedule
100  * @brief Non-instantiable container for task-scheduling types and the @c process() entry points.
101  *
102  * @details
103  * Provides the @c Config envelope, the @c Status / @c RetStatus handles, and the
104  * @c process / @c process_with_ret static functions used by @c MessageLoop::exec_task().
105  */
106 struct VLINK_EXPORT Schedule final {
107  /**
108  * @brief Callback signature for void tasks and lifecycle hooks.
109  */
110  using Callback = MoveFunction<void()>;
111 
112  /**
113  * @brief Callback signature for tasks that return a boolean indicating success.
114  */
115  using RetCallback = MoveFunction<bool()>;
116 
117  /**
118  * @brief Callback signature for exception handlers attached via @c on_catch().
119  */
120  using CatchCallback = MoveFunction<void(std::exception&)>;
121 
122  /**
123  * @struct Config
124  * @brief Scheduling parameters captured at the call to @c exec_task().
125  *
126  * @details
127  * All fields default to zero, which corresponds to immediate dispatch with no timeouts.
128  */
129  struct VLINK_EXPORT Config final {
130  /**
131  * @brief Constructs a default @c Config with every field zero-initialised.
132  */
134 
135  /**
136  * @brief Constructs a fully populated @c Config.
137  *
138  * @param _delay_ms Delay before posting in milliseconds.
139  * @param _priority Dispatch priority for priority-aware loops.
140  * @param _schedule_timeout_ms Maximum queue-wait budget after the delay.
141  * @param _execution_timeout_ms Maximum execution budget per callback in the chain.
142  */
143  explicit Config(uint32_t _delay_ms, uint16_t _priority = 0, uint32_t _schedule_timeout_ms = 0,
144  uint32_t _execution_timeout_ms = 0);
145 
146  uint32_t delay_ms{0}; ///< Delay before posting; @c 0 posts immediately.
147  uint16_t priority{0}; ///< Dispatch priority hint; higher fires sooner.
148  uint32_t schedule_timeout_ms{0}; ///< Queue-wait budget after the delay; @c 0 disables.
149  uint32_t execution_timeout_ms{0}; ///< Execution budget per callback; @c 0 disables.
150  };
151 
152  /**
153  * @class Status
154  * @brief RAII handle returned by @c exec_task() when the wrapped callback returns @c void.
155  *
156  * @details
157  * Holds a shared reference to the underlying task state. Continuation callbacks may be attached
158  * in any order before the configuration is committed; the wrapped task is not dispatched until
159  * commit, which happens when the originating handle is destroyed (i.e. the end of the fluent
160  * expression) or when @c dispatch() is called explicitly. Every callback attached before commit
161  * is therefore guaranteed to be registered before the task runs; attachments after commit are
162  * ignored. Committing does not cancel the task; it posts it.
163  */
165  public:
166  /**
167  * @brief Constructs a fresh handle backed by a newly allocated task state.
168  */
170 
171  /**
172  * @brief Destroys the handle reference, committing the configuration so the task may dispatch.
173  *
174  * @details
175  * Commits the pending configuration: once the originating handle is gone no further
176  * continuation callbacks can be attached, so the dispatcher is released to run the task
177  * with the full callback chain in place. Does not cancel the task.
178  */
180 
181  Status(const Status&) = delete;
182 
183  Status& operator=(const Status&) = delete;
184 
185  /**
186  * @brief Move-constructs from @p status, transferring its task state.
187  *
188  * @param status Source handle to move from.
189  */
190  Status(Status&& status) noexcept;
191 
192  /**
193  * @brief Move-assigns from @p status, transferring its task state.
194  *
195  * @param status Source handle to move from.
196  * @return Reference to @c *this.
197  */
198  Status& operator=(Status&& status) noexcept;
199 
200  /**
201  * @brief Marks whether the handle's underlying task was successfully posted.
202  *
203  * @param valid @c true once the task is queued.
204  */
205  void set_valid(bool valid);
206 
207  /**
208  * @brief Reports whether the wrapped task was successfully posted.
209  *
210  * @return @c true when the handle refers to a queued task.
211  */
212  [[nodiscard]] bool is_valid() const;
213 
214  /**
215  * @brief Commits the configuration immediately and reports whether the task was posted.
216  *
217  * @details
218  * The task is otherwise posted when the handle is destroyed. A caller that stores the handle
219  * instead of consuming it inline calls @c dispatch() to post it now and learn the result
220  * synchronously. Attach all continuations first; idempotent with the destructor.
221  *
222  * @return @c true when the task was accepted by its dispatcher.
223  */
224  bool dispatch();
225 
226  /**
227  * @brief Installs the callback fired when the task missed its scheduling deadline.
228  *
229  * @details
230  * Triggered once when the task is dequeued and the elapsed time since posting exceeds
231  * @c delay_ms + @c schedule_timeout_ms. Only one schedule-timeout callback may be
232  * registered; late registrations are dropped.
233  *
234  * @param callback Hook invoked on the dispatcher thread.
235  * @return Reference to @c *this for fluent chaining.
236  */
238 
239  /**
240  * @brief Installs the callback fired when a chained callback exceeds @c execution_timeout_ms.
241  *
242  * @details
243  * Triggered after each callback in the chain returns. Only one execution-timeout
244  * callback may be registered; late registrations are dropped. Does not interrupt a
245  * running callback.
246  *
247  * @param callback Hook invoked on the dispatcher thread.
248  * @return Reference to @c *this for fluent chaining.
249  */
251 
252  /**
253  * @brief Installs the callback fired when the task throws a @c std::exception.
254  *
255  * @details
256  * Only @c std::exception-derived failures are caught; other exception types are
257  * allowed to propagate. Only one catch callback may be registered.
258  *
259  * @param callback Hook receiving the caught exception.
260  * @return Reference to @c *this for fluent chaining.
261  */
263 
264  protected:
265  friend Schedule;
266  friend MessageLoop;
267 
268  struct Impl final {
269  std::atomic_bool is_valid{false};
270  std::atomic_bool dispatched{false};
271  std::recursive_mutex mtx;
276  std::vector<RetCallback> then_callback_list;
277  };
278 
279  /**
280  * @brief Runs the installed launcher exactly once, posting the wrapped task to its dispatcher.
281  *
282  * @details
283  * Posting is deferred until the handle is committed so every fluent continuation is registered
284  * before the task is enqueued. The launcher lives in the handle (not the shared state), so the
285  * post payload is released with the handle and forms no reference cycle. Idempotent and safe on
286  * a moved-from handle.
287  */
288  void commit() noexcept;
289 
290  std::shared_ptr<Impl> impl_;
291  Callback launcher_;
292  bool committed_{false};
293  };
294 
295  /**
296  * @class RetStatus
297  * @brief RAII handle returned by @c exec_task() when the wrapped callback returns @c bool.
298  *
299  * @details
300  * Extends @c Status with the @c on_then chain and the @c on_else fallback so callers can
301  * express success/failure branches inline.
302  */
303  class VLINK_EXPORT RetStatus final : public Status {
304  public:
305  using Status::Status;
306 
307  /**
308  * @brief Installs the callback fired when the wrapped task returns @c false.
309  *
310  * @details
311  * Only one else callback may be registered; late registrations are dropped.
312  *
313  * @param callback Hook invoked on the dispatcher thread when @c false is returned.
314  * @return Reference to the base @c Status for further chaining.
315  */
316  Status& on_else(Callback&& callback);
317 
318  /**
319  * @brief Appends a continuation that runs only when the previous callback returned @c true.
320  *
321  * @details
322  * Multiple @c on_then callbacks may be chained. Each is invoked in registration order
323  * until one returns @c false, at which point the chain stops and the registered
324  * @c on_else (if any) fires.
325  *
326  * @param callback Continuation taking no arguments and returning @c bool.
327  * @return Reference to @c *this for further chaining.
328  */
330  };
331 
332  Schedule() = delete;
333 
334  Schedule(const Schedule&) = delete;
335 
336  Schedule& operator=(const Schedule&) = delete;
337 
338  Schedule(Schedule&&) = delete;
339 
341 
342  /**
343  * @brief Wraps a void callback in a @c Config envelope and produces the task wrapper for the dispatcher.
344  *
345  * @details
346  * Called internally by @c MessageLoop::exec_task(). Allocates the @c Status state and
347  * fills @p wrapper_callback with the closure that the dispatcher will eventually run.
348  *
349  * @param config Scheduling configuration.
350  * @param callback Void callable to execute.
351  * @param wrapper_callback Out parameter receiving the dispatcher-ready wrapper.
352  * @return Fresh @c Status handle for fluent chaining.
353  */
354  [[nodiscard]] static Status process(const Config& config, Callback&& callback, Callback& wrapper_callback);
355 
356  /**
357  * @brief Wraps a bool-returning callback in a @c Config envelope and produces the task wrapper for the dispatcher.
358  *
359  * @details
360  * Called internally by @c MessageLoop::exec_task(). Allocates the @c RetStatus state and
361  * fills @p wrapper_callback with the closure that the dispatcher will eventually run.
362  *
363  * @param config Scheduling configuration.
364  * @param callback Bool-returning callable to execute.
365  * @param wrapper_callback Out parameter receiving the dispatcher-ready wrapper.
366  * @return Fresh @c RetStatus handle for fluent chaining.
367  */
368  [[nodiscard]] static RetStatus process_with_ret(const Config& config, RetCallback&& callback,
369  Callback& wrapper_callback);
370 
371  private:
372  static RetStatus internal_process_with_ret(const Config& config, RetCallback&& callback, Callback& wrapper_callback);
373 };
374 
375 } // namespace vlink
Pool-backed type-erased callables: copyable vlink::Function and move-only vlink::MoveFunction.
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VLINK_EXPORT
Definition: macros.h:81