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.
Files changed (91) hide show
  1. package/README.md +37 -8
  2. package/dist/srpc/client-rpc.d.ts +1 -0
  3. package/dist/srpc/client-rpc.js +9 -0
  4. package/dist/srpc/client-rpc.test.d.ts +1 -0
  5. package/dist/srpc/client-rpc.test.js +50 -0
  6. package/dist/srpc/common-rpc.d.ts +5 -0
  7. package/dist/srpc/common-rpc.js +37 -8
  8. package/dist/srpc/errors.d.ts +2 -0
  9. package/dist/srpc/errors.js +14 -0
  10. package/dist/srpc/index.d.ts +1 -1
  11. package/dist/srpc/index.js +1 -1
  12. package/echo/Cargo.toml +8 -0
  13. package/echo/echo_e2e_test.cpp +542 -985
  14. package/echo/echo_srpc.pb.cpp +3 -5
  15. package/echo/echo_srpc.pb.go +6 -9
  16. package/echo/echo_srpc.pb.hpp +23 -3
  17. package/echo/echo_srpc.pb.rs +61 -26
  18. package/echo/echo_srpc.py +4 -1
  19. package/echo/half_close_test.rs +178 -0
  20. package/echo/integration_server.rs +14 -21
  21. package/echo/main.rs +28 -48
  22. package/go.mod +1 -1
  23. package/go.sum +2 -18
  24. package/integration/cross-language/cpp-client.cpp +446 -402
  25. package/integration/cross-language/cpp-pipe-server.cpp +139 -0
  26. package/integration/cross-language/cpp-pipe_test.go +114 -0
  27. package/integration/cross-language/cpp-server.cpp +222 -186
  28. package/mock/mock_srpc.pb.cpp +1 -1
  29. package/mock/mock_srpc.pb.hpp +1 -1
  30. package/mock/mock_srpc.pb.rs +10 -9
  31. package/package.json +1 -1
  32. package/srpc/CMakeLists.txt +3 -58
  33. package/srpc/README-cpp.md +69 -0
  34. package/srpc/accept.go +10 -4
  35. package/srpc/accept_test.go +25 -0
  36. package/srpc/build.rs +129 -34
  37. package/srpc/client-half-close_test.go +152 -0
  38. package/srpc/client-invoker.go +32 -42
  39. package/srpc/client-invoker_test.go +53 -0
  40. package/srpc/client-rpc.cpp +65 -80
  41. package/srpc/client-rpc.go +32 -33
  42. package/srpc/client-rpc.hpp +52 -38
  43. package/srpc/client-rpc.test.ts +67 -0
  44. package/srpc/client-rpc.ts +10 -0
  45. package/srpc/client-rpc_test.go +76 -0
  46. package/srpc/client.cpp +99 -87
  47. package/srpc/client.hpp +46 -35
  48. package/srpc/client.rs +78 -38
  49. package/srpc/client_half_close_test.cc +331 -0
  50. package/srpc/client_invoker.rs +75 -0
  51. package/srpc/common-rpc.cpp +186 -146
  52. package/srpc/common-rpc.go +5 -4
  53. package/srpc/common-rpc.hpp +119 -89
  54. package/srpc/common-rpc.ts +43 -8
  55. package/srpc/common-rpc_test.go +23 -0
  56. package/srpc/error.rs +19 -2
  57. package/srpc/errors.hpp +80 -50
  58. package/srpc/errors.ts +16 -0
  59. package/srpc/handler.hpp +9 -7
  60. package/srpc/handler.rs +7 -45
  61. package/srpc/index.ts +2 -0
  62. package/srpc/invoker.hpp +82 -55
  63. package/srpc/invoker.rs +14 -52
  64. package/srpc/lib.rs +2 -0
  65. package/srpc/message.hpp +17 -12
  66. package/srpc/msg-stream.go +1 -0
  67. package/srpc/msg-stream.hpp +108 -61
  68. package/srpc/mux.cpp +84 -84
  69. package/srpc/mux.hpp +48 -38
  70. package/srpc/mux.rs +4 -4
  71. package/srpc/net.go +12 -3
  72. package/srpc/packet.cpp +60 -58
  73. package/srpc/packet.hpp +35 -20
  74. package/srpc/packet.rs +8 -27
  75. package/srpc/rpc.rs +332 -115
  76. package/srpc/rpc_lifecycle_test.cc +333 -0
  77. package/srpc/server-cancel_test.go +39 -0
  78. package/srpc/server-rpc.cpp +83 -73
  79. package/srpc/server-rpc.go +21 -19
  80. package/srpc/server-rpc.hpp +56 -37
  81. package/srpc/server.rs +76 -131
  82. package/srpc/starpc.hpp +4 -2
  83. package/srpc/stream.go +4 -0
  84. package/srpc/stream.hpp +95 -34
  85. package/srpc/stream.rs +7 -2
  86. package/srpc/testing.rs +11 -66
  87. package/srpc/transport.rs +21 -71
  88. package/srpc/websocket.rs +106 -57
  89. package/srpc/writer.hpp +46 -32
  90. package/srpc/yamux.rs +82 -26
  91. package/srpc/tests/integration_test.rs +0 -495
