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/client.rs
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
use async_trait::async_trait;
|
|
8
8
|
use bytes::Bytes;
|
|
9
|
+
use futures::StreamExt as _;
|
|
9
10
|
use prost::Message;
|
|
10
11
|
use std::sync::Arc;
|
|
11
12
|
|
|
@@ -14,8 +15,8 @@ use crate::rpc::{ClientRpc, PacketWriter};
|
|
|
14
15
|
use crate::stream::{Context, Stream, StreamExt};
|
|
15
16
|
use crate::transport::create_packet_channel;
|
|
16
17
|
|
|
17
|
-
///
|
|
18
|
-
pub type PacketReceiver =
|
|
18
|
+
/// Owned incoming packets; dropping this stream releases its transport or nested call.
|
|
19
|
+
pub type PacketReceiver = futures::stream::BoxStream<'static, crate::proto::Packet>;
|
|
19
20
|
|
|
20
21
|
/// Trait for opening streams to a remote server.
|
|
21
22
|
///
|
|
@@ -40,12 +41,8 @@ pub trait Client: Send + Sync {
|
|
|
40
41
|
I: Message + Send + Sync,
|
|
41
42
|
O: Message + Default;
|
|
42
43
|
|
|
43
|
-
/// Opens a new stream for a streaming RPC
|
|
44
|
-
///
|
|
45
|
-
/// # Arguments
|
|
46
|
-
/// * `service` - The service ID
|
|
47
|
-
/// * `method` - The method ID
|
|
48
|
-
/// * `first_msg` - Optional initial message data
|
|
44
|
+
/// Opens a new stream for a streaming RPC, sending `first_msg` as the
|
|
45
|
+
/// initial data when present.
|
|
49
46
|
async fn new_stream(
|
|
50
47
|
&self,
|
|
51
48
|
service: &str,
|
|
@@ -95,38 +92,36 @@ impl<T: OpenStream + 'static> Client for SrpcClient<T> {
|
|
|
95
92
|
writer,
|
|
96
93
|
));
|
|
97
94
|
|
|
98
|
-
//
|
|
95
|
+
// Cancellation and every early return release the directly owned incoming transport.
|
|
96
|
+
let _cancel = ctx.cancel_token().drop_guard_ref();
|
|
99
97
|
rpc.start(Some(Bytes::from(input_data))).await?;
|
|
100
98
|
|
|
101
|
-
//
|
|
102
|
-
let
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if rpc_clone.handle_packet(packet).await.is_err() {
|
|
99
|
+
// Read packets alongside the unary response without a second task or lifetime.
|
|
100
|
+
let read = async {
|
|
101
|
+
while let Some(packet) = receiver.next().await {
|
|
102
|
+
if rpc.handle_packet(packet).await.is_err() {
|
|
106
103
|
break;
|
|
107
104
|
}
|
|
108
105
|
}
|
|
109
|
-
let _ =
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
Ok(output)
|
|
106
|
+
let _ = rpc.handle_stream_close(None).await;
|
|
107
|
+
};
|
|
108
|
+
let response = async {
|
|
109
|
+
// A failed half-close means the call ended; msg_recv reports its outcome.
|
|
110
|
+
let _ = rpc.close_send().await;
|
|
111
|
+
let output: O = rpc.msg_recv().await?;
|
|
112
|
+
|
|
113
|
+
// Process trailing completion before releasing the writer and receive capability.
|
|
114
|
+
let _ = rpc.wait().await;
|
|
115
|
+
let _ = rpc.close().await;
|
|
116
|
+
Ok(output)
|
|
117
|
+
};
|
|
118
|
+
tokio::pin!(read, response);
|
|
119
|
+
|
|
120
|
+
// Transport closure settles the same RPC that the response future is observing.
|
|
121
|
+
tokio::select! {
|
|
122
|
+
output = &mut response => output,
|
|
123
|
+
() = &mut read => response.await,
|
|
124
|
+
}
|
|
130
125
|
}
|
|
131
126
|
|
|
132
127
|
async fn new_stream(
|
|
@@ -154,7 +149,7 @@ impl<T: OpenStream + 'static> Client for SrpcClient<T> {
|
|
|
154
149
|
// Spawn a task to handle incoming packets.
|
|
155
150
|
let rpc_clone = rpc.clone();
|
|
156
151
|
let packet_handler = tokio::spawn(async move {
|
|
157
|
-
while let Some(packet) = receiver.
|
|
152
|
+
while let Some(packet) = receiver.next().await {
|
|
158
153
|
if rpc_clone.handle_packet(packet).await.is_err() {
|
|
159
154
|
break;
|
|
160
155
|
}
|
|
@@ -178,6 +173,15 @@ struct ClientStream {
|
|
|
178
173
|
packet_handler: tokio::sync::Mutex<Option<tokio::task::JoinHandle<()>>>,
|
|
179
174
|
}
|
|
180
175
|
|
|
176
|
+
impl Drop for ClientStream {
|
|
177
|
+
fn drop(&mut self) {
|
|
178
|
+
self.rpc.context().cancel();
|
|
179
|
+
if let Some(handler) = self.packet_handler.get_mut().take() {
|
|
180
|
+
handler.abort();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
181
185
|
#[async_trait]
|
|
182
186
|
impl Stream for ClientStream {
|
|
183
187
|
fn context(&self) -> &Context {
|
|
@@ -199,9 +203,12 @@ impl Stream for ClientStream {
|
|
|
199
203
|
async fn close(&self) -> Result<()> {
|
|
200
204
|
let _ = self.rpc.close().await;
|
|
201
205
|
// Abort the background packet handler to ensure cleanup.
|
|
202
|
-
|
|
206
|
+
let mut handler = self.packet_handler.lock().await;
|
|
207
|
+
if let Some(handle) = handler.as_mut() {
|
|
203
208
|
handle.abort();
|
|
209
|
+
let _ = handle.await;
|
|
204
210
|
}
|
|
211
|
+
handler.take();
|
|
205
212
|
Ok(())
|
|
206
213
|
}
|
|
207
214
|
}
|
|
@@ -309,7 +316,12 @@ mod tests {
|
|
|
309
316
|
(
|
|
310
317
|
Self {
|
|
311
318
|
writer: Arc::new(MockWriter::new()),
|
|
312
|
-
receiver: Mutex::new(Some(
|
|
319
|
+
receiver: Mutex::new(Some(Box::pin(futures::stream::unfold(
|
|
320
|
+
rx,
|
|
321
|
+
|mut receiver| async move {
|
|
322
|
+
receiver.recv().await.map(|packet| (packet, receiver))
|
|
323
|
+
},
|
|
324
|
+
)))),
|
|
313
325
|
},
|
|
314
326
|
tx,
|
|
315
327
|
)
|
|
@@ -340,6 +352,9 @@ mod tests {
|
|
|
340
352
|
.unwrap();
|
|
341
353
|
|
|
342
354
|
assert!(!stream.context().is_cancelled());
|
|
355
|
+
let context = stream.context().clone();
|
|
356
|
+
drop(stream);
|
|
357
|
+
assert!(context.is_cancelled());
|
|
343
358
|
}
|
|
344
359
|
|
|
345
360
|
#[tokio::test]
|
|
@@ -357,4 +372,29 @@ mod tests {
|
|
|
357
372
|
let result2 = opener.open_stream().await;
|
|
358
373
|
assert!(matches!(result2, Err(Error::StreamClosed)));
|
|
359
374
|
}
|
|
375
|
+
|
|
376
|
+
#[tokio::test]
|
|
377
|
+
async fn malformed_unary_response_releases_receiver() {
|
|
378
|
+
#[derive(Clone, PartialEq, prost::Message)]
|
|
379
|
+
struct Reply {
|
|
380
|
+
#[prost(string, tag = "1")]
|
|
381
|
+
text: String,
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
let (opener, sender) = MockOpener::new();
|
|
385
|
+
let client = SrpcClient::new(opener);
|
|
386
|
+
sender
|
|
387
|
+
.send(crate::packet::new_call_data_full(
|
|
388
|
+
Some(Bytes::from_static(&[0xff])),
|
|
389
|
+
false,
|
|
390
|
+
None,
|
|
391
|
+
))
|
|
392
|
+
.await
|
|
393
|
+
.unwrap();
|
|
394
|
+
let result: Result<Reply> = client.exec_call("test", "call", &Reply::default()).await;
|
|
395
|
+
assert!(result.is_err());
|
|
396
|
+
tokio::time::timeout(std::time::Duration::from_secs(2), sender.closed())
|
|
397
|
+
.await
|
|
398
|
+
.expect("failed unary call retained its packet receiver");
|
|
399
|
+
}
|
|
360
400
|
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
//go:build deps_only
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Half-close checks for the generated C++ callers. Once the request is
|
|
5
|
+
* written, the half-close is only a notice: a caller that half-closes after
|
|
6
|
+
* the remote ended the call still reads the buffered responses and the
|
|
7
|
+
* call's outcome.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include "echo/echo_srpc.pb.hpp"
|
|
11
|
+
#include "srpc/rpcproto.pb.h"
|
|
12
|
+
#include "srpc/server-rpc.hpp"
|
|
13
|
+
|
|
14
|
+
#include <cstdlib>
|
|
15
|
+
#include <iostream>
|
|
16
|
+
#include <latch>
|
|
17
|
+
|
|
18
|
+
namespace {
|
|
19
|
+
using starpc::Error;
|
|
20
|
+
|
|
21
|
+
const char *body = "hello world via starpc half-close test";
|
|
22
|
+
|
|
23
|
+
void Check(bool value, const char *message)
|
|
24
|
+
{
|
|
25
|
+
if (value)
|
|
26
|
+
return;
|
|
27
|
+
std::cerr << message << '\n';
|
|
28
|
+
std::exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* OnceEchoServer answers each streaming call once and completes it.
|
|
33
|
+
*/
|
|
34
|
+
class OnceEchoServer final : public echo::SRPCEchoerServer {
|
|
35
|
+
public:
|
|
36
|
+
Error Echo(const echo::EchoMsg &req, echo::EchoMsg *resp) override
|
|
37
|
+
{
|
|
38
|
+
*resp = req;
|
|
39
|
+
return Error::OK;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Error
|
|
43
|
+
EchoServerStream(const echo::EchoMsg &req,
|
|
44
|
+
echo::SRPCEchoer_EchoServerStreamStream *strm) override
|
|
45
|
+
{
|
|
46
|
+
return strm->Send(req);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/*
|
|
50
|
+
* EchoClientStream replies to the first message without waiting for
|
|
51
|
+
* the half-close.
|
|
52
|
+
*/
|
|
53
|
+
Error EchoClientStream(echo::SRPCEchoer_EchoClientStreamStream *strm,
|
|
54
|
+
echo::EchoMsg *resp) override
|
|
55
|
+
{
|
|
56
|
+
return strm->Recv(resp);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Error EchoBidiStream(echo::SRPCEchoer_EchoBidiStreamStream *) override
|
|
60
|
+
{
|
|
61
|
+
return Error::Unimplemented;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
Error RpcStream(echo::SRPCEchoer_RpcStreamStream *) override
|
|
65
|
+
{
|
|
66
|
+
return Error::Unimplemented;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
Error DoNothing(const google::protobuf::Empty &,
|
|
70
|
+
google::protobuf::Empty *) override
|
|
71
|
+
{
|
|
72
|
+
return Error::OK;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/* FuncWriter forwards packets and the transport close to callbacks. */
|
|
77
|
+
class FuncWriter final : public starpc::PacketWriter {
|
|
78
|
+
public:
|
|
79
|
+
FuncWriter(std::function<Error(const srpc::Packet &)> write,
|
|
80
|
+
std::function<void()> close)
|
|
81
|
+
: write(std::move(write)),
|
|
82
|
+
close(std::move(close))
|
|
83
|
+
{
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
Error WritePacket(const srpc::Packet &packet) override
|
|
87
|
+
{
|
|
88
|
+
return write(packet);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Error Close() override
|
|
92
|
+
{
|
|
93
|
+
close();
|
|
94
|
+
return Error::OK;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private:
|
|
98
|
+
std::function<Error(const srpc::Packet &)> write;
|
|
99
|
+
std::function<void()> close;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/*
|
|
103
|
+
* EchoPipe serves OnceEchoServer to one client stream in memory. closed
|
|
104
|
+
* counts down after the client's transport close callback returns.
|
|
105
|
+
*/
|
|
106
|
+
class EchoPipe {
|
|
107
|
+
public:
|
|
108
|
+
EchoPipe()
|
|
109
|
+
{
|
|
110
|
+
auto [handler, err] =
|
|
111
|
+
echo::SRPCRegisterEchoer(mux.get(), &impl);
|
|
112
|
+
Check(err == Error::OK, "register echoer");
|
|
113
|
+
this->handler = std::move(handler);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* OpenStream connects the client stream to the server. */
|
|
117
|
+
starpc::OpenStreamFunc OpenStream()
|
|
118
|
+
{
|
|
119
|
+
return [this](starpc::PacketDataHandler on_packet,
|
|
120
|
+
starpc::CloseHandler on_close) {
|
|
121
|
+
server_writer = std::make_unique<FuncWriter>(
|
|
122
|
+
[on_packet](const srpc::Packet &packet) {
|
|
123
|
+
return on_packet(
|
|
124
|
+
packet.SerializeAsString());
|
|
125
|
+
},
|
|
126
|
+
[this, on_close] {
|
|
127
|
+
on_close(Error::OK);
|
|
128
|
+
closed.count_down();
|
|
129
|
+
});
|
|
130
|
+
server = starpc::NewServerRPC(mux.get(),
|
|
131
|
+
server_writer.get());
|
|
132
|
+
auto client_writer = std::make_unique<FuncWriter>(
|
|
133
|
+
[this](const srpc::Packet &packet) {
|
|
134
|
+
return server->HandlePacket(packet);
|
|
135
|
+
},
|
|
136
|
+
[this] {
|
|
137
|
+
server->HandleStreamClose(Error::OK);
|
|
138
|
+
});
|
|
139
|
+
return std::make_pair(
|
|
140
|
+
std::unique_ptr<starpc::PacketWriter>(
|
|
141
|
+
std::move(client_writer)),
|
|
142
|
+
Error::OK);
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
std::latch closed{1};
|
|
147
|
+
|
|
148
|
+
private:
|
|
149
|
+
OnceEchoServer impl;
|
|
150
|
+
std::unique_ptr<starpc::Mux> mux = starpc::NewMux();
|
|
151
|
+
std::unique_ptr<echo::SRPCEchoerHandler> handler;
|
|
152
|
+
std::unique_ptr<FuncWriter> server_writer;
|
|
153
|
+
std::unique_ptr<starpc::ServerRPC> server;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
* DroppingPipe accepts CallStart, then drops the transport without a
|
|
158
|
+
* response. closed counts down after the client's transport close callback
|
|
159
|
+
* returns.
|
|
160
|
+
*/
|
|
161
|
+
class DroppingPipe {
|
|
162
|
+
public:
|
|
163
|
+
starpc::OpenStreamFunc OpenStream()
|
|
164
|
+
{
|
|
165
|
+
return [this](starpc::PacketDataHandler,
|
|
166
|
+
starpc::CloseHandler on_close) {
|
|
167
|
+
auto writer = std::make_unique<FuncWriter>(
|
|
168
|
+
[this, on_close](const srpc::Packet &) {
|
|
169
|
+
dropper =
|
|
170
|
+
std::jthread([this, on_close] {
|
|
171
|
+
on_close(Error::EOF_);
|
|
172
|
+
closed.count_down();
|
|
173
|
+
});
|
|
174
|
+
return Error::OK;
|
|
175
|
+
},
|
|
176
|
+
[] {});
|
|
177
|
+
return std::make_pair(
|
|
178
|
+
std::unique_ptr<starpc::PacketWriter>(
|
|
179
|
+
std::move(writer)),
|
|
180
|
+
Error::OK);
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
std::latch closed{1};
|
|
185
|
+
|
|
186
|
+
private:
|
|
187
|
+
std::jthread dropper;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/*
|
|
191
|
+
* EndedClient returns each new stream only after its transport has closed,
|
|
192
|
+
* so the generated caller half-closes after the remote already ended the
|
|
193
|
+
* call.
|
|
194
|
+
*/
|
|
195
|
+
class EndedClient final : public starpc::Client {
|
|
196
|
+
public:
|
|
197
|
+
EndedClient(starpc::OpenStreamFunc open_stream, std::latch *closed)
|
|
198
|
+
: client(std::move(open_stream)),
|
|
199
|
+
closed(closed)
|
|
200
|
+
{
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
Error ExecCall(const std::string &service, const std::string &method,
|
|
204
|
+
const starpc::Message &in, starpc::Message *out) override
|
|
205
|
+
{
|
|
206
|
+
return client.ExecCall(service, method, in, out);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
std::pair<std::unique_ptr<starpc::Stream>, Error>
|
|
210
|
+
NewStream(const std::string &service, const std::string &method,
|
|
211
|
+
const starpc::Message *first_msg) override
|
|
212
|
+
{
|
|
213
|
+
auto opened = client.NewStream(service, method, first_msg);
|
|
214
|
+
if (opened.second == Error::OK)
|
|
215
|
+
closed->wait();
|
|
216
|
+
return opened;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private:
|
|
220
|
+
starpc::ClientImpl client;
|
|
221
|
+
std::latch *closed;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/* ServerStreamCompletedBeforeCloseSend reads a reply after remote completion.
|
|
225
|
+
*/
|
|
226
|
+
void ServerStreamCompletedBeforeCloseSend()
|
|
227
|
+
{
|
|
228
|
+
/* Complete the server stream before the generated caller half-closes.
|
|
229
|
+
*/
|
|
230
|
+
EchoPipe pipe;
|
|
231
|
+
EndedClient client(pipe.OpenStream(), &pipe.closed);
|
|
232
|
+
echo::SRPCEchoerClientImpl echoer(&client);
|
|
233
|
+
echo::EchoMsg req;
|
|
234
|
+
req.set_body(body);
|
|
235
|
+
auto [strm, err] = echoer.EchoServerStream(req);
|
|
236
|
+
Check(err == Error::OK,
|
|
237
|
+
"server stream discarded after remote completion");
|
|
238
|
+
|
|
239
|
+
/* Read the buffered response, then the clean completion. */
|
|
240
|
+
echo::EchoMsg msg;
|
|
241
|
+
Check(strm->Recv(&msg) == Error::OK, "buffered response lost");
|
|
242
|
+
Check(msg.body() == body, "response body");
|
|
243
|
+
Check(strm->Recv(&msg) == Error::EOF_, "expected EOF after completion");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* ClientStreamCompletedBeforeCloseSend reads a reply after a failed half-close.
|
|
247
|
+
*/
|
|
248
|
+
void ClientStreamCompletedBeforeCloseSend()
|
|
249
|
+
{
|
|
250
|
+
EchoPipe pipe;
|
|
251
|
+
auto client = starpc::NewClient(pipe.OpenStream());
|
|
252
|
+
echo::SRPCEchoerClientImpl echoer(client.get());
|
|
253
|
+
auto [strm, err] = echoer.EchoClientStream();
|
|
254
|
+
Check(err == Error::OK, "open client stream");
|
|
255
|
+
|
|
256
|
+
/* Send the one request and wait for the call to end. */
|
|
257
|
+
echo::EchoMsg req;
|
|
258
|
+
req.set_body(body);
|
|
259
|
+
Check(strm->Send(req) == Error::OK, "send request");
|
|
260
|
+
pipe.closed.wait();
|
|
261
|
+
|
|
262
|
+
/* The half-close fails, but the reply is still readable. */
|
|
263
|
+
echo::EchoMsg msg;
|
|
264
|
+
Check(strm->CloseAndRecv(&msg) == Error::OK, "buffered reply lost");
|
|
265
|
+
Check(msg.body() == body, "reply body");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/* ServerStreamTransportFailureBeforeCloseSend reports a dropped transport. */
|
|
269
|
+
void ServerStreamTransportFailureBeforeCloseSend()
|
|
270
|
+
{
|
|
271
|
+
DroppingPipe pipe;
|
|
272
|
+
EndedClient client(pipe.OpenStream(), &pipe.closed);
|
|
273
|
+
echo::SRPCEchoerClientImpl echoer(&client);
|
|
274
|
+
echo::EchoMsg req;
|
|
275
|
+
req.set_body(body);
|
|
276
|
+
auto [strm, err] = echoer.EchoServerStream(req);
|
|
277
|
+
Check(err == Error::OK,
|
|
278
|
+
"server stream discarded after transport failure");
|
|
279
|
+
|
|
280
|
+
/* Recv reports that the call ended without a verdict. */
|
|
281
|
+
echo::EchoMsg msg;
|
|
282
|
+
Check(strm->Recv(&msg) == Error::ClosedBeforeCompletion,
|
|
283
|
+
"expected ClosedBeforeCompletion");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/*
|
|
287
|
+
* FailedHalfCloseKeepsPendingCompletion keeps the transport's buffered reply
|
|
288
|
+
* when the half-close write fails.
|
|
289
|
+
*/
|
|
290
|
+
void FailedHalfCloseKeepsPendingCompletion()
|
|
291
|
+
{
|
|
292
|
+
/*
|
|
293
|
+
* The half-close write fails while the transport still holds the
|
|
294
|
+
* reply and completion it read but has not delivered.
|
|
295
|
+
*/
|
|
296
|
+
starpc::ClientRPC rpc("test", "call");
|
|
297
|
+
FuncWriter writer(
|
|
298
|
+
[](const srpc::Packet &packet) {
|
|
299
|
+
return packet.has_call_data() ? Error::Canceled
|
|
300
|
+
: Error::OK;
|
|
301
|
+
},
|
|
302
|
+
[] {});
|
|
303
|
+
Check(rpc.Start(&writer, false, "") == Error::OK, "start");
|
|
304
|
+
auto strm = starpc::NewMsgStream(&rpc, [&] { rpc.Close(); });
|
|
305
|
+
Check(strm->CloseSend() == Error::Canceled, "half-close write fails");
|
|
306
|
+
|
|
307
|
+
/* The transport delivers what it read, then closes. */
|
|
308
|
+
srpc::CallData reply;
|
|
309
|
+
reply.set_data("reply");
|
|
310
|
+
reply.set_complete(true);
|
|
311
|
+
Check(rpc.HandleCallData(reply) == Error::OK,
|
|
312
|
+
"deliver pending completion");
|
|
313
|
+
rpc.HandleStreamClose(Error::OK);
|
|
314
|
+
|
|
315
|
+
std::string data;
|
|
316
|
+
Check(rpc.ReadOne(&data) == Error::OK && data == "reply",
|
|
317
|
+
"pending reply lost");
|
|
318
|
+
Check(rpc.ReadOne(&data) == Error::EOF_,
|
|
319
|
+
"expected EOF after completion");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
} // namespace
|
|
323
|
+
|
|
324
|
+
int main()
|
|
325
|
+
{
|
|
326
|
+
ServerStreamCompletedBeforeCloseSend();
|
|
327
|
+
ClientStreamCompletedBeforeCloseSend();
|
|
328
|
+
ServerStreamTransportFailureBeforeCloseSend();
|
|
329
|
+
FailedHalfCloseKeepsPendingCompletion();
|
|
330
|
+
std::cout << "Half-close checks passed\n";
|
|
331
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//! Proxy invocations through an existing client while retaining both directions.
|
|
2
|
+
|
|
3
|
+
use async_trait::async_trait;
|
|
4
|
+
|
|
5
|
+
use crate::{Client, Error, Invoker, Result, Stream};
|
|
6
|
+
|
|
7
|
+
/// Routes incoming methods through one client, preserving the remote terminal result.
|
|
8
|
+
pub struct ClientInvoker<C> {
|
|
9
|
+
client: C,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
impl<C: Client> ClientInvoker<C> {
|
|
13
|
+
/// Wraps a client whose transport and routing remain under its existing owner.
|
|
14
|
+
pub fn new(client: C) -> Self {
|
|
15
|
+
Self { client }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#[async_trait]
|
|
20
|
+
impl<C: Client> Invoker for ClientInvoker<C> {
|
|
21
|
+
async fn invoke_method(
|
|
22
|
+
&self,
|
|
23
|
+
service: &str,
|
|
24
|
+
method: &str,
|
|
25
|
+
stream: Box<dyn Stream>,
|
|
26
|
+
) -> (bool, Result<()>) {
|
|
27
|
+
// Opening a remote stream remains within the incoming call's cancellation.
|
|
28
|
+
let remote = tokio::select! {
|
|
29
|
+
() = stream.context().cancelled() => return (true, Err(Error::Cancelled)),
|
|
30
|
+
remote = self.client.new_stream(service, method, None) => match remote {
|
|
31
|
+
Ok(remote) => remote,
|
|
32
|
+
Err(error) => return (true, Err(error)),
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Request forwarding may half-close before the remote sends its result.
|
|
37
|
+
let requests = async {
|
|
38
|
+
match forward_messages(stream.as_ref(), remote.as_ref()).await {
|
|
39
|
+
Ok(()) => remote.close_send().await,
|
|
40
|
+
Err(error) => {
|
|
41
|
+
let _ = remote.close().await;
|
|
42
|
+
Err(error)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
let responses = forward_messages(remote.as_ref(), stream.as_ref());
|
|
47
|
+
tokio::pin!(requests, responses);
|
|
48
|
+
|
|
49
|
+
// A remote result settles the call even if its caller has not half-closed.
|
|
50
|
+
let exchange = async {
|
|
51
|
+
tokio::select! {
|
|
52
|
+
result = &mut responses => result,
|
|
53
|
+
_ = &mut requests => responses.await,
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
let result = tokio::select! {
|
|
57
|
+
result = exchange => result,
|
|
58
|
+
() = stream.context().cancelled() => Err(Error::Cancelled),
|
|
59
|
+
};
|
|
60
|
+
let _ = remote.close().await;
|
|
61
|
+
(true, result)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Copies every message, including empty protobufs, until the source half-closes.
|
|
66
|
+
async fn forward_messages(source: &dyn Stream, target: &dyn Stream) -> Result<()> {
|
|
67
|
+
loop {
|
|
68
|
+
let data = match source.recv_bytes().await {
|
|
69
|
+
Ok(data) => data,
|
|
70
|
+
Err(Error::StreamClosed) => return Ok(()),
|
|
71
|
+
Err(error) => return Err(error),
|
|
72
|
+
};
|
|
73
|
+
target.send_bytes(data).await?;
|
|
74
|
+
}
|
|
75
|
+
}
|