Branch data Line data Source code
1 : : /*
2 : : * Copyright 2025 Jinwoo Sung
3 : : *
4 : : * Licensed under the Apache License, Version 2.0 (the "License");
5 : : * you may not use this file except in compliance with the License.
6 : : * You may obtain a copy of the License at
7 : : *
8 : : * http://www.apache.org/licenses/LICENSE-2.0
9 : : *
10 : : * Unless required by applicable law or agreed to in writing, software
11 : : * distributed under the License is distributed on an "AS IS" BASIS,
12 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : : * See the License for the specific language governing permissions and
14 : : * limitations under the License.
15 : : */
16 : :
17 : : #pragma once
18 : :
19 : : #include <functional>
20 : : #include <future>
21 : : #include <memory>
22 : : #include <optional>
23 : : #include <string_view>
24 : : #include <vector>
25 : :
26 : : #include "wirestead/base/visibility.hpp"
27 : : #include "wirestead/diagnostics/logger.hpp"
28 : : #include "wirestead/framer/iframer.hpp"
29 : : #include "wirestead/wrapper/context.hpp"
30 : : #include "wirestead/wrapper/runtime_stats.hpp"
31 : :
32 : : namespace wirestead {
33 : : namespace wrapper {
34 : :
35 : : /**
36 : : * @brief Interface for 1:N server communication (e.g., TcpServer)
37 : : */
38 : : class WIRESTEAD_API ServerInterface {
39 : : public:
40 : : using MessageHandler = std::function<void(const MessageContext&)>;
41 : : using BatchMessageHandler = std::function<void(const std::vector<MessageContext>&)>;
42 : : using ConnectionHandler = std::function<void(const ConnectionContext&)>;
43 : : using ErrorHandler = std::function<void(const ErrorContext&)>;
44 : : using FramerFactory = std::function<std::unique_ptr<framer::IFramer>()>;
45 : :
46 : 185 : virtual ~ServerInterface() = default;
47 : :
48 : : // Lifecycle
49 : : /**
50 : : * @brief Start the server asynchronously.
51 : : *
52 : : * @return A future that resolves to true when listening, or false on failure
53 : : * (e.g. bind failure, port already in use). This future always
54 : : * resolves - including on a restart after stop() (#444) - never
55 : : * blocks indefinitely.
56 : : */
57 : : [[nodiscard]] virtual std::future<bool> start() = 0;
58 : :
59 : : /**
60 : : * @brief Synchronously start the channel/server and wait for the result.
61 : : */
62 : 7 : [[nodiscard]] virtual bool start_sync() { return start().get(); }
63 : :
64 : : /**
65 : : * @brief Stop the server and block until all active sessions are closed.
66 : : *
67 : : * Safe to call from any thread. After stop() returns, no further callbacks will fire
68 : : * and it is safe to destroy the object. Calling stop() more than once is a no-op.
69 : : *
70 : : * Restart contract (#444): stop() fully tears down the underlying transport
71 : : * (acceptor, sessions, timers) rather than leaving it in a reusable
72 : : * half-alive state. Every on_*() callback and every config setter ever
73 : : * called on this wrapper remains in force across any number of stop()/
74 : : * start() cycles - a subsequent start() reconstructs the transport from
75 : : * this wrapper's retained configuration and re-registers all callbacks
76 : : * automatically, including config changes made while stopped. Connected-
77 : : * client state and stats() reset on restart. Callers never need to
78 : : * re-register callbacks or reconfigure after a stop()/start() cycle.
79 : : */
80 : : virtual void stop() = 0;
81 : : virtual bool listening() const = 0;
82 : :
83 : : /**
84 : : * @brief Snapshot of this server's runtime counters.
85 : : *
86 : : * The cumulative fields (`bytes_*`, `messages_*`, `failed_sends`, `dropped_*`,
87 : : * `backpressure_events`, `max_queued_bytes`) cover every session this server
88 : : * has accepted, including ones that have since disconnected. The
89 : : * instantaneous fields (`queued_bytes`, `pending_bytes`,
90 : : * `backpressure_active`) describe only the sessions that are live right now.
91 : : *
92 : : * Cleared by reset_stats(), and by a stop()/start() cycle per the restart
93 : : * contract above. See docs/error_model.md.
94 : : */
95 : : virtual RuntimeStats stats() const = 0;
96 : :
97 : : /**
98 : : * @brief Counters for one connected client.
99 : : *
100 : : * Returns nullopt when the id is unknown - including for a client that has
101 : : * already disconnected, since a session's counters are folded into the
102 : : * server-wide stats() and the session itself is gone. Sample this while the
103 : : * client is connected if you need its numbers in isolation.
104 : : *
105 : : * Not every server can answer. UDP servers group datagrams into virtual
106 : : * sessions that have no queues or counters of their own, so they always
107 : : * return nullopt; their traffic is only visible in the aggregate.
108 : : */
109 : 2 : virtual std::optional<RuntimeStats> client_stats(ClientId /*client_id*/) const { return std::nullopt; }
110 : :
111 : : virtual void reset_stats() = 0;
112 : :
113 : : // Transmission
114 : : //
115 : : // Strategy-aware API (recommended):
116 : : //
117 : : // send_to() / broadcast()
118 : : // Behaviour depends on the configured backpressure strategy:
119 : : // BestEffort — non-blocking; drops data when the target client's queue is full.
120 : : // Reliable — blocks the calling thread until queue pressure is relieved,
121 : : // then enqueues for send_to().
122 : : // broadcast() always performs non-blocking fan-out to connected clients.
123 : : // It does not wait for one slow client to relieve backpressure.
124 : : //
125 : : // Explicit API (escape hatch):
126 : : //
127 : : // try_send_to() / try_broadcast()
128 : : // Always non-blocking and always drops on full queue, regardless of strategy.
129 : : // Rejects payloads that would exceed the current non-blocking queue
130 : : // threshold; payloads below MAX_BUFFER_SIZE can still be rejected when
131 : : // they exceed the current backpressure high-water budget.
132 : : //
133 : : // send_to_blocking()
134 : : // Always blocks until queue pressure is relieved, regardless of strategy.
135 : : // This call may block indefinitely while the server remains active and
136 : : // backpressure does not clear. stop() from another thread is expected to
137 : : // unblock waiting senders.
138 : :
139 : : /**
140 : : * @brief Send to a specific client, honouring the backpressure strategy.
141 : : *
142 : : * BestEffort: non-blocking, drops if the client's send queue is full.
143 : : * Reliable: blocks until queue pressure is relieved, then enqueues.
144 : : *
145 : : * @return true Data was accepted. @return false Dropped or client not found.
146 : : */
147 : : virtual bool send_to(ClientId client_id, std::string_view data) = 0;
148 : :
149 : : /**
150 : : * @brief Send to all connected clients using non-blocking fan-out.
151 : : *
152 : : * Does not wait for slow clients to relieve backpressure. Use send_to_blocking()
153 : : * for strict per-client blocking delivery.
154 : : *
155 : : * @return true At least one client accepted the data.
156 : : */
157 : : virtual bool broadcast(std::string_view data) = 0;
158 : :
159 : : /**
160 : : * @brief Block until queue pressure is relieved, then send to a client. Ignores strategy.
161 : : *
162 : : * This call may block indefinitely while the server remains active and
163 : : * backpressure does not clear. stop() from another thread is expected to unblock
164 : : * waiting senders.
165 : : *
166 : : * @return true Data was accepted. @return false Server stopped while waiting.
167 : : */
168 : : virtual bool send_to_blocking(ClientId client_id, std::string_view data) = 0;
169 : :
170 : : /**
171 : : * @brief Non-blocking send_to that always drops on a full queue, ignoring strategy.
172 : : *
173 : : * Use as an escape hatch when you need drop-on-full behaviour on a Reliable channel.
174 : : * Rejects payloads that would exceed the current non-blocking queue threshold.
175 : : * A payload may be rejected even if it is below MAX_BUFFER_SIZE when it is larger
176 : : * than the current backpressure high-water budget. Use send_to() or
177 : : * send_to_blocking() when Reliable enqueue semantics are required for large
178 : : * payloads.
179 : : *
180 : : * @return true Data was accepted. @return false Dropped or client not found.
181 : : */
182 : : virtual bool try_send_to(ClientId client_id, std::string_view data) = 0;
183 : :
184 : : /**
185 : : * @brief Non-blocking broadcast that always drops on full queues, ignoring strategy.
186 : : *
187 : : * Uses the same non-blocking queue threshold policy as try_send_to().
188 : : *
189 : : * @return true At least one client accepted the data.
190 : : */
191 : : virtual bool try_broadcast(std::string_view data) = 0;
192 : :
193 : : /**
194 : : * @brief Send a line (data + "\n") to all clients, honouring the backpressure strategy.
195 : : */
196 : : virtual bool broadcast_line(std::string_view line) = 0;
197 : :
198 : : /**
199 : : * @brief Send a line (data + "\n") to a specific client, honouring the backpressure strategy.
200 : : */
201 : : virtual bool send_to_line(ClientId client_id, std::string_view line) = 0;
202 : :
203 : : /**
204 : : * @brief Non-blocking broadcast_line that always drops on a full queue, ignoring strategy.
205 : : */
206 : : virtual bool try_broadcast_line(std::string_view line) = 0;
207 : :
208 : : /**
209 : : * @brief Non-blocking send_to_line that always drops on a full queue, ignoring strategy.
210 : : */
211 : : virtual bool try_send_to_line(ClientId client_id, std::string_view line) = 0;
212 : :
213 : : // Event handlers
214 : :
215 : : /**
216 : : * @brief Fires when a connection is accepted.
217 : : *
218 : : * Accepted, not usable. On a TLS server this runs before the handshake, so a
219 : : * client that fails it still produces one on_connect - followed by an
220 : : * on_disconnect, with no on_data in between. The two callbacks stay paired
221 : : * either way, so connection bookkeeping keyed on them does not leak; treat
222 : : * the first byte of data, not this callback, as proof the peer got through.
223 : : */
224 : : virtual ServerInterface& on_connect(ConnectionHandler handler) = 0;
225 : : virtual ServerInterface& on_disconnect(ConnectionHandler handler) = 0;
226 : : virtual ServerInterface& on_data(MessageHandler handler) = 0;
227 : :
228 : : /** @brief Register a callback for batched data reception */
229 : : virtual ServerInterface& on_data_batch(BatchMessageHandler handler) = 0;
230 : :
231 : : virtual ServerInterface& on_error(ErrorHandler handler) = 0;
232 : :
233 : : /**
234 : : * @brief Register a callback to be notified when send queue congestion changes.
235 : : * @param handler Callback receiving the current number of queued bytes.
236 : : *
237 : : * Default implementation is a no-op. Concrete implementations that support
238 : : * backpressure reporting must override this method; otherwise the handler
239 : : * is silently discarded.
240 : : */
241 : 0 : virtual ServerInterface& on_backpressure(std::function<void(size_t)> handler) {
242 [ # # ]: 0 : if (handler) {
243 : 0 : WIRESTEAD_LOG_WARNING("server_interface", "on_backpressure",
244 : : "Backpressure reporting is not supported by this server; handler is discarded.");
245 : : }
246 : 0 : return *this;
247 : : }
248 : :
249 : : /**
250 : : * @brief Set a factory function to create a new framer for each client connection.
251 : : * @param factory Function that returns a unique_ptr to a new framer.
252 : : */
253 : : virtual ServerInterface& framer(FramerFactory factory) = 0;
254 : :
255 : : /**
256 : : * @brief Set a handler for complete messages extracted by the framer.
257 : : * @param handler callback taking MessageContext (where data is the framed payload).
258 : : */
259 : : virtual ServerInterface& on_message(MessageHandler handler) = 0;
260 : :
261 : : /** @brief Register a callback for batched framed message reception */
262 : : virtual ServerInterface& on_message_batch(BatchMessageHandler handler) = 0;
263 : :
264 : : // Management
265 : : virtual ServerInterface& auto_start(bool manage = true) = 0;
266 : : virtual size_t client_count() const = 0;
267 : : virtual std::vector<ClientId> connected_clients() const = 0;
268 : : };
269 : :
270 : : } // namespace wrapper
271 : : } // namespace wirestead
|