starpc 0.52.1 → 0.52.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -8
- package/dist/srpc/client-rpc.d.ts +1 -0
- package/dist/srpc/client-rpc.js +9 -0
- package/dist/srpc/client-rpc.test.d.ts +1 -0
- package/dist/srpc/client-rpc.test.js +50 -0
- package/dist/srpc/common-rpc.d.ts +5 -0
- package/dist/srpc/common-rpc.js +37 -8
- package/dist/srpc/errors.d.ts +2 -0
- package/dist/srpc/errors.js +14 -0
- package/dist/srpc/index.d.ts +1 -1
- package/dist/srpc/index.js +1 -1
- package/echo/Cargo.toml +8 -0
- package/echo/echo_e2e_test.cpp +542 -985
- package/echo/echo_srpc.pb.cpp +3 -5
- package/echo/echo_srpc.pb.go +6 -9
- package/echo/echo_srpc.pb.hpp +23 -3
- package/echo/echo_srpc.pb.rs +61 -26
- package/echo/echo_srpc.py +4 -1
- package/echo/half_close_test.rs +178 -0
- package/echo/integration_server.rs +14 -21
- package/echo/main.rs +28 -48
- package/go.mod +1 -1
- package/go.sum +2 -18
- package/integration/cross-language/cpp-client.cpp +446 -402
- package/integration/cross-language/cpp-pipe-server.cpp +139 -0
- package/integration/cross-language/cpp-pipe_test.go +114 -0
- package/integration/cross-language/cpp-server.cpp +222 -186
- package/mock/mock_srpc.pb.cpp +1 -1
- package/mock/mock_srpc.pb.hpp +1 -1
- package/mock/mock_srpc.pb.rs +10 -9
- package/package.json +1 -1
- package/srpc/CMakeLists.txt +3 -58
- package/srpc/README-cpp.md +69 -0
- package/srpc/accept.go +10 -4
- package/srpc/accept_test.go +25 -0
- package/srpc/build.rs +129 -34
- package/srpc/client-half-close_test.go +152 -0
- package/srpc/client-invoker.go +32 -42
- package/srpc/client-invoker_test.go +53 -0
- package/srpc/client-rpc.cpp +65 -80
- package/srpc/client-rpc.go +32 -33
- package/srpc/client-rpc.hpp +52 -38
- package/srpc/client-rpc.test.ts +67 -0
- package/srpc/client-rpc.ts +10 -0
- package/srpc/client-rpc_test.go +76 -0
- package/srpc/client.cpp +99 -87
- package/srpc/client.hpp +46 -35
- package/srpc/client.rs +78 -38
- package/srpc/client_half_close_test.cc +331 -0
- package/srpc/client_invoker.rs +75 -0
- package/srpc/common-rpc.cpp +186 -146
- package/srpc/common-rpc.go +5 -4
- package/srpc/common-rpc.hpp +119 -89
- package/srpc/common-rpc.ts +43 -8
- package/srpc/common-rpc_test.go +23 -0
- package/srpc/error.rs +19 -2
- package/srpc/errors.hpp +80 -50
- package/srpc/errors.ts +16 -0
- package/srpc/handler.hpp +9 -7
- package/srpc/handler.rs +7 -45
- package/srpc/index.ts +2 -0
- package/srpc/invoker.hpp +82 -55
- package/srpc/invoker.rs +14 -52
- package/srpc/lib.rs +2 -0
- package/srpc/message.hpp +17 -12
- package/srpc/msg-stream.go +1 -0
- package/srpc/msg-stream.hpp +108 -61
- package/srpc/mux.cpp +84 -84
- package/srpc/mux.hpp +48 -38
- package/srpc/mux.rs +4 -4
- package/srpc/net.go +12 -3
- package/srpc/packet.cpp +60 -58
- package/srpc/packet.hpp +35 -20
- package/srpc/packet.rs +8 -27
- package/srpc/rpc.rs +332 -115
- package/srpc/rpc_lifecycle_test.cc +333 -0
- package/srpc/server-cancel_test.go +39 -0
- package/srpc/server-rpc.cpp +83 -73
- package/srpc/server-rpc.go +21 -19
- package/srpc/server-rpc.hpp +56 -37
- package/srpc/server.rs +76 -131
- package/srpc/starpc.hpp +4 -2
- package/srpc/stream.go +4 -0
- package/srpc/stream.hpp +95 -34
- package/srpc/stream.rs +7 -2
- package/srpc/testing.rs +11 -66
- package/srpc/transport.rs +21 -71
- package/srpc/websocket.rs +106 -57
- package/srpc/writer.hpp +46 -32
- package/srpc/yamux.rs +82 -26
- package/srpc/tests/integration_test.rs +0 -495
package/srpc/server-rpc.hpp
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
-
#include <string>
|
|
4
|
-
#include <thread>
|
|
5
|
-
|
|
6
3
|
#include "common-rpc.hpp"
|
|
7
4
|
#include "errors.hpp"
|
|
8
5
|
#include "invoker.hpp"
|
|
9
6
|
#include "msg-stream.hpp"
|
|
10
7
|
#include "writer.hpp"
|
|
11
8
|
|
|
9
|
+
#include <string>
|
|
10
|
+
#include <thread>
|
|
11
|
+
|
|
12
12
|
namespace srpc {
|
|
13
13
|
class Packet;
|
|
14
14
|
class CallStart;
|
|
@@ -16,48 +16,67 @@ class CallStart;
|
|
|
16
16
|
|
|
17
17
|
namespace starpc {
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
/*
|
|
20
|
+
* ServerRPC is the remote's side of one call: it runs the handler the
|
|
21
|
+
* Invoker selects and cancels and joins it on destruction. The Invoker and
|
|
22
|
+
* PacketWriter must outlive it. HandlePacket calls are serialized by the
|
|
23
|
+
* transport; handlers observe cancellation through Stream::StopToken.
|
|
24
|
+
*/
|
|
21
25
|
class ServerRPC : public CommonRPC, public MsgStreamRw {
|
|
22
26
|
public:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
27
|
+
/* ServerRPC serves one call through invoker, writing via writer. */
|
|
28
|
+
ServerRPC(Invoker *invoker, PacketWriter *writer);
|
|
29
|
+
~ServerRPC() override;
|
|
30
|
+
|
|
31
|
+
/* HandlePacketData parses one serialized packet and handles it. */
|
|
32
|
+
Error HandlePacketData(const std::string &data);
|
|
33
|
+
|
|
34
|
+
/* HandlePacket validates and dispatches one parsed packet. */
|
|
35
|
+
Error HandlePacket(const srpc::Packet &msg);
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* HandleCallStart accepts the call's first packet and runs the
|
|
39
|
+
* handler on its own thread.
|
|
40
|
+
*/
|
|
41
|
+
Error HandleCallStart(const srpc::CallStart &pkt);
|
|
42
|
+
|
|
43
|
+
/* MsgStreamRw over the shared call state. */
|
|
44
|
+
Error ReadOne(std::string *out) override
|
|
45
|
+
{
|
|
46
|
+
return CommonRPC::ReadOne(out);
|
|
47
|
+
}
|
|
48
|
+
Error WriteCallData(const std::string &data, bool data_is_zero,
|
|
49
|
+
bool complete, Error err) override
|
|
50
|
+
{
|
|
51
|
+
return CommonRPC::WriteCallData(data, data_is_zero, complete,
|
|
52
|
+
err);
|
|
53
|
+
}
|
|
54
|
+
Error WriteCallCancel() override
|
|
55
|
+
{
|
|
56
|
+
return CommonRPC::WriteCallCancel();
|
|
57
|
+
}
|
|
58
|
+
std::string RemoteErrorMessage() const override
|
|
59
|
+
{
|
|
60
|
+
return CommonRPC::RemoteErrorMessage();
|
|
61
|
+
}
|
|
46
62
|
|
|
47
63
|
private:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
/* InvokeRPC runs the handler and publishes its verdict. */
|
|
65
|
+
void InvokeRPC(const std::string &service_id,
|
|
66
|
+
const std::string &method_id);
|
|
67
|
+
|
|
68
|
+
/* invoker is borrowed; the caller owns it. */
|
|
69
|
+
Invoker *invoker;
|
|
51
70
|
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
/* invoke_thread is last so it stops before the state it reads. */
|
|
72
|
+
std::jthread invoke_thread;
|
|
54
73
|
};
|
|
55
74
|
|
|
56
|
-
|
|
57
|
-
// Matches Go NewServerRPC function in server-rpc.go
|
|
75
|
+
/* NewServerRPC constructs a ServerRPC for one call. */
|
|
58
76
|
inline std::unique_ptr<ServerRPC> NewServerRPC(Invoker *invoker,
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
PacketWriter *writer)
|
|
78
|
+
{
|
|
79
|
+
return std::make_unique<ServerRPC>(invoker, writer);
|
|
61
80
|
}
|
|
62
81
|
|
|
63
82
|
} // namespace starpc
|
package/srpc/server.rs
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
//! The server supports all streaming patterns: unary, client streaming,
|
|
5
5
|
//! server streaming, and bidirectional streaming.
|
|
6
6
|
|
|
7
|
-
use
|
|
8
|
-
use futures::StreamExt;
|
|
7
|
+
use futures::{stream::FuturesUnordered, StreamExt};
|
|
9
8
|
use std::sync::Arc;
|
|
10
9
|
use std::time::Duration;
|
|
11
10
|
use tokio::io::{AsyncRead, AsyncWrite};
|
|
@@ -128,86 +127,48 @@ impl<I: Invoker + 'static> Server<I> {
|
|
|
128
127
|
// Create framed reader.
|
|
129
128
|
let mut framed = FramedRead::new(read_half, PacketCodec::new());
|
|
130
129
|
|
|
131
|
-
// Wait for the first packet
|
|
130
|
+
// Wait for the first packet and take its CallStart body.
|
|
132
131
|
let first_packet = framed.next().await;
|
|
133
|
-
let
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
_ => return Err(Error::ExpectedCallStart),
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
Some(Err(e)) => return Err(e),
|
|
144
|
-
None => return Err(Error::StreamClosed),
|
|
132
|
+
let Some(Ok(packet)) = first_packet else {
|
|
133
|
+
// A decode error ends the stream; a closed transport has no error.
|
|
134
|
+
return first_packet.map_or(Err(Error::StreamClosed), |res| res.map(|_| ()));
|
|
135
|
+
};
|
|
136
|
+
packet.validate()?;
|
|
137
|
+
let Some(Body::CallStart(call_start)) = packet.body else {
|
|
138
|
+
return Err(Error::ExpectedCallStart);
|
|
145
139
|
};
|
|
146
140
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
let service_id = call_start.rpc_service.clone();
|
|
157
|
-
let method_id = call_start.rpc_method.clone();
|
|
158
|
-
|
|
159
|
-
// Create the server RPC.
|
|
160
|
-
let ctx = Context::new();
|
|
161
|
-
let rpc = Arc::new(ServerRpc::from_call_start(ctx, call_start, writer));
|
|
141
|
+
// Retain the call's transport context until this borrowing operation ends.
|
|
142
|
+
let rpc = Arc::new(ServerRpc::from_call_start(
|
|
143
|
+
Context::new(),
|
|
144
|
+
call_start,
|
|
145
|
+
writer,
|
|
146
|
+
));
|
|
147
|
+
let _cancel = rpc.context().cancel_token().drop_guard_ref();
|
|
162
148
|
|
|
163
|
-
//
|
|
164
|
-
let
|
|
165
|
-
let mut read_task = tokio::spawn(async move {
|
|
149
|
+
// The packet reader is a sibling future, so dropping this call cannot detach transport work.
|
|
150
|
+
let read = async {
|
|
166
151
|
while let Some(result) = framed.next().await {
|
|
167
152
|
match result {
|
|
168
153
|
Ok(packet) => {
|
|
169
|
-
if
|
|
154
|
+
if rpc.handle_packet(packet).await.is_err() {
|
|
170
155
|
break;
|
|
171
156
|
}
|
|
172
157
|
}
|
|
173
158
|
Err(_) => break,
|
|
174
159
|
}
|
|
175
160
|
}
|
|
176
|
-
let _ =
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
let stream: Box<dyn Stream> = Box::new(ServerStream { rpc: rpc.clone() });
|
|
181
|
-
let (found, result) = self
|
|
182
|
-
.invoker
|
|
183
|
-
.invoke_method(&service_id, &method_id, stream)
|
|
184
|
-
.await;
|
|
185
|
-
|
|
186
|
-
// Handle the result.
|
|
187
|
-
if !found {
|
|
188
|
-
// Send unimplemented error.
|
|
189
|
-
let _ = rpc.send_error("method not implemented".to_string()).await;
|
|
190
|
-
} else if let Err(e) = result {
|
|
191
|
-
// Send error response.
|
|
192
|
-
let _ = rpc.send_error(e.to_string()).await;
|
|
193
|
-
} else {
|
|
194
|
-
// Close send side on success.
|
|
195
|
-
let _ = rpc.close_send().await;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Explicitly close the RPC to release the writer/transport.
|
|
199
|
-
// This ensures the connection doesn't remain open after the terminal response.
|
|
200
|
-
let _ = rpc.close().await;
|
|
161
|
+
let _ = rpc.handle_stream_close(None).await;
|
|
162
|
+
};
|
|
163
|
+
let invoke = serve_rpc(self.invoker.as_ref(), &rpc);
|
|
164
|
+
tokio::pin!(read, invoke);
|
|
201
165
|
|
|
202
|
-
//
|
|
203
|
-
//
|
|
166
|
+
// Remote closure cancels the same context that the active service receives.
|
|
167
|
+
// After a reply, retain the existing bounded grace period for the peer's transport close.
|
|
204
168
|
tokio::select! {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
_ = tokio::time::sleep(self.config.shutdown_timeout) => {
|
|
209
|
-
// Timeout - abort the read task
|
|
210
|
-
read_task.abort();
|
|
169
|
+
() = &mut read => invoke.await,
|
|
170
|
+
() = &mut invoke => {
|
|
171
|
+
let _ = tokio::time::timeout(self.config.shutdown_timeout, read).await;
|
|
211
172
|
}
|
|
212
173
|
}
|
|
213
174
|
|
|
@@ -252,73 +213,59 @@ impl<I: Invoker + 'static> Server<I> {
|
|
|
252
213
|
.await
|
|
253
214
|
}
|
|
254
215
|
|
|
255
|
-
/// Accepts and handles connections
|
|
256
|
-
///
|
|
257
|
-
///
|
|
258
|
-
/// and spawns a task to handle each one.
|
|
259
|
-
///
|
|
260
|
-
/// Errors from individual connections are reported via the error handler
|
|
261
|
-
/// (if configured) but don't stop the server.
|
|
216
|
+
/// Accepts and handles connections concurrently, draining accepted calls when the listener ends.
|
|
217
|
+
/// Dropping this future drops every active call and cancels its transport context.
|
|
218
|
+
/// Individual connection failures reach the error handler without stopping other calls.
|
|
262
219
|
pub async fn serve<L, T>(&self, mut listener: L) -> Result<()>
|
|
263
220
|
where
|
|
264
221
|
L: futures::Stream<Item = std::io::Result<T>> + Unpin,
|
|
265
222
|
T: AsyncRead + AsyncWrite + Send + Unpin + 'static,
|
|
266
223
|
{
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
224
|
+
// Concurrent call futures remain owned by this invocation, without detached tasks.
|
|
225
|
+
let mut calls = FuturesUnordered::new();
|
|
226
|
+
let mut accepting = true;
|
|
227
|
+
loop {
|
|
228
|
+
tokio::select! {
|
|
229
|
+
connection = listener.next(), if accepting => {
|
|
230
|
+
match connection {
|
|
231
|
+
Some(Ok(stream)) => calls.push(async move {
|
|
232
|
+
if let Err(error) = self.handle_stream(stream).await {
|
|
233
|
+
self.report_error(error);
|
|
234
|
+
}
|
|
235
|
+
}),
|
|
236
|
+
Some(Err(error)) => self.report_error(Error::Io(error)),
|
|
237
|
+
None => accepting = false,
|
|
238
|
+
}
|
|
279
239
|
}
|
|
240
|
+
Some(()) = calls.next(), if !calls.is_empty() => {}
|
|
241
|
+
else => return Ok(()),
|
|
280
242
|
}
|
|
281
243
|
}
|
|
282
|
-
|
|
283
|
-
Ok(())
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/// Creates a clone of the server for spawning tasks.
|
|
287
|
-
pub(crate) fn clone_for_spawn(&self) -> Server<I> {
|
|
288
|
-
Server {
|
|
289
|
-
invoker: self.invoker.clone(),
|
|
290
|
-
config: self.config.clone(),
|
|
291
|
-
error_handler: self.error_handler.clone(),
|
|
292
|
-
}
|
|
293
244
|
}
|
|
294
245
|
}
|
|
295
246
|
|
|
296
|
-
///
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
async fn close(&self) -> Result<()> {
|
|
320
|
-
self.rpc.close().await
|
|
321
|
-
}
|
|
247
|
+
/// Invokes the method a server RPC names and settles the call with the outcome.
|
|
248
|
+
///
|
|
249
|
+
/// An unknown method or a handler error reaches the peer as an error, and a
|
|
250
|
+
/// handler that returns cleanly closes the send side. The RPC is closed last,
|
|
251
|
+
/// which releases its writer.
|
|
252
|
+
pub(crate) async fn serve_rpc<I: Invoker + ?Sized>(invoker: &I, rpc: &Arc<ServerRpc>) {
|
|
253
|
+
// Invoke the method with the RPC as its stream.
|
|
254
|
+
let (found, result) = invoker
|
|
255
|
+
.invoke_method(rpc.service(), rpc.method(), Box::new(rpc.clone()))
|
|
256
|
+
.await;
|
|
257
|
+
|
|
258
|
+
// Report the outcome to the peer; a failed write leaves nothing to settle.
|
|
259
|
+
let _ = if !found {
|
|
260
|
+
rpc.send_error("method not implemented".to_string()).await
|
|
261
|
+
} else if let Err(err) = result {
|
|
262
|
+
rpc.send_error(err.to_string()).await
|
|
263
|
+
} else {
|
|
264
|
+
rpc.close_send().await
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Close the RPC so the connection does not stay open after the terminal response.
|
|
268
|
+
let _ = rpc.close().await;
|
|
322
269
|
}
|
|
323
270
|
|
|
324
271
|
#[cfg(test)]
|
|
@@ -330,10 +277,9 @@ mod tests {
|
|
|
330
277
|
#[tokio::test]
|
|
331
278
|
async fn test_server_config() {
|
|
332
279
|
let mux = Mux::new();
|
|
333
|
-
let server = Server::new(mux)
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
});
|
|
280
|
+
let server = Server::new(mux).with_config(ServerConfig {
|
|
281
|
+
shutdown_timeout: Duration::from_secs(1),
|
|
282
|
+
});
|
|
337
283
|
|
|
338
284
|
assert_eq!(server.config.shutdown_timeout, Duration::from_secs(1));
|
|
339
285
|
}
|
|
@@ -346,10 +292,9 @@ mod tests {
|
|
|
346
292
|
let errors_clone = errors.clone();
|
|
347
293
|
|
|
348
294
|
let mux = Mux::new();
|
|
349
|
-
let server = Server::new(mux)
|
|
350
|
-
.
|
|
351
|
-
|
|
352
|
-
});
|
|
295
|
+
let server = Server::new(mux).with_error_handler(move |e| {
|
|
296
|
+
errors_clone.lock().unwrap().push(e.to_string());
|
|
297
|
+
});
|
|
353
298
|
|
|
354
299
|
// Report an error
|
|
355
300
|
server.report_error(Error::StreamClosed);
|
package/srpc/starpc.hpp
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/*
|
|
4
|
+
* starpc.hpp is the library's umbrella header: include it for the whole
|
|
5
|
+
* hand-written C++ API.
|
|
6
|
+
*/
|
|
5
7
|
|
|
6
8
|
#include "client-rpc.hpp"
|
|
7
9
|
#include "client.hpp"
|
package/srpc/stream.go
CHANGED
|
@@ -17,6 +17,10 @@ type Stream interface {
|
|
|
17
17
|
MsgRecv(msg Message) error
|
|
18
18
|
|
|
19
19
|
// CloseSend signals to the remote that we will no longer send any messages.
|
|
20
|
+
//
|
|
21
|
+
// An error means the signal was not written, usually because the call
|
|
22
|
+
// already ended. MsgRecv still returns every message received before the
|
|
23
|
+
// end, then the call's outcome.
|
|
20
24
|
CloseSend() error
|
|
21
25
|
|
|
22
26
|
// Close closes the stream for reading and writing.
|
package/srpc/stream.hpp
CHANGED
|
@@ -1,61 +1,122 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
-
#include <functional>
|
|
4
|
-
#include <memory>
|
|
5
|
-
|
|
6
3
|
#include "errors.hpp"
|
|
7
4
|
#include "message.hpp"
|
|
8
5
|
|
|
6
|
+
#include <atomic>
|
|
7
|
+
#include <functional>
|
|
8
|
+
#include <memory>
|
|
9
|
+
#include <stop_token>
|
|
10
|
+
#include <string>
|
|
11
|
+
|
|
9
12
|
namespace starpc {
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
/*
|
|
15
|
+
* Stream is one live RPC stream between the caller and the remote. Matches
|
|
16
|
+
* the Go Stream interface in stream.go.
|
|
17
|
+
*/
|
|
13
18
|
class Stream {
|
|
14
19
|
public:
|
|
15
|
-
|
|
20
|
+
virtual ~Stream() = default;
|
|
21
|
+
|
|
22
|
+
/* StopToken interrupts work when the RPC ends or is canceled. */
|
|
23
|
+
virtual std::stop_token StopToken() const
|
|
24
|
+
{
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
16
27
|
|
|
17
|
-
|
|
18
|
-
|
|
28
|
+
/*
|
|
29
|
+
* RemoteErrorMessage returns the diagnostic accompanying
|
|
30
|
+
* Error::RemoteError.
|
|
31
|
+
*/
|
|
32
|
+
virtual std::string RemoteErrorMessage() const
|
|
33
|
+
{
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
19
36
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
virtual Error MsgRecv(Message *msg) = 0;
|
|
37
|
+
/* MsgSend serializes msg and writes it to the remote. */
|
|
38
|
+
virtual Error MsgSend(const Message &msg) = 0;
|
|
23
39
|
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
/*
|
|
41
|
+
* MsgRecv reads the next message from the remote and parses it into
|
|
42
|
+
* msg. Returns EOF_ after the call completes.
|
|
43
|
+
*/
|
|
44
|
+
virtual Error MsgRecv(Message *msg) = 0;
|
|
26
45
|
|
|
27
|
-
|
|
28
|
-
|
|
46
|
+
/*
|
|
47
|
+
* CloseSend half-closes: no further messages will be sent. Once the
|
|
48
|
+
* request is written, the half-close is only a notice: it fails when
|
|
49
|
+
* the call already ended, and MsgRecv then returns the buffered
|
|
50
|
+
* messages followed by the call's outcome.
|
|
51
|
+
*/
|
|
52
|
+
virtual Error CloseSend() = 0;
|
|
53
|
+
|
|
54
|
+
/* Close ends the stream for both reading and writing. */
|
|
55
|
+
virtual Error Close() = 0;
|
|
29
56
|
};
|
|
30
57
|
|
|
31
|
-
|
|
32
|
-
|
|
58
|
+
/*
|
|
59
|
+
* StreamWithClose forwards a Stream and runs an extra callback on the first
|
|
60
|
+
* Close. Matches streamWithClose in stream.go.
|
|
61
|
+
*/
|
|
33
62
|
class StreamWithClose : public Stream {
|
|
34
63
|
public:
|
|
35
|
-
|
|
36
|
-
|
|
64
|
+
/*
|
|
65
|
+
* StreamWithClose borrows inner; it must outlive the wrapper.
|
|
66
|
+
* close_fn runs once, after inner->Close.
|
|
67
|
+
*/
|
|
68
|
+
StreamWithClose(Stream *inner, std::function<Error()> close_fn)
|
|
69
|
+
: inner(inner),
|
|
70
|
+
close_fn(std::move(close_fn))
|
|
71
|
+
{
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Error MsgSend(const Message &msg) override
|
|
75
|
+
{
|
|
76
|
+
return inner->MsgSend(msg);
|
|
77
|
+
}
|
|
78
|
+
Error MsgRecv(Message *msg) override
|
|
79
|
+
{
|
|
80
|
+
return inner->MsgRecv(msg);
|
|
81
|
+
}
|
|
82
|
+
Error CloseSend() override
|
|
83
|
+
{
|
|
84
|
+
return inner->CloseSend();
|
|
85
|
+
}
|
|
86
|
+
std::stop_token StopToken() const override
|
|
87
|
+
{
|
|
88
|
+
return inner->StopToken();
|
|
89
|
+
}
|
|
90
|
+
std::string RemoteErrorMessage() const override
|
|
91
|
+
{
|
|
92
|
+
return inner->RemoteErrorMessage();
|
|
93
|
+
}
|
|
37
94
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
95
|
+
Error Close() override
|
|
96
|
+
{
|
|
97
|
+
if (closed.exchange(true))
|
|
98
|
+
return Error::OK;
|
|
41
99
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
100
|
+
/* Report the inner close even when the callback also fails. */
|
|
101
|
+
Error err = inner->Close();
|
|
102
|
+
Error err2 = close_fn();
|
|
103
|
+
if (err != Error::OK)
|
|
104
|
+
return err;
|
|
105
|
+
return err2;
|
|
106
|
+
}
|
|
49
107
|
|
|
50
108
|
private:
|
|
51
|
-
|
|
52
|
-
|
|
109
|
+
/* inner is borrowed; the caller owns it. */
|
|
110
|
+
Stream *inner;
|
|
111
|
+
std::function<Error()> close_fn;
|
|
112
|
+
std::atomic<bool> closed{false};
|
|
53
113
|
};
|
|
54
114
|
|
|
55
|
-
|
|
115
|
+
/* NewStreamWithClose wraps strm so close_fn runs on the first Close. */
|
|
56
116
|
inline std::unique_ptr<Stream>
|
|
57
|
-
NewStreamWithClose(Stream *strm, std::function<Error()> close_fn)
|
|
58
|
-
|
|
117
|
+
NewStreamWithClose(Stream *strm, std::function<Error()> close_fn)
|
|
118
|
+
{
|
|
119
|
+
return std::make_unique<StreamWithClose>(strm, std::move(close_fn));
|
|
59
120
|
}
|
|
60
121
|
|
|
61
122
|
} // namespace starpc
|
package/srpc/stream.rs
CHANGED
|
@@ -110,7 +110,7 @@ impl Context {
|
|
|
110
110
|
///
|
|
111
111
|
/// - All methods are async and may block waiting for I/O or messages
|
|
112
112
|
/// - `send_bytes` may return `Error::Completed` if `close_send` was already called
|
|
113
|
-
/// - `recv_bytes` returns `Error::StreamClosed` when the remote
|
|
113
|
+
/// - `recv_bytes` returns `Error::StreamClosed` when the remote completes
|
|
114
114
|
/// - `close` cancels the context and releases all resources
|
|
115
115
|
#[async_trait]
|
|
116
116
|
pub trait Stream: Send + Sync {
|
|
@@ -133,8 +133,9 @@ pub trait Stream: Send + Sync {
|
|
|
133
133
|
///
|
|
134
134
|
/// # Errors
|
|
135
135
|
///
|
|
136
|
-
/// - `Error::StreamClosed` if the remote
|
|
136
|
+
/// - `Error::StreamClosed` if the remote completed the call
|
|
137
137
|
/// - `Error::Remote` if the remote sent an error
|
|
138
|
+
/// - `Error::ClosedBeforeCompletion` if the transport closed first
|
|
138
139
|
/// - `Error::Cancelled` if the context was cancelled
|
|
139
140
|
async fn recv_bytes(&self) -> Result<Bytes>;
|
|
140
141
|
|
|
@@ -142,6 +143,10 @@ pub trait Stream: Send + Sync {
|
|
|
142
143
|
///
|
|
143
144
|
/// After calling this, `send_bytes` will return `Error::Completed`.
|
|
144
145
|
/// The receive side remains open until the remote closes.
|
|
146
|
+
///
|
|
147
|
+
/// Once the request is written, the half-close is only a notice. It fails
|
|
148
|
+
/// when the call already ended, and `recv_bytes` then returns the buffered
|
|
149
|
+
/// messages followed by the call's outcome.
|
|
145
150
|
async fn close_send(&self) -> Result<()>;
|
|
146
151
|
|
|
147
152
|
/// Closes both sides of the stream.
|