@@ -1,7 +1,7 @@
1
1
  //go:build deps_only
2
2
 
3
3
  // Code generated by protoc-gen-starpc-cpp. DO NOT EDIT.
4
- // protoc-gen-starpc-cpp version: v0.49.21
4
+ // protoc-gen-starpc-cpp version: v0.52.2-0.20260927183858-4d0f9be5aa9d+dirty
5
5
  // source: github.com/aperturerobotics/starpc/echo/echo.proto
6
6
 
7
7
  #include "echo_srpc.pb.hpp"
@@ -17,10 +17,8 @@ std::pair<std::unique_ptr<SRPCEchoer_EchoServerStreamClient>, starpc::Error> SRP
17
17
  if (err != starpc::Error::OK) {
18
18
  return {nullptr, err};
19
19
  }
20
- err = strm->CloseSend();
21
- if (err != starpc::Error::OK) {
22
- return {nullptr, err};
23
- }
20
+ // A failed half-close means the call ended; MsgRecv reports its outcome.
21
+ (void)strm->CloseSend();
24
22
  return {std::make_unique<SRPCEchoer_EchoServerStreamClient>(std::move(strm)), starpc::Error::OK};
25
23
  }
26
24
 
@@ -63,9 +63,8 @@ func (c *srpcEchoerClient) EchoServerStream(ctx context.Context, in *EchoMsg) (S
63
63
  return nil, err
64
64
  }
65
65
  strm := &srpcEchoer_EchoServerStreamClient{stream}
66
- if err := strm.CloseSend(); err != nil {
67
- return nil, err
68
- }
66
+ // A failed half-close means the call ended; MsgRecv reports its outcome.
67
+ _ = strm.CloseSend()
69
68
  return strm, nil
70
69
  }
71
70
 
@@ -118,9 +117,8 @@ func (x *srpcEchoer_EchoClientStreamClient) Send(m *EchoMsg) error {
118
117
  }
119
118
 
