starpc 0.52.0 → 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 (152) hide show
  1. package/README.md +37 -8
  2. package/dist/echo/client-test.d.ts +1 -1
  3. package/dist/echo/client-test.js +110 -2
  4. package/dist/integration/cross-language/tcp-packet-stream.d.ts +3 -0
  5. package/dist/integration/cross-language/tcp-packet-stream.js +112 -0
  6. package/dist/integration/cross-language/tcp-packet-stream.test.d.ts +1 -0
  7. package/dist/integration/cross-language/tcp-packet-stream.test.js +121 -0
  8. package/dist/integration/cross-language/ts-client.js +50 -37
  9. package/dist/integration/cross-language/ts-server.js +1 -36
  10. package/dist/rpcstream/rpcstream.d.ts +5 -1
  11. package/dist/rpcstream/rpcstream.js +75 -28
  12. package/dist/rpcstream/rpcstream.test.d.ts +1 -0
  13. package/dist/rpcstream/rpcstream.test.js +92 -0
  14. package/dist/srpc/client-rpc.d.ts +1 -0
  15. package/dist/srpc/client-rpc.js +9 -0
  16. package/dist/srpc/client-rpc.test.d.ts +1 -0
  17. package/dist/srpc/client-rpc.test.js +50 -0
  18. package/dist/srpc/client.js +18 -4
  19. package/dist/srpc/common-rpc.d.ts +5 -0
  20. package/dist/srpc/common-rpc.js +37 -8
  21. package/dist/srpc/common-rpc.test.js +2 -0
  22. package/dist/srpc/errors.d.ts +2 -0
  23. package/dist/srpc/errors.js +14 -0
  24. package/dist/srpc/index.d.ts +1 -1
  25. package/dist/srpc/index.js +1 -1
  26. package/dist/srpc/packet-codec.test.d.ts +1 -0
  27. package/dist/srpc/packet-codec.test.js +75 -0
  28. package/dist/srpc/packet.d.ts +1 -1
  29. package/dist/srpc/packet.js +11 -1
  30. package/dist/srpc/server.js +19 -6
  31. package/dist/srpc/server.test.js +43 -0
  32. package/dist/srpc/stream.d.ts +4 -1
  33. package/dist/srpc/stream.js +62 -3
  34. package/dist/srpc/stream.test.js +110 -1
  35. package/dist/srpc/termination.d.ts +27 -0
  36. package/dist/srpc/termination.js +56 -0
  37. package/dist/srpc/termination.test.d.ts +1 -0
  38. package/dist/srpc/termination.test.js +24 -0
  39. package/dist/testdata/packet-codec-vectors.json +64 -0
  40. package/echo/Cargo.toml +8 -0
  41. package/echo/client-test.ts +124 -2
  42. package/echo/echo_e2e_test.cpp +542 -985
  43. package/echo/echo_pb2.py +40 -0
  44. package/echo/echo_pb2.pyi +13 -0
  45. package/echo/echo_srpc.pb.cpp +3 -5
  46. package/echo/echo_srpc.pb.go +6 -9
  47. package/echo/echo_srpc.pb.hpp +23 -3
  48. package/echo/echo_srpc.pb.rs +61 -26
  49. package/echo/echo_srpc.py +309 -0
  50. package/echo/echo_srpc.pyi +85 -0
  51. package/echo/half_close_test.rs +178 -0
  52. package/echo/integration_server.rs +14 -21
  53. package/echo/main.rs +28 -48
  54. package/go.mod +3 -3
  55. package/go.sum +6 -8
  56. package/integration/cross-language/cpp-client.cpp +446 -402
  57. package/integration/cross-language/cpp-pipe-server.cpp +139 -0
  58. package/integration/cross-language/cpp-pipe_test.go +114 -0
  59. package/integration/cross-language/cpp-server.cpp +222 -186
  60. package/integration/cross-language/go-client/main.go +79 -3
  61. package/integration/cross-language/python-client.py +146 -0
  62. package/integration/cross-language/python-server.py +140 -0
  63. package/integration/cross-language/run.bash +190 -65
  64. package/integration/cross-language/tcp-packet-stream.test.ts +154 -0
  65. package/integration/cross-language/tcp-packet-stream.ts +121 -0
  66. package/integration/cross-language/ts-client.ts +62 -40
  67. package/integration/cross-language/ts-server.ts +1 -45
  68. package/mock/mock_pb2.py +38 -0
  69. package/mock/mock_pb2.pyi +11 -0
  70. package/mock/mock_srpc.pb.cpp +1 -1
  71. package/mock/mock_srpc.pb.hpp +1 -1
  72. package/mock/mock_srpc.pb.rs +10 -9
  73. package/mock/mock_srpc.py +71 -0
  74. package/mock/mock_srpc.pyi +27 -0
  75. package/package.json +19 -5
  76. package/srpc/CMakeLists.txt +3 -58
  77. package/srpc/README-cpp.md +69 -0
  78. package/srpc/__init__.py +0 -0
  79. package/srpc/accept.go +10 -4
  80. package/srpc/accept_test.go +25 -0
  81. package/srpc/build.rs +129 -34
  82. package/srpc/client-half-close_test.go +152 -0
  83. package/srpc/client-invoker.go +32 -42
  84. package/srpc/client-invoker_test.go +53 -0
  85. package/srpc/client-rpc.cpp +65 -80
  86. package/srpc/client-rpc.go +32 -33
  87. package/srpc/client-rpc.hpp +52 -38
  88. package/srpc/client-rpc.test.ts +67 -0
  89. package/srpc/client-rpc.ts +10 -0
  90. package/srpc/client-rpc_test.go +76 -0
  91. package/srpc/client.cpp +99 -87
  92. package/srpc/client.hpp +46 -35
  93. package/srpc/client.rs +78 -38
  94. package/srpc/client.ts +20 -4
  95. package/srpc/client_half_close_test.cc +331 -0
  96. package/srpc/client_invoker.rs +75 -0
  97. package/srpc/codec.rs +6 -0
  98. package/srpc/common-rpc.cpp +186 -146
  99. package/srpc/common-rpc.go +5 -4
  100. package/srpc/common-rpc.hpp +119 -89
  101. package/srpc/common-rpc.test.ts +2 -0
  102. package/srpc/common-rpc.ts +43 -8
  103. package/srpc/common-rpc_test.go +23 -0
  104. package/srpc/error.rs +19 -2
  105. package/srpc/errors.hpp +80 -50
  106. package/srpc/errors.ts +16 -0
  107. package/srpc/handler.hpp +9 -7
  108. package/srpc/handler.rs +7 -45
  109. package/srpc/index.ts +2 -0
  110. package/srpc/invoker.hpp +82 -55
  111. package/srpc/invoker.rs +14 -52
  112. package/srpc/lib.rs +2 -0
  113. package/srpc/message.hpp +17 -12
  114. package/srpc/msg-stream.go +1 -0
  115. package/srpc/msg-stream.hpp +108 -61
  116. package/srpc/mux.cpp +84 -84
  117. package/srpc/mux.hpp +48 -38
  118. package/srpc/mux.rs +4 -4
  119. package/srpc/net.go +12 -3
  120. package/srpc/packet-codec-vectors_test.go +195 -0
  121. package/srpc/packet-codec.test.ts +139 -0
  122. package/srpc/packet-rw.go +9 -2
  123. package/srpc/packet.cpp +60 -58
  124. package/srpc/packet.hpp +35 -20
  125. package/srpc/packet.rs +8 -27
  126. package/srpc/packet.ts +15 -2
  127. package/srpc/py.typed +0 -0
  128. package/srpc/rpc.rs +332 -115
  129. package/srpc/rpc_lifecycle_test.cc +333 -0
  130. package/srpc/rpcproto_pb2.py +40 -0
  131. package/srpc/rpcproto_pb2.pyi +40 -0
  132. package/srpc/server-cancel_test.go +39 -0
  133. package/srpc/server-rpc.cpp +83 -73
  134. package/srpc/server-rpc.go +21 -19
  135. package/srpc/server-rpc.hpp +56 -37
  136. package/srpc/server.rs +76 -131
  137. package/srpc/server.test.ts +50 -0
  138. package/srpc/server.ts +22 -6
  139. package/srpc/starpc.hpp +4 -2
  140. package/srpc/stream.go +4 -0
  141. package/srpc/stream.hpp +95 -34
  142. package/srpc/stream.rs +7 -2
  143. package/srpc/stream.test.ts +132 -1
  144. package/srpc/stream.ts +65 -9
  145. package/srpc/termination.test.ts +30 -0
  146. package/srpc/termination.ts +70 -0
  147. package/srpc/testing.rs +11 -66
  148. package/srpc/transport.rs +21 -71
  149. package/srpc/websocket.rs +106 -57
  150. package/srpc/writer.hpp +46 -32
  151. package/srpc/yamux.rs +82 -26
  152. package/srpc/tests/integration_test.rs +0 -495
package/srpc/rpc.rs CHANGED
@@ -1,14 +1,16 @@
1
1
  //! RPC state machines for client and server.
2
2
  //!
3
3
  //! This module provides the core RPC state machines that manage the lifecycle
4
- //! of RPC calls, matching the behavior of the Go and TypeScript implementations.
4
+ //! of calls over the shared Go, TypeScript, and Rust wire protocol.
5
+
6
+ use std::sync::{
7
+ atomic::{AtomicBool, Ordering},
8
+ Arc, Mutex,
9
+ };
5
10
 
6
11
  use async_trait::async_trait;
7
12
  use bytes::Bytes;
8
- use std::collections::VecDeque;
9
- use std::sync::atomic::{AtomicBool, Ordering};
10
- use std::sync::Arc;
11
- use tokio::sync::{Mutex, Notify};
13
+ use tokio::sync::{mpsc, Notify};
12
14
 
13
15
  use crate::error::{Error, Result};
14
16
  use crate::packet::{new_call_cancel, new_call_data_full, new_call_start, Validate};
@@ -44,7 +46,6 @@ pub struct CommonRpc {
44
46
  method: String,
45
47
 
46
48
  /// Whether we have completed locally (sent complete/cancel).
47
- /// Note: not guarded by the mutex, uses atomic operations.
48
49
  local_completed: AtomicBool,
49
50
 
50
51
  /// Packet writer.
@@ -53,21 +54,43 @@ pub struct CommonRpc {
53
54
  /// Notification for state changes.
54
55
  notify: Notify,
55
56
 
57
+ /// Bounded incoming data, shared by the packet reader and RPC consumer.
58
+ data: mpsc::Sender<Bytes>,
59
+
56
60
  /// Internal state protected by mutex.
57
61
  state: Mutex<RpcState>,
58
62
  }
59
63
 
60
64
  /// Internal RPC state.
61
65
  struct RpcState {
62
- /// Queue of incoming data messages.
63
- /// Note: messages may be len() == 0 (empty data with data_is_zero).
64
- data_queue: VecDeque<Bytes>,
66
+ /// Incoming messages, including empty payloads, retained until read.
67
+ data: mpsc::Receiver<Bytes>,
68
+
69
+ /// How the incoming side ended, once it has.
70
+ end: Option<RpcEnd>,
71
+ }
72
+
73
+ /// How the incoming side of an RPC ended.
74
+ enum RpcEnd {
75
+ /// The remote completed the call, or the local side closed it.
76
+ Complete,
65
77
 
66
- /// Whether the remote side has closed the stream.
67
- data_closed: bool,
78
+ /// The remote failed or cancelled the call.
79
+ Remote(String),
68
80
 
69
- /// Error from the remote side, if any.
70
- remote_err: Option<String>,
81
+ /// The transport closed without a remote completion or error.
82
+ ClosedBeforeCompletion,
83
+ }
84
+
85
+ impl RpcEnd {
86
+ /// Returns the error a read reports after the queued messages.
87
+ fn error(&self) -> Error {
88
+ match self {
89
+ RpcEnd::Complete => Error::StreamClosed,
90
+ RpcEnd::Remote(err) => Error::Remote(err.clone()),
91
+ RpcEnd::ClosedBeforeCompletion => Error::ClosedBeforeCompletion,
92
+ }
93
+ }
71
94
  }
72
95
 
73
96
  impl CommonRpc {
@@ -78,6 +101,8 @@ impl CommonRpc {
78
101
  method: String,
79
102
  writer: Arc<dyn PacketWriter>,
80
103
  ) -> Self {
104
+ // Retain one unread message; the packet reader waits before accepting another.
105
+ let (data, receiver) = mpsc::channel(1);
81
106
  Self {
82
107
  ctx,
83
108
  service,
@@ -85,10 +110,10 @@ impl CommonRpc {
85
110
  local_completed: AtomicBool::new(false),
86
111
  writer,
87
112
  notify: Notify::new(),
113
+ data,
88
114
  state: Mutex::new(RpcState {
89
- data_queue: VecDeque::new(),
90
- data_closed: false,
91
- remote_err: None,
115
+ data: receiver,
116
+ end: None,
92
117
  }),
93
118
  }
94
119
  }
@@ -113,32 +138,34 @@ impl CommonRpc {
113
138
  self.local_completed.load(Ordering::SeqCst)
114
139
  }
115
140
 
116
- /// Waits for the RPC to finish (remote end closed the stream).
141
+ /// Waits for the remote verdict after preceding payloads have been accepted.
117
142
  ///
118
- /// This matches the Go implementation's `Wait(ctx context.Context) error`.
143
+ /// Streaming consumers must receive data concurrently so the packet reader
144
+ /// can reach completion beyond a full receive channel.
119
145
  pub async fn wait(&self) -> Result<()> {
120
146
  loop {
147
+ // Capture the notification before reading state so a concurrent
148
+ // completion cannot fall between the check and the wait.
149
+ let changed = self.notify.notified();
121
150
  // Check current state
122
151
  {
123
- let state = self.state.lock().await;
152
+ let state = self.state.lock().unwrap();
124
153
 
125
154
  // Check cancellation first - local cancellation takes priority
126
155
  if self.ctx.is_cancelled() {
127
156
  return Err(Error::Cancelled);
128
157
  }
129
158
 
130
- if let Some(ref err) = state.remote_err {
131
- return Err(Error::Remote(err.clone()));
132
- }
133
-
134
- if state.data_closed {
135
- return Ok(());
159
+ match state.end {
160
+ Some(RpcEnd::Complete) => return Ok(()),
161
+ Some(ref end) => return Err(end.error()),
162
+ None => {}
136
163
  }
137
164
  }
138
165
 
139
166
  // Wait for notification or cancellation
140
167
  tokio::select! {
141
- _ = self.notify.notified() => continue,
168
+ _ = changed => continue,
142
169
  _ = self.ctx.cancelled() => return Err(Error::Cancelled),
143
170
  }
144
171
  }
@@ -146,36 +173,43 @@ impl CommonRpc {
146
173
 
147
174
  /// Reads one message from the data queue, blocking until available.
148
175
  ///
149
- /// Returns `Err(Error::StreamClosed)` if the stream ended without a packet.
150
- /// This matches the Go implementation which returns `io.EOF`.
176
+ /// After the queued messages, returns `Err(Error::StreamClosed)` if the
177
+ /// call completed, matching `io.EOF` in the Go implementation, or the
178
+ /// error that ended it.
151
179
  pub async fn read_one(&self) -> Result<Bytes> {
152
180
  loop {
181
+ // notify_waiters preserves notifications from this future's creation.
182
+ let changed = self.notify.notified();
153
183
  // Try to get a message from the queue first, before checking cancellation.
154
184
  // This ensures we drain any pending messages even if the context is cancelled.
155
185
  {
156
- let mut state = self.state.lock().await;
186
+ let mut state = self.state.lock().unwrap();
157
187
 
158
- if let Some(data) = state.data_queue.pop_front() {
188
+ if let Ok(data) = state.data.try_recv() {
159
189
  return Ok(data);
160
190
  }
161
191
 
162
- // Check if the stream is closed (graceful close from remote)
163
- if state.data_closed {
164
- if let Some(ref err) = state.remote_err {
165
- return Err(Error::Remote(err.clone()));
166
- }
167
- return Err(Error::StreamClosed);
192
+ if let Some(ref end) = state.end {
193
+ return Err(end.error());
168
194
  }
169
195
  }
170
196
 
171
197
  // Now check for cancellation - only if no data is available
172
198
  // and the stream isn't properly closed.
173
199
  if self.ctx.is_cancelled() {
174
- // If context cancelled and data not closed, close it now
175
- let mut state = self.state.lock().await;
176
- if !state.data_closed {
177
- state.data_closed = true;
178
- drop(state);
200
+ // Settle cancellation before releasing the writer outside the lock.
201
+ let close = {
202
+ let mut state = self.state.lock().unwrap();
203
+ if state.end.is_none() {
204
+ state.end = Some(RpcEnd::Complete);
205
+ state.data.close();
206
+ true
207
+ } else {
208
+ false
209
+ }
210
+ };
211
+ if close {
212
+ // Cancellation determines the result even if transport close fails.
179
213
  let _ = self.writer.close().await;
180
214
  self.ctx.cancel();
181
215
  self.notify.notify_waiters();
@@ -185,7 +219,7 @@ impl CommonRpc {
185
219
 
186
220
  // Wait for notification or cancellation
187
221
  tokio::select! {
188
- _ = self.notify.notified() => continue,
222
+ _ = changed => continue,
189
223
  _ = self.ctx.cancelled() => {
190
224
  // Loop will handle the cancellation
191
225
  continue;
@@ -194,12 +228,9 @@ impl CommonRpc {
194
228
  }
195
229
  }
196
230
 
197
- /// Writes a CallData packet.
198
- ///
199
- /// # Arguments
200
- /// * `data` - Optional data to send
201
- /// * `complete` - Whether this completes the RPC
202
- /// * `error` - Optional error message
231
+ /// Writes a CallData packet, completing the call when either `complete`
232
+ /// or `error` is set. Returns `Error::Completed` once the call has
233
+ /// already completed locally.
203
234
  pub async fn write_call_data(
204
235
  &self,
205
236
  data: Option<Bytes>,
@@ -242,34 +273,51 @@ impl CommonRpc {
242
273
  self.writer.write_packet(new_call_cancel()).await
243
274
  }
244
275
 
245
- /// Handles an incoming CallData packet.
276
+ /// Accepts incoming data, waiting for the consumer when one message is unread.
277
+ ///
278
+ /// The transport reader retains at most one further packet while waiting.
279
+ /// Cancellation or closing the RPC releases a blocked packet reader. Completion
280
+ /// remains ordered after its data, and control-only packets need no capacity.
246
281
  pub async fn handle_call_data(&self, call_data: CallData) -> Result<()> {
247
- let mut state = self.state.lock().await;
248
-
249
- // Check if already closed
250
- if state.data_closed {
251
- // If the packet is just indicating the call is complete, ignore it
252
- // This matches Go behavior
253
- if call_data.complete {
254
- return Ok(());
255
- }
256
- return Err(Error::Completed);
282
+ // Reserve capacity without holding the state lock needed by the consumer.
283
+ let pending =
284
+ if let Some(data) = decode_optional_data(call_data.data, call_data.data_is_zero) {
285
+ let result = tokio::select! {
286
+ biased;
287
+ result = self.data.reserve() => result,
288
+ () = self.ctx.cancelled() => return Err(Error::Cancelled),
289
+ };
290
+ match result {
291
+ Ok(permit) => Some((data, permit)),
292
+ Err(_) if call_data.complete => return Ok(()),
293
+ Err(_) => return Err(Error::Completed),
294
+ }
295
+ } else {
296
+ None
297
+ };
298
+
299
+ // Closing and packet delivery share the lock, so no data arrives after an end.
300
+ let mut state = self.state.lock().unwrap();
301
+ if state.end.is_some() {
302
+ return if call_data.complete {
303
+ Ok(())
304
+ } else {
305
+ Err(Error::Completed)
306
+ };
257
307
  }
258
-
259
- // Extract data if present
260
- if let Some(data) = decode_optional_data(call_data.data, call_data.data_is_zero) {
261
- state.data_queue.push_back(data);
308
+ if let Some((data, permit)) = pending {
309
+ permit.send(data);
262
310
  }
263
311
 
264
- // Handle completion or error
312
+ // Publish the terminal verdict after its final payload and wake blocked readers.
265
313
  if !call_data.error.is_empty() {
266
- state.remote_err = Some(call_data.error);
267
- state.data_closed = true;
314
+ state.end = Some(RpcEnd::Remote(call_data.error));
268
315
  } else if call_data.complete {
269
- state.data_closed = true;
316
+ state.end = Some(RpcEnd::Complete);
317
+ }
318
+ if state.end.is_some() {
319
+ state.data.close();
270
320
  }
271
-
272
- // Notify waiters
273
321
  drop(state);
274
322
  self.notify.notify_waiters();
275
323
 
@@ -278,22 +326,29 @@ impl CommonRpc {
278
326
 
279
327
  /// Handles a CallCancel packet.
280
328
  pub async fn handle_call_cancel(&self) -> Result<()> {
281
- self.handle_stream_close(Some("cancelled".to_string())).await
329
+ self.handle_stream_close(Some("cancelled".to_string()))
330
+ .await
282
331
  }
283
332
 
284
333
  /// Handles stream close from the transport.
334
+ ///
335
+ /// A clean close with no completion behind it leaves the call without a
336
+ /// verdict, so reads report `Error::ClosedBeforeCompletion` rather than the
337
+ /// clean end of the stream.
285
338
  pub async fn handle_stream_close(&self, err: Option<String>) -> Result<()> {
286
- let mut state = self.state.lock().await;
287
-
288
- if let Some(e) = err {
289
- if state.remote_err.is_none() {
290
- state.remote_err = Some(e);
339
+ // Preserve a verdict already received and release blocked packet producers.
340
+ {
341
+ let mut state = self.state.lock().unwrap();
342
+ if state.end.is_none() {
343
+ state.end = Some(match err {
344
+ Some(err) => RpcEnd::Remote(err),
345
+ None => RpcEnd::ClosedBeforeCompletion,
346
+ });
291
347
  }
348
+ state.data.close();
292
349
  }
293
- state.data_closed = true;
294
-
295
- drop(state);
296
350
 
351
+ // The incoming verdict determines reads even if closing the writer fails.
297
352
  let _ = self.writer.close().await;
298
353
  self.ctx.cancel();
299
354
  self.notify.notify_waiters();
@@ -301,22 +356,28 @@ impl CommonRpc {
301
356
  Ok(())
302
357
  }
303
358
 
304
- /// Closes the RPC, releasing resources.
359
+ /// Ends the call locally and releases its resources.
305
360
  ///
306
- /// This is called internally and handles cleanup.
307
- /// Note: We don't set remote_err here because local cancellation is signaled
308
- /// via ctx.is_cancelled(), which is checked first in wait()/read_one().
309
- async fn close_locked(&self) {
310
- let mut state = self.state.lock().await;
311
- state.data_closed = true;
312
- self.local_completed.store(true, Ordering::SeqCst);
313
- // Don't set remote_err for local closure - rely on ctx.is_cancelled() instead
314
- // to distinguish between local cancellation and remote errors.
315
- drop(state);
361
+ /// A call with no verdict ends as complete, pending waiters and reads
362
+ /// wake, and the writer closes before the context cancels. Returns the
363
+ /// writer's close error; every caller still finds the call ended.
364
+ async fn close_local(&self) -> Result<()> {
365
+ // End data reception and release packet producers before transport shutdown.
366
+ {
367
+ let mut state = self.state.lock().unwrap();
368
+ if state.end.is_none() {
369
+ state.end = Some(RpcEnd::Complete);
370
+ }
371
+ self.local_completed.store(true, Ordering::SeqCst);
372
+ state.data.close();
373
+ }
316
374
 
317
- let _ = self.writer.close().await;
375
+ // Wake observers even when the writer fails to close.
376
+ let closed = self.writer.close().await;
318
377
  self.notify.notify_waiters();
319
378
  self.ctx.cancel();
379
+
380
+ closed
320
381
  }
321
382
  }
322
383
 
@@ -356,7 +417,7 @@ impl ClientRpc {
356
417
  self.common.method()
357
418
  }
358
419
 
359
- /// Waits for the RPC to finish.
420
+ /// Waits for the remote verdict; receive streaming payloads concurrently.
360
421
  pub async fn wait(&self) -> Result<()> {
361
422
  self.common.wait().await
362
423
  }
@@ -429,7 +490,7 @@ impl ClientRpc {
429
490
  let _ = self.common.write_call_cancel().await;
430
491
 
431
492
  // Close resources
432
- self.common.close_locked().await;
493
+ let _ = self.common.close_local().await;
433
494
  }
434
495
  }
435
496
 
@@ -440,9 +501,7 @@ impl Stream for ClientRpc {
440
501
  }
441
502
 
442
503
  async fn send_bytes(&self, data: Bytes) -> Result<()> {
443
- self.common
444
- .write_call_data(Some(data), false, None)
445
- .await
504
+ self.common.write_call_data(Some(data), false, None).await
446
505
  }
447
506
 
448
507
  async fn recv_bytes(&self) -> Result<Bytes> {
@@ -496,7 +555,7 @@ impl ServerRpc {
496
555
  self.common.method()
497
556
  }
498
557
 
499
- /// Waits for the RPC to finish.
558
+ /// Waits for the remote verdict; receive streaming payloads concurrently.
500
559
  pub async fn wait(&self) -> Result<()> {
501
560
  self.common.wait().await
502
561
  }
@@ -536,15 +595,13 @@ impl Stream for ServerRpc {
536
595
  }
537
596
 
538
597
  async fn send_bytes(&self, data: Bytes) -> Result<()> {
539
- self.common
540
- .write_call_data(Some(data), false, None)
541
- .await
598
+ self.common.write_call_data(Some(data), false, None).await
542
599
  }
543
600
 
544
601
  async fn recv_bytes(&self) -> Result<Bytes> {
545
602
  // First check for initial data
546
603
  {
547
- let mut initial = self.initial_data.lock().await;
604
+ let mut initial = self.initial_data.lock().unwrap();
548
605
  if let Some(data) = initial.take() {
549
606
  return Ok(data);
550
607
  }
@@ -559,31 +616,26 @@ impl Stream for ServerRpc {
559
616
  }
560
617
 
561
618
  async fn close(&self) -> Result<()> {
562
- // Mark as locally completed
563
- self.common.local_completed.store(true, Ordering::SeqCst);
564
-
565
- // Close the writer
566
- self.common.writer.close().await?;
567
-
568
- // Cancel the context
569
- self.common.ctx.cancel();
570
-
571
- Ok(())
619
+ self.common.close_local().await
572
620
  }
573
621
  }
574
622
 
575
623
  #[cfg(test)]
576
624
  mod tests {
577
- use super::*;
578
625
  use std::sync::Mutex as StdMutex;
579
626
 
580
- /// Mock packet writer for testing.
627
+ use super::*;
628
+
629
+ /// Records outgoing packets and whether the transport was closed.
581
630
  struct MockWriter {
631
+ /// Packets written in order.
582
632
  packets: StdMutex<Vec<Packet>>,
633
+ /// Whether close has run.
583
634
  closed: AtomicBool,
584
635
  }
585
636
 
586
637
  impl MockWriter {
638
+ /// Creates an open writer with no packets.
587
639
  fn new() -> Self {
588
640
  Self {
589
641
  packets: StdMutex::new(Vec::new()),
@@ -591,10 +643,12 @@ mod tests {
591
643
  }
592
644
  }
593
645
 
646
+ /// Copies the recorded wire packets.
594
647
  fn packets(&self) -> Vec<Packet> {
595
648
  self.packets.lock().unwrap().clone()
596
649
  }
597
650
 
651
+ /// Reports transport closure.
598
652
  fn is_closed(&self) -> bool {
599
653
  self.closed.load(Ordering::SeqCst)
600
654
  }
@@ -613,11 +667,18 @@ mod tests {
613
667
  }
614
668
  }
615
669
 
670
+ /// Starting a call transmits its service, method, and initial data.
616
671
  #[tokio::test]
617
672
  async fn test_client_rpc_start() {
673
+ // Establish the call and its transport recorder.
618
674
  let writer = Arc::new(MockWriter::new());
619
675
  let ctx = Context::new();
620
- let rpc = ClientRpc::new(ctx, "test.Service".into(), "TestMethod".into(), writer.clone());
676
+ let rpc = ClientRpc::new(
677
+ ctx,
678
+ "test.Service".into(),
679
+ "TestMethod".into(),
680
+ writer.clone(),
681
+ );
621
682
 
622
683
  rpc.start(Some(Bytes::from(vec![1, 2, 3]))).await.unwrap();
623
684
 
@@ -635,8 +696,10 @@ mod tests {
635
696
  }
636
697
  }
637
698
 
699
+ /// A client call sends its start packet once.
638
700
  #[tokio::test]
639
701
  async fn test_client_rpc_double_start_fails() {
702
+ // Establish the call and its transport recorder.
640
703
  let writer = Arc::new(MockWriter::new());
641
704
  let ctx = Context::new();
642
705
  let rpc = ClientRpc::new(ctx, "test.Service".into(), "TestMethod".into(), writer);
@@ -646,11 +709,18 @@ mod tests {
646
709
  assert!(matches!(result, Err(Error::Completed)));
647
710
  }
648
711
 
712
+ /// Closing a started call sends cancellation and closes the transport.
649
713
  #[tokio::test]
650
714
  async fn test_client_rpc_close_sends_cancel() {
715
+ // Establish the call and its transport recorder.
651
716
  let writer = Arc::new(MockWriter::new());
652
717
  let ctx = Context::new();
653
- let rpc = ClientRpc::new(ctx, "test.Service".into(), "TestMethod".into(), writer.clone());
718
+ let rpc = ClientRpc::new(
719
+ ctx,
720
+ "test.Service".into(),
721
+ "TestMethod".into(),
722
+ writer.clone(),
723
+ );
654
724
 
655
725
  rpc.start(None).await.unwrap();
656
726
  rpc.close().await;
@@ -663,8 +733,10 @@ mod tests {
663
733
  assert!(writer.is_closed());
664
734
  }
665
735
 
736
+ /// A server delivers the initial payload before subsequent data.
666
737
  #[tokio::test]
667
738
  async fn test_server_rpc_from_call_start() {
739
+ // Establish the call and its transport recorder.
668
740
  let call_start = CallStart {
669
741
  rpc_service: "test.Service".into(),
670
742
  rpc_method: "TestMethod".into(),
@@ -684,8 +756,37 @@ mod tests {
684
756
  assert_eq!(&data[..], &[1, 2, 3]);
685
757
  }
686
758
 
759
+ /// Local closure rejects data arriving after the call ends.
760
+ #[tokio::test]
761
+ async fn test_server_rpc_close_ends_the_call() {
762
+ // Establish the call and its transport recorder.
763
+ let call_start = CallStart {
764
+ rpc_service: "test.Service".into(),
765
+ rpc_method: "TestMethod".into(),
766
+ data: Vec::new(),
767
+ data_is_zero: false,
768
+ };
769
+ let writer = Arc::new(MockWriter::new());
770
+ let rpc = ServerRpc::from_call_start(Context::new(), call_start, writer.clone());
771
+
772
+ // Closing releases the writer and cancels the call.
773
+ rpc.close().await.unwrap();
774
+ assert!(writer.is_closed());
775
+ assert!(rpc.context().is_cancelled());
776
+
777
+ // The call has an end state, so later data is refused instead of queued.
778
+ let late = CallData {
779
+ data: vec![1],
780
+ ..Default::default()
781
+ };
782
+ let result = rpc.common.handle_call_data(late).await;
783
+ assert!(matches!(result, Err(Error::Completed)));
784
+ }
785
+
786
+ /// Receiving preserves payload bytes.
687
787
  #[tokio::test]
688
788
  async fn test_common_rpc_read_one_with_data() {
789
+ // Establish the call and its transport recorder.
689
790
  let writer = Arc::new(MockWriter::new());
690
791
  let ctx = Context::new();
691
792
  let rpc = CommonRpc::new(ctx, "svc".into(), "method".into(), writer);
@@ -703,8 +804,10 @@ mod tests {
703
804
  assert_eq!(&data[..], &[1, 2, 3]);
704
805
  }
705
806
 
807
+ /// Completion without data ends reads cleanly.
706
808
  #[tokio::test]
707
809
  async fn test_common_rpc_read_one_stream_closed() {
810
+ // Establish the call and its transport recorder.
708
811
  let writer = Arc::new(MockWriter::new());
709
812
  let ctx = Context::new();
710
813
  let rpc = CommonRpc::new(ctx, "svc".into(), "method".into(), writer);
@@ -722,8 +825,10 @@ mod tests {
722
825
  assert!(matches!(result, Err(Error::StreamClosed)));
723
826
  }
724
827
 
828
+ /// A remote failure preserves its error text.
725
829
  #[tokio::test]
726
830
  async fn test_common_rpc_read_one_with_error() {
831
+ // Establish the call and its transport recorder.
727
832
  let writer = Arc::new(MockWriter::new());
728
833
  let ctx = Context::new();
729
834
  let rpc = CommonRpc::new(ctx, "svc".into(), "method".into(), writer);
@@ -744,8 +849,10 @@ mod tests {
744
849
  }
745
850
  }
746
851
 
852
+ /// Local completion rejects subsequent data writes.
747
853
  #[tokio::test]
748
854
  async fn test_write_call_data_after_complete() {
855
+ // Establish the call and its transport recorder.
749
856
  let writer = Arc::new(MockWriter::new());
750
857
  let ctx = Context::new();
751
858
  let rpc = CommonRpc::new(ctx, "svc".into(), "method".into(), writer);
@@ -754,12 +861,16 @@ mod tests {
754
861
  rpc.write_call_data(None, true, None).await.unwrap();
755
862
 
756
863
  // Trying to send more data should fail
757
- let result = rpc.write_call_data(Some(Bytes::from(vec![1])), false, None).await;
864
+ let result = rpc
865
+ .write_call_data(Some(Bytes::from(vec![1])), false, None)
866
+ .await;
758
867
  assert!(matches!(result, Err(Error::Completed)));
759
868
  }
760
869
 
870
+ /// Cancellation sends exactly one wire packet.
761
871
  #[tokio::test]
762
872
  async fn test_write_call_cancel() {
873
+ // Establish the call and its transport recorder.
763
874
  let writer = Arc::new(MockWriter::new());
764
875
  let ctx = Context::new();
765
876
  let rpc = CommonRpc::new(ctx, "svc".into(), "method".into(), writer.clone());
@@ -774,4 +885,110 @@ mod tests {
774
885
  let result = rpc.write_call_cancel().await;
775
886
  assert!(matches!(result, Err(Error::Completed)));
776
887
  }
888
+
889
+ /// A full receive channel pauses packet processing until the consumer reads.
890
+ #[tokio::test]
891
+ async fn receive_capacity_preserves_data_and_terminal_order() {
892
+ // Fill the receive channel with an empty but present message.
893
+ let rpc = CommonRpc::new(
894
+ Context::new(),
895
+ "svc".into(),
896
+ "method".into(),
897
+ Arc::new(MockWriter::new()),
898
+ );
899
+ rpc.handle_call_data(CallData {
900
+ data_is_zero: true,
901
+ ..Default::default()
902
+ })
903
+ .await
904
+ .unwrap();
905
+
906
+ // The final payload waits for capacity without publishing completion early.
907
+ let final_packet = rpc.handle_call_data(CallData {
908
+ data: b"last".to_vec(),
909
+ complete: true,
910
+ error: "finished".into(),
911
+ ..Default::default()
912
+ });
913
+ tokio::pin!(final_packet);
914
+ assert!(futures::poll!(&mut final_packet).is_pending());
915
+ let wait = rpc.wait();
916
+ tokio::pin!(wait);
917
+ assert!(futures::poll!(&mut wait).is_pending());
918
+
919
+ // Draining the prior message admits the final payload before its error.
920
+ assert!(rpc.read_one().await.unwrap().is_empty());
921
+ final_packet.await.unwrap();
922
+ assert_eq!(rpc.read_one().await.unwrap().as_ref(), b"last");
923
+ assert!(matches!(rpc.read_one().await, Err(Error::Remote(err)) if err == "finished"));
924
+ assert!(matches!(wait.await, Err(Error::Remote(err)) if err == "finished"));
925
+ }
926
+
927
+ /// Cancellation and closure both wake a packet reader waiting for capacity.
928
+ #[tokio::test]
929
+ async fn full_receive_channel_releases_on_cancel_and_close() {
930
+ // Exercise context cancellation, local close, and transport loss separately.
931
+ for end in 0..3 {
932
+ let rpc = CommonRpc::new(
933
+ Context::new(),
934
+ "svc".into(),
935
+ "method".into(),
936
+ Arc::new(MockWriter::new()),
937
+ );
938
+ rpc.handle_call_data(CallData {
939
+ data: vec![1],
940
+ ..Default::default()
941
+ })
942
+ .await
943
+ .unwrap();
944
+ let pending = rpc.handle_call_data(CallData {
945
+ data: vec![2],
946
+ ..Default::default()
947
+ });
948
+ tokio::pin!(pending);
949
+ assert!(futures::poll!(&mut pending).is_pending());
950
+
951
+ // End the RPC through its public lifetime operation while data remains unread.
952
+ match end {
953
+ 0 => rpc.context().cancel(),
954
+ 1 => rpc.close_local().await.unwrap(),
955
+ _ => rpc.handle_stream_close(None).await.unwrap(),
956
+ }
957
+ let result = tokio::time::timeout(std::time::Duration::from_secs(2), pending)
958
+ .await
959
+ .unwrap();
960
+ assert!(matches!(result, Err(Error::Cancelled | Error::Completed)));
961
+ assert_eq!(rpc.read_one().await.unwrap().as_ref(), &[1]);
962
+ assert!(rpc.read_one().await.is_err());
963
+ }
964
+ }
965
+
966
+ /// A completion with no payload bypasses a full data channel.
967
+ #[tokio::test]
968
+ async fn completion_does_not_need_receive_capacity() {
969
+ // Leave one unread payload when the peer completes the stream.
970
+ let rpc = CommonRpc::new(
971
+ Context::new(),
972
+ "svc".into(),
973
+ "method".into(),
974
+ Arc::new(MockWriter::new()),
975
+ );
976
+ rpc.handle_call_data(CallData {
977
+ data: vec![1],
978
+ ..Default::default()
979
+ })
980
+ .await
981
+ .unwrap();
982
+ rpc.handle_call_data(CallData {
983
+ complete: true,
984
+ ..Default::default()
985
+ })
986
+ .await
987
+ .unwrap();
988
+
989
+ // Wait observes completion, while receive preserves the payload preceding it.
990
+ rpc.wait().await.unwrap();
991
+ assert_eq!(rpc.read_one().await.unwrap().as_ref(), &[1]);
992
+ assert!(matches!(rpc.read_one().await, Err(Error::StreamClosed)));
993
+ }
777
994
  }