VLink  2.1.0
A high-performance communication middleware
ack_manager.h
Go to the documentation of this file.
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 ack_manager.h
26  * @brief Pending-request bookkeeping used to implement blocking RPC calls.
27  *
28  * @details
29  * This is an internal implementation header used by the public @c Client template
30  * and the method-model transport backends; it is not part of the user API.
31  * @c AckManager links the thread that initiates a request with the transport
32  * thread that delivers its acknowledgement: callers obtain a @c RequestPtr, hand
33  * it to @c process() (which both publishes the wire frame through a supplied send
34  * callback and blocks on a per-request condition variable). The matching
35  * @c notify() acknowledges the wait after filling the response; @c remove() cancels it.
36  *
37  * @par Request state diagram
38  * @code
39  * create_request() process()
40  * [none] --------------------> [token] ----------------> [pending]
41  * |
42  * notify() / remove() / timeout / clear()
43  * v
44  * [resolved]
45  * @endcode
46  *
47  * @par API summary
48  * | Method | Caller | Effect |
49  * | ----------------------- | --------------------- | ------------------------------------------------------- |
50  * | @c create_request() | RPC caller | Allocates a unique request token. |
51  * | @c process() | RPC caller | Sends, blocks, returns @c true on @c notify(). |
52  * | @c notify() | Transport callback | Removes pending entry and wakes the waiting caller. |
53  * | @c remove() | RPC caller / cleanup | Cancels an entry and wakes its waiter. |
54  * | @c clear() | Node shutdown | Aborts all waits and refuses new @c process() calls. |
55  * | @c reset_interrupted() | Node resume | Re-enables new @c process() calls after @c clear(). |
56  *
57  * @par Example
58  * @code
59  * vlink::AckManager mgr;
60  * auto req = mgr.create_request();
61  *
62  * bool ok = mgr.process(req, 200, [&]() {
63  * return transport.send(req_bytes); // returning false aborts the wait
64  * });
65  *
66  * // Transport thread, upon receiving a matching reply:
67  * mgr.notify(req, [&]() {
68  * response_bytes = std::move(reply_payload);
69  * });
70  * @endcode
71  *
72  * @par Thread safety
73  * All public methods are safe to call from any thread. Multiple concurrent
74  * @c process() calls may be in flight at the same time; each request is tracked
75  * independently and is keyed by a monotonically increasing sequence number.
76  */
77 
78 #pragma once
79 
80 #include <cstdint>
81 #include <memory>
82 #include <mutex>
83 #include <set>
84 
85 #include "../base/condition_variable.h"
86 #include "../base/functional.h"
87 #include "../base/macros.h"
88 
89 namespace vlink {
90 
91 /**
92  * @class AckManager
93  * @brief Thread-safe coordinator that pairs RPC requests with their acknowledgements.
94  *
95  * @details
96  * Stores a sorted set of in-flight @c RequestPtr handles indexed by their
97  * sequence number and uses one condition variable per request to wake the
98  * blocked caller when @c notify() arrives. Used internally by every
99  * @c ClientImpl subclass to provide the blocking @c call() semantics exposed at
100  * the public @c Client API.
101  */
103  private:
104  struct Request;
105 
106  public:
107  /**
108  * @brief Send callback supplied to @c process(); returning @c false aborts the wait.
109  *
110  * @details
111  * Invoked while the request is already part of the pending set, so any
112  * concurrent @c notify() that arrives before the callback returns is safe.
113  */
114  using ProcessCallback = MoveFunction<bool()>;
115 
116  /**
117  * @brief Optional fill-in callback invoked from inside @c notify() under the lock.
118  *
119  * @details
120  * Lets the transport copy the response into the caller's buffer before the
121  * waiting thread is resumed. May be @c nullptr.
122  */
123  using NotifyCallback = MoveFunction<void()>;
124 
125  /**
126  * @brief Shared handle representing a single in-flight RPC request.
127  *
128  * @details
129  * Returned by @c create_request() and consumed by @c process(), @c notify()
130  * and @c remove(). Equality is implied by the monotonic sequence number.
131  */
132  using RequestPtr = std::shared_ptr<Request>;
133 
134  /**
135  * @brief Constructs an empty manager.
136  */
137  AckManager() noexcept;
138 
139  /**
140  * @brief Destroys the manager and releases any remaining pending records.
141  */
142  ~AckManager() noexcept;
143 
144  /**
145  * @brief Allocates a new request token with the next monotonic sequence number.
146  *
147  * @return Shared handle ready to be passed to @c process().
148  */
149  [[nodiscard]] RequestPtr create_request() noexcept;
150 
151  /**
152  * @brief Publishes @p request via @p process_callback and blocks until it is acknowledged.
153  *
154  * @details
155  * The method performs four steps:
156  * -# Registers @p request in the pending set; returns @c false straight away
157  * if @c clear() has been called and not yet reset.
158  * -# Invokes @p process_callback to dispatch the request. When the callback
159  * returns @c false the entry is removed and @c process() also returns @c false.
160  * -# Sleeps on a per-request condition variable until @c notify() or cancellation
161  * resolves the request, or @p ms expires. A negative @p ms blocks forever.
162  * -# Returns @c true on a successful @c notify(); @c false on timeout, abort or
163  * @c clear() interruption.
164  *
165  * @param request Token obtained from @c create_request().
166  * @param ms Wait budget in milliseconds; negative for unlimited.
167  * @param process_callback Send callback; returning @c false aborts the wait.
168  * @return @c true on acknowledgement, @c false otherwise.
169  */
170  [[nodiscard]] bool process(RequestPtr request, int ms, ProcessCallback&& process_callback) noexcept;
171 
172  /**
173  * @brief Resolves @p request and, if supplied, runs @p notify_callback before waking the caller.
174  *
175  * @details
176  * Erases the entry from the pending set, executes @p notify_callback while
177  * holding the request lock so the caller observes the side effects before
178  * resuming, and signals the condition variable.
179  *
180  * @param request Token to acknowledge.
181  * @param notify_callback Optional callable executed before notification.
182  * @return @c true if the request was still pending and was resolved; @c false otherwise.
183  */
184  bool notify(RequestPtr request, NotifyCallback&& notify_callback = nullptr) noexcept;
185 
186  /**
187  * @brief Cancels @p request and wakes its waiter.
188  *
189  * @param request Token to drop from the pending set.
190  * @return @c true if the entry existed and was removed; @c false otherwise.
191  */
192  bool remove(RequestPtr request) noexcept;
193 
194  /**
195  * @brief Interrupts every pending wait and refuses new ones until @c reset_interrupted().
196  *
197  * @details
198  * Bumps the generation counter, drains the pending set into a local copy and
199  * notifies every condition variable so that the affected @c process() calls
200  * return @c false. Used during node shutdown to avoid blocking destructors.
201  */
202  void clear() noexcept;
203 
204  /**
205  * @brief Permits new @c process() calls again after a previous @c clear().
206  *
207  * @details
208  * Does not unblock requests interrupted by the earlier @c clear(); the
209  * generation tag they were created in remains cancelled.
210  */
211  void reset_interrupted() noexcept;
212 
213  private:
214  struct Request final {
215  enum class Status : uint8_t {
216  kPending,
217  kAcknowledged,
218  kCancelled,
219  };
220 
221  int64_t seq{0};
222  int64_t generation{0};
223  Status status{Status::kPending};
224  std::mutex mtx;
226 
227  struct Compare final {
228  bool operator()(const RequestPtr& left, const RequestPtr& right) const noexcept {
229  if VUNLIKELY (!left || !right) {
230  return left < right; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
231  }
232 
233  return left->seq < right->seq;
234  }
235  };
236  };
237 
238  bool is_interrupted_{false};
239  int64_t request_seq_{0};
240  int64_t generation_{0};
241  mutable std::mutex mtx_;
242  std::set<RequestPtr, Request::Compare> request_set_;
243 
245 };
246 
247 } // namespace vlink
#define VUNLIKELY(...)
Short alias for VLINK_UNLIKELY.
Definition: macros.h:289
#define VLINK_EXPORT
Definition: macros.h:81
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition: macros.h:174