120
119
  func (x *srpcEchoer_EchoClientStreamClient) CloseAndRecv() (*EchoMsg, error) {
121
- if err := x.CloseSend(); err != nil {
122
- return nil, err
123
- }
120
+ // A failed half-close means the call ended; MsgRecv reports its outcome.
121
+ _ = x.CloseSend()
124
122
  m := new(EchoMsg)
125
123
  if err := x.MsgRecv(m); err != nil {
126
124
  return nil, err
@@ -129,9 +127,8 @@ func (x *srpcEchoer_EchoClientStreamClient) CloseAndRecv() (*EchoMsg, error) {
129
127
  }
130
128
 
131
129
  func (x *srpcEchoer_EchoClientStreamClient) CloseAndMsgRecv(m *EchoMsg) error {
132
- if err := x.CloseSend(); err != nil {
133
- return err
134
- }
130
+ // A failed half-close means the call ended; MsgRecv reports its outcome.
131
+ _ = x.CloseSend()
135
132
  return x.MsgRecv(m)
136
133
  }
137
134
 
@@ -1,7 +1,7 @@
1
1
  //go:build deps_only && cgo
2
2
 
3
3
  // Code generated by protoc-gen-starpc-cpp. DO NOT EDIT.
4
- // protoc-gen-starpc-cpp version: v0.49.21
4
+ // protoc-gen-starpc-cpp version: v0.52.2-0.20260927183858-4d0f9be5aa9d+dirty
5
5
  // source: github.com/aperturerobotics/starpc/echo/echo.proto
6
6
 
7
7
  #pragma once
@@ -146,6 +146,8 @@ class SRPCEchoer_EchoServerStreamClient {
146
146
 
147
147
  starpc::Error CloseSend() { return strm_->CloseSend(); }
148
148
  starpc::Error Close() { return strm_->Close(); }
149
+ std::stop_token StopToken() const { return strm_->StopToken(); }
150
+ std::string RemoteErrorMessage() const { return strm_->RemoteErrorMessage(); }
149
151
 
150
152
  private:
151
153
  std::unique_ptr<starpc::Stream> strm_;
@@ -155,6 +157,9 @@ class SRPCEchoer_EchoServerStreamClient {
155
157
  class SRPCEchoer_EchoServerStreamStream {
156
158
  public:
157
159
  explicit SRPCEchoer_EchoServerStreamStream(starpc::Stream* strm) : strm_(strm) {}
160
+ std::stop_token StopToken() const { return strm_->StopToken(); }
161
+ starpc::Error Close() { return strm_->Close(); }
162
+ starpc::Error CloseSend() { return strm_->CloseSend(); }
158
163
 
159
164
  starpc::Error Send(const echo::EchoMsg& msg) {
160
165
  return strm_->MsgSend(msg);
@@ -180,13 +185,15 @@ class SRPCEchoer_EchoClientStreamClient {
180
185
  }
181
186
 
182
187
  starpc::Error CloseAndRecv(echo::EchoMsg* msg) {
183
- starpc::Error err = strm_->CloseSend();
184
- if (err != starpc::Error::OK) return err;
188
+ // A failed half-close means the call ended; MsgRecv reports its outcome.
189
+ (void)strm_->CloseSend();
185
190
  return strm_->MsgRecv(msg);
186
191
  }
187
192
 
188
193
  starpc::Error CloseSend() { return strm_->CloseSend(); }
189
194
  starpc::Error Close() { return strm_->Close(); }
195
+ std::stop_token StopToken() const { return strm_->StopToken(); }
196
+ std::string RemoteErrorMessage() const { return strm_->RemoteErrorMessage(); }
190
197
 
191
198
  private:
192
199
  std::unique_ptr<starpc::Stream> strm_;
@@ -196,6 +203,9 @@ class SRPCEchoer_EchoClientStreamClient {
196
203
  class SRPCEchoer_EchoClientStreamStream {
197
204
  public:
198
205
  explicit SRPCEchoer_EchoClientStreamStream(starpc::Stream* strm) : strm_(strm) {}
206
+ std::stop_token StopToken() const { return strm_->StopToken(); }
207
+ starpc::Error Close() { return strm_->Close(); }
208
+ starpc::Error CloseSend() { return strm_->CloseSend(); }
199
209
 
200
210
  starpc::Error Recv(echo::EchoMsg* msg) {
201
211
  return strm_->MsgRecv(msg);
@@ -220,6 +230,8 @@ class SRPCEchoer_EchoBidiStreamClient {
220
230
 
221
231
  starpc::Error CloseSend() { return strm_->CloseSend(); }
222
232
  starpc::Error Close() { return strm_->Close(); }
233
+ std::stop_token StopToken() const { return strm_->StopToken(); }
234
+ std::string RemoteErrorMessage() const { return strm_->RemoteErrorMessage(); }
223
235
 
224
236
  private:
225
237
  std::unique_ptr<starpc::Stream> strm_;
@@ -229,6 +241,9 @@ class SRPCEchoer_EchoBidiStreamClient {
229
241
  class SRPCEchoer_EchoBidiStreamStream {
230
242
  public:
231
243
  explicit SRPCEchoer_EchoBidiStreamStream(starpc::Stream* strm) : strm_(strm) {}
244
+ std::stop_token StopToken() const { return strm_->StopToken(); }
245
+ starpc::Error Close() { return strm_->Close(); }
246
+ starpc::Error CloseSend() { return strm_->CloseSend(); }
232
247
 
233
248
  starpc::Error Send(const echo::EchoMsg& msg) {
234
249
  return strm_->MsgSend(msg);
@@ -263,6 +278,8 @@ class SRPCEchoer_RpcStreamClient {
263
278
 
264
279
  starpc::Error CloseSend() { return strm_->CloseSend(); }
265
280
  starpc::Error Close() { return strm_->Close(); }
281
+ std::stop_token StopToken() const { return strm_->StopToken(); }
282
+ std::string RemoteErrorMessage() const { return strm_->RemoteErrorMessage(); }
266
283
 
267
284
  private:
268
285
  std::unique_ptr<starpc::Stream> strm_;
@@ -272,6 +289,9 @@ class SRPCEchoer_RpcStreamClient {
272
289
  class SRPCEchoer_RpcStreamStream {
273
290
  public:
274
291
  explicit SRPCEchoer_RpcStreamStream(starpc::Stream* strm) : strm_(strm) {}
292
+ std::stop_token StopToken() const { return strm_->StopToken(); }
293
+ starpc::Error Close() { return strm_->Close(); }
294
+ starpc::Error CloseSend() { return strm_->CloseSend(); }
275
295
 
276
296
  starpc::Error Send(const rpcstream::RpcStreamPacket& msg) {
277
297
  return strm_->MsgSend(msg);
@@ -1,9 +1,9 @@
1
1
  // Code generated by protoc-gen-starpc-rust. DO NOT EDIT.
2
- // protoc-gen-starpc-rust version: v0.49.21
2
+ // protoc-gen-starpc-rust version: (devel)
3
3
  // source: github.com/aperturerobotics/starpc/echo/echo.proto
4
4
 
5
5
  #[allow(unused_imports)]
6
- use starpc::StreamExt;
6
+ use starpc::StreamExt as _;
7
7
 
8
8
  /// Service ID for Echoer.
9
9
  pub const ECHOER_SERVICE_ID: &str = "echo.Echoer";
@@ -62,7 +62,10 @@ pub trait EchoerClient: Send + Sync {
62
62
  /// Echo.
63
63
  async fn echo(&self, request: &EchoMsg) -> starpc::Result<EchoMsg>;
64
64
  /// EchoServerStream.
65
- async fn echo_server_stream(&self, request: &EchoMsg) -> starpc::Result<Box<dyn EchoerEchoServerStreamStream>>;
65
+ async fn echo_server_stream(
66
+ &self,
67
+ request: &EchoMsg,
68
+ ) -> starpc::Result<Box<dyn EchoerEchoServerStreamStream>>;
66
69
  /// EchoClientStream.
67
70
  async fn echo_client_stream(&self) -> starpc::Result<Box<dyn EchoerEchoClientStreamStream>>;
68
71
  /// EchoBidiStream.
@@ -90,27 +93,45 @@ impl<C: starpc::Client + 'static> EchoerClient for EchoerClientImpl<C> {
90
93
  async fn echo(&self, request: &EchoMsg) -> starpc::Result<EchoMsg> {
91
94
  self.client.exec_call("echo.Echoer", "Echo", request).await
92
95
  }
93
- async fn echo_server_stream(&self, request: &EchoMsg) -> starpc::Result<Box<dyn EchoerEchoServerStreamStream>> {
96
+ async fn echo_server_stream(
97
+ &self,
98
+ request: &EchoMsg,
99
+ ) -> starpc::Result<Box<dyn EchoerEchoServerStreamStream>> {
94
100
  use starpc::ProstMessage;
95
101
  let data = request.encode_to_vec();
96
- let stream = self.client.new_stream("echo.Echoer", "EchoServerStream", Some(&data)).await?;
97
- stream.close_send().await?;
102
+ let stream = self
103
+ .client
104
+ .new_stream("echo.Echoer", "EchoServerStream", Some(&data))
105
+ .await?;
106
+ // A failed half-close means the call ended; msg_recv reports its outcome.
107
+ let _ = stream.close_send().await;
98
108
  Ok(Box::new(EchoerEchoServerStreamStreamImpl { stream }))
99
109
  }
100
110
  async fn echo_client_stream(&self) -> starpc::Result<Box<dyn EchoerEchoClientStreamStream>> {
101
- let stream = self.client.new_stream("echo.Echoer", "EchoClientStream", None).await?;
111
+ let stream = self
112
+ .client
113
+ .new_stream("echo.Echoer", "EchoClientStream", None)
114
+ .await?;
102
115
  Ok(Box::new(EchoerEchoClientStreamStreamImpl { stream }))
103
116
  }
104
117
  async fn echo_bidi_stream(&self) -> starpc::Result<Box<dyn EchoerEchoBidiStreamStream>> {
105
- let stream = self.client.new_stream("echo.Echoer", "EchoBidiStream", None).await?;
118
+ let stream = self
119
+ .client
120
+ .new_stream("echo.Echoer", "EchoBidiStream", None)
121
+ .await?;
106
122
  Ok(Box::new(EchoerEchoBidiStreamStreamImpl { stream }))
107
123
  }
108
124
  async fn rpc_stream(&self) -> starpc::Result<Box<dyn EchoerRpcStreamStream>> {
109
- let stream = self.client.new_stream("echo.Echoer", "RpcStream", None).await?;
125
+ let stream = self
126
+ .client
127
+ .new_stream("echo.Echoer", "RpcStream", None)
128
+ .await?;
110
129
  Ok(Box::new(EchoerRpcStreamStreamImpl { stream }))
111
130
  }
112
131
  async fn do_nothing(&self, request: &Empty) -> starpc::Result<Empty> {
113
- self.client.exec_call("echo.Echoer", "DoNothing", request).await
132
+ self.client
133
+ .exec_call("echo.Echoer", "DoNothing", request)
134
+ .await
114
135
  }
115
136
  }
116
137
 
@@ -144,7 +165,8 @@ impl EchoerEchoClientStreamStream for EchoerEchoClientStreamStreamImpl {
144
165
  self.stream.msg_send(msg).await
145
166
  }
146
167
  async fn close_and_recv(&self) -> starpc::Result<EchoMsg> {
147
- self.stream.close_send().await?;
168
+ // A failed half-close means the call ended; msg_recv reports its outcome.
169
+ let _ = self.stream.close_send().await;
148
170
  self.stream.msg_recv().await
149
171
  }
150
172
  }
@@ -193,9 +215,13 @@ impl EchoerRpcStreamStream for EchoerRpcStreamStreamImpl {
193
215
  #[starpc::async_trait]
194
216
  pub trait EchoerServer: Send + Sync {
195
217
  /// Echo.
196
- async fn echo(&self, request: EchoMsg) -> starpc::Result<EchoMsg>;
218
+ async fn echo(&self, context: &starpc::Context, request: EchoMsg) -> starpc::Result<EchoMsg>;
197
219
  /// EchoServerStream.
198
- async fn echo_server_stream(&self, request: EchoMsg, stream: Box<dyn starpc::Stream>) -> starpc::Result<()>;
220
+ async fn echo_server_stream(
221
+ &self,
222
+ request: EchoMsg,
223
+ stream: Box<dyn starpc::Stream>,
224
+ ) -> starpc::Result<()>;
199
225
  /// EchoClientStream.
200
226
  async fn echo_client_stream(&self, stream: &dyn starpc::Stream) -> starpc::Result<EchoMsg>;
201
227
  /// EchoBidiStream.
@@ -203,7 +229,7 @@ pub trait EchoerServer: Send + Sync {
203
229
  /// RpcStream.
204
230
  async fn rpc_stream(&self, stream: Box<dyn starpc::Stream>) -> starpc::Result<()>;
205
231
  /// DoNothing.
206
- async fn do_nothing(&self, request: Empty) -> starpc::Result<Empty>;
232
+ async fn do_nothing(&self, context: &starpc::Context, request: Empty) -> starpc::Result<Empty>;
207
233
  }
208
234
 
209
235
  const ECHOER_METHOD_IDS: &[&str] = &[
@@ -223,7 +249,9 @@ pub struct EchoerHandler<S: EchoerServer> {
223
249
  impl<S: EchoerServer + 'static> EchoerHandler<S> {
224
250
  /// Creates a new handler wrapping the server implementation.
225
251
  pub fn new(server: S) -> Self {
226
- Self { server: std::sync::Arc::new(server) }
252
+ Self {
253
+ server: std::sync::Arc::new(server),
254
+ }
227
255
  }
228
256
 
229
257
  /// Creates a new handler with a shared server.
@@ -246,7 +274,7 @@ impl<S: EchoerServer + 'static> starpc::Invoker for EchoerHandler<S> {
246
274
  Ok(r) => r,
247
275
  Err(e) => return (true, Err(e)),
248
276
  };
249
- match self.server.echo(request).await {
277
+ match <S as EchoerServer>::echo(self.server.as_ref(), stream.context(), request).await {
250
278
  Ok(response) => {
251
279
  if let Err(e) = stream.msg_send(&response).await {
252
280
  return (true, Err(e));
@@ -261,10 +289,16 @@ impl<S: EchoerServer + 'static> starpc::Invoker for EchoerHandler<S> {
261
289
  Ok(r) => r,
262
290
  Err(e) => return (true, Err(e)),
263
291
  };
264
- (true, self.server.echo_server_stream(request, stream).await)
292
+ (
293
+ true,
294
+ <S as EchoerServer>::echo_server_stream(self.server.as_ref(), request, stream)
295
+ .await,
296
+ )
265
297
  }
266
298
  "EchoClientStream" => {
267
- match self.server.echo_client_stream(stream.as_ref()).await {
299
+ match <S as EchoerServer>::echo_client_stream(self.server.as_ref(), stream.as_ref())
300
+ .await
301
+ {
268
302
  Ok(response) => {
269
303
  if let Err(e) = stream.msg_send(&response).await {
270
304
  return (true, Err(e));
@@ -274,18 +308,20 @@ impl<S: EchoerServer + 'static> starpc::Invoker for EchoerHandler<S> {
274
308
  Err(e) => (true, Err(e)),
275
309
  }
276
310
  }
277
- "EchoBidiStream" => {
278
- (true, self.server.echo_bidi_stream(stream).await)
279
- }
280
- "RpcStream" => {
281
- (true, self.server.rpc_stream(stream).await)
282
- }
311
+ "EchoBidiStream" => (
312
+ true,
313
+ <S as EchoerServer>::echo_bidi_stream(self.server.as_ref(), stream).await,
314
+ ),
315
+ "RpcStream" => (
316
+ true,
317
+ <S as EchoerServer>::rpc_stream(self.server.as_ref(), stream).await,
318
+ ),
283
319
  "DoNothing" => {
284
320
  let request: Empty = match stream.msg_recv().await {
285
321
  Ok(r) => r,
286
322
  Err(e) => return (true, Err(e)),
287
323
  };
288
- match self.server.do_nothing(request).await {
324
+ match <S as EchoerServer>::do_nothing(self.server.as_ref(), stream.context(), request).await {
289
325
  Ok(response) => {
290
326
  if let Err(e) = stream.msg_send(&response).await {
291
327
  return (true, Err(e));
@@ -309,4 +345,3 @@ impl<S: EchoerServer + 'static> starpc::Handler for EchoerHandler<S> {
309
345
  ECHOER_METHOD_IDS
310
346
  }
311
347
  }
312
-
package/echo/echo_srpc.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  from collections.abc import AsyncIterable, AsyncIterator
4
5
  from typing import Protocol
5
6
 
@@ -117,7 +118,9 @@ class EchoerClient:
117
118
  try:
118
119
  async for request in requests:
119
120
  await call.send(request.SerializeToString(deterministic=True))
120
- await call.finish()
121
+ # A failed half-close means the call ended; receive reports its outcome.
122
+ with contextlib.suppress(OSError):
123
+ await call.finish()
121
124
  data = await call.receive()
122
125
  if data is None:
123
126
  raise CallProtocolError("missing client-stream response")
@@ -0,0 +1,178 @@
1
+ //! Half-close tests for the generated Rust callers.
2
+ //!
3
+ //! The half-close is only a notice once the request is written: a caller that
4
+ //! half-closes after the remote ended the call must still read the buffered
5
+ //! responses and the call's outcome.
6
+
7
+ #[allow(dead_code)]
8
+ mod gen;
9
+
10
+ use std::sync::Arc;
11
+
12
+ use async_trait::async_trait;
13
+ use futures::StreamExt as _;
14
+ use prost::Message;
15
+ use starpc::client::OpenStream;
16
+ use starpc::testing::{create_pipe_default, create_test_pair, SingleInMemoryOpener};
17
+ use starpc::{Client, Error, Mux, PacketCodec, Result, Server, SrpcClient, Stream, StreamExt};
18
+ use tokio::task::JoinHandle;
19
+ use tokio_util::codec::FramedRead;
20
+
21
+ use gen::{EchoMsg, EchoerClient, EchoerClientImpl, EchoerHandler, EchoerServer};
22
+
23
+ const BODY_TXT: &str = "hello world via starpc half-close test";
24
+
25
+ /// Answers each streaming call once and completes it.
26
+ struct OnceEchoServer;
27
+
28
+ #[async_trait]
29
+ impl EchoerServer for OnceEchoServer {
30
+ async fn echo(&self, _context: &starpc::Context, request: EchoMsg) -> Result<EchoMsg> {
31
+ Ok(request)
32
+ }
33
+
34
+ async fn echo_server_stream(&self, request: EchoMsg, stream: Box<dyn Stream>) -> Result<()> {
35
+ stream.msg_send(&request).await
36
+ }
37
+
38
+ async fn echo_client_stream(&self, stream: &dyn Stream) -> Result<EchoMsg> {
39
+ // Reply to the first message without waiting for the half-close.
40
+ stream.msg_recv().await
41
+ }
42
+
43
+ async fn echo_bidi_stream(&self, _stream: Box<dyn Stream>) -> Result<()> {
44
+ Err(Error::Unimplemented)
45
+ }
46
+
47
+ async fn rpc_stream(&self, _stream: Box<dyn Stream>) -> Result<()> {
48
+ Err(Error::Unimplemented)
49
+ }
50
+
51
+ async fn do_nothing(
52
+ &self,
53
+ _context: &starpc::Context,
54
+ request: gen::Empty,
55
+ ) -> Result<gen::Empty> {
56
+ Ok(request)
57
+ }
58
+ }
59
+
60
+ /// Returns each new stream only after its transport has closed, so the
61
+ /// generated caller half-closes after the remote already ended the call.
62
+ struct EndedClient<T: OpenStream> {
63
+ client: SrpcClient<T>,
64
+ }
65
+
66
+ #[async_trait]
67
+ impl<T: OpenStream + 'static> Client for EndedClient<T> {
68
+ async fn exec_call<I, O>(&self, service: &str, method: &str, input: &I) -> Result<O>
69
+ where
70
+ I: Message + Send + Sync,
71
+ O: Message + Default,
72
+ {
73
+ self.client.exec_call(service, method, input).await
74
+ }
75
+
76
+ async fn new_stream(
77
+ &self,
78
+ service: &str,
79
+ method: &str,
80
+ first_msg: Option<&[u8]>,
81
+ ) -> Result<Box<dyn Stream>> {
82
+ let stream = self.client.new_stream(service, method, first_msg).await?;
83
+ // The transport close handler cancels the context after it settles
84
+ // the call's outcome and closes the writer.
85
+ stream.context().cancelled().await;
86
+ Ok(stream)
87
+ }
88
+ }
89
+
90
+ /// Returns one transport opener and the server task that its caller must join.
91
+ fn serve_once_echo() -> (SingleInMemoryOpener, JoinHandle<Result<()>>) {
92
+ let mux = Arc::new(Mux::new());
93
+ mux.register(Arc::new(EchoerHandler::new(OnceEchoServer)))
94
+ .unwrap();
95
+ let (opener, server_stream) = create_test_pair();
96
+ let server = Server::with_arc(mux);
97
+ let task = tokio::spawn(async move { server.handle_stream(server_stream).await });
98
+ (opener, task)
99
+ }
100
+
101
+ /// Returns a transport that closes after CallStart and the peer task to join after observation.
102
+ fn drop_after_request() -> (SingleInMemoryOpener, JoinHandle<()>) {
103
+ // Use the actual framing boundary to order peer closure after request admission.
104
+ let (client_stream, server_stream) = create_pipe_default();
105
+ let task = tokio::spawn(async move {
106
+ let mut framed = FramedRead::new(server_stream, PacketCodec::new());
107
+ let _ = framed.next().await;
108
+ });
109
+ (SingleInMemoryOpener::new(client_stream), task)
110
+ }
111
+
112
+ #[tokio::test]
113
+ async fn server_stream_completed_before_close_send() {
114
+ // Complete the server stream before the generated caller half-closes.
115
+ let (opener, server) = serve_once_echo();
116
+ let client = EchoerClientImpl::new(EndedClient {
117
+ client: SrpcClient::new(opener),
118
+ });
119
+ let request = EchoMsg {
120
+ body: BODY_TXT.into(),
121
+ };
122
+ let stream = client
123
+ .echo_server_stream(&request)
124
+ .await
125
+ .expect("server stream discarded after remote completion");
126
+
127
+ // Read the buffered response, then the clean completion.
128
+ let msg = stream.recv().await.expect("buffered response lost");
129
+ assert_eq!(msg.body, BODY_TXT);
130
+ let end = stream.recv().await;
131
+ assert!(
132
+ matches!(end, Err(Error::StreamClosed)),
133
+ "expected StreamClosed after completion, got {end:?}"
134
+ );
135
+ server.await.unwrap().unwrap();
136
+ }
137
+
138
+ #[tokio::test]
139
+ async fn client_stream_completed_before_close_send() {
140
+ let (opener, server) = serve_once_echo();
141
+ let client = EchoerClientImpl::new(SrpcClient::new(opener));
142
+ let stream = client.echo_client_stream().await.unwrap();
143
+
144
+ // Send the one request and wait for the call to end.
145
+ let request = EchoMsg {
146
+ body: BODY_TXT.into(),
147
+ };
148
+ stream.send(&request).await.unwrap();
149
+ stream.context().cancelled().await;
150
+
151
+ // The half-close fails, but the reply is still readable.
152
+ let msg = stream.close_and_recv().await.expect("buffered reply lost");
153
+ assert_eq!(msg.body, BODY_TXT);
154
+ server.await.unwrap().unwrap();
155
+ }
156
+
157
+ #[tokio::test]
158
+ async fn server_stream_transport_failure_before_close_send() {
159
+ let (opener, server) = drop_after_request();
160
+ let client = EchoerClientImpl::new(EndedClient {
161
+ client: SrpcClient::new(opener),
162
+ });
163
+ let request = EchoMsg {
164
+ body: BODY_TXT.into(),
165
+ };
166
+ let stream = client
167
+ .echo_server_stream(&request)
168
+ .await
169
+ .expect("server stream discarded after transport failure");
170
+
171
+ // Recv reports that the call ended without a verdict.
172
+ let end = stream.recv().await;
173
+ assert!(
174
+ matches!(end, Err(Error::ClosedBeforeCompletion)),
175
+ "expected ClosedBeforeCompletion, got {end:?}"
176
+ );
177
+ server.await.unwrap();
178
+ }
@@ -1,3 +1,5 @@
1
+ //! Echo transport endpoint used by cross-language integration checks.
2
+
1
3
  #[allow(dead_code)]
2
4
  mod gen;
3
5
 
@@ -9,21 +11,16 @@ use tokio::net::TcpListener;
9
11
 
10
12
  use gen::{EchoMsg, EchoerHandler, EchoerServer, Empty};
11
13
 
14
+ /// Implements each echo call shape through the supplied transport stream.
12
15
  struct EchoServerImpl;
13
16
 
14
17
  #[async_trait]
15
18
  impl EchoerServer for EchoServerImpl {
16
- async fn echo(&self, request: EchoMsg) -> Result<EchoMsg> {
17
- Ok(EchoMsg {
18
- body: request.body,
19
- })
19
+ async fn echo(&self, _context: &starpc::Context, request: EchoMsg) -> Result<EchoMsg> {
20
+ Ok(EchoMsg { body: request.body })
20
21
  }
21
22
 
22
- async fn echo_server_stream(
23
- &self,
24
- request: EchoMsg,
25
- stream: Box<dyn Stream>,
26
- ) -> Result<()> {
23
+ async fn echo_server_stream(&self, request: EchoMsg, stream: Box<dyn Stream>) -> Result<()> {
27
24
  for _ in 0..5 {
28
25
  let response = EchoMsg {
29
26
  body: request.body.clone(),
@@ -34,10 +31,7 @@ impl EchoerServer for EchoServerImpl {
34
31
  }
35
32
 
36
33
  async fn echo_client_stream(&self, stream: &dyn Stream) -> Result<EchoMsg> {
37
- match stream.msg_recv::<EchoMsg>().await {
38
- Ok(msg) => Ok(msg),
39
- Err(e) => Err(e),
40
- }
34
+ stream.msg_recv().await
41
35
  }
42
36
 
43
37
  async fn echo_bidi_stream(&self, stream: Box<dyn Stream>) -> Result<()> {
@@ -63,11 +57,12 @@ impl EchoerServer for EchoServerImpl {
63
57
  Err(Error::Unimplemented)
64
58
  }
65
59
 
66
- async fn do_nothing(&self, _request: Empty) -> Result<Empty> {
60
+ async fn do_nothing(&self, _context: &starpc::Context, _request: Empty) -> Result<Empty> {
67
61
  Ok(Empty {})
68
62
  }
69
63
  }
70
64
 
65
+ /// Publishes a bound address and keeps accepted connections under the server owner.
71
66
  #[tokio::main]
72
67
  async fn main() -> Result<()> {
73
68
  let listener = TcpListener::bind("127.0.0.1:0").await?;
@@ -77,11 +72,9 @@ async fn main() -> Result<()> {
77
72
  let mux = Arc::new(Mux::new());
78
73
  mux.register(Arc::new(EchoerHandler::new(EchoServerImpl)))?;
79
74
 
80
- loop {
81
- let (stream, _) = listener.accept().await?;
82
- let server = Server::with_arc(mux.clone());
83
- tokio::spawn(async move {
84
- let _ = server.handle_stream(stream).await;
85
- });
86
- }
75
+ // The server owns concurrent calls and their cancellation for this listener's lifetime.
76
+ let incoming = futures::stream::unfold(listener, |listener| async move {
77
+ Some((listener.accept().await.map(|(stream, _)| stream), listener))
78
+ });
79
+ Server::with_arc(mux).serve(Box::pin(incoming)).await
87
80
  }