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