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.
- package/README.md +37 -8
- package/dist/echo/client-test.d.ts +1 -1
- package/dist/echo/client-test.js +110 -2
- package/dist/integration/cross-language/tcp-packet-stream.d.ts +3 -0
- package/dist/integration/cross-language/tcp-packet-stream.js +112 -0
- package/dist/integration/cross-language/tcp-packet-stream.test.d.ts +1 -0
- package/dist/integration/cross-language/tcp-packet-stream.test.js +121 -0
- package/dist/integration/cross-language/ts-client.js +50 -37
- package/dist/integration/cross-language/ts-server.js +1 -36
- package/dist/rpcstream/rpcstream.d.ts +5 -1
- package/dist/rpcstream/rpcstream.js +75 -28
- package/dist/rpcstream/rpcstream.test.d.ts +1 -0
- package/dist/rpcstream/rpcstream.test.js +92 -0
- 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/client.js +18 -4
- package/dist/srpc/common-rpc.d.ts +5 -0
- package/dist/srpc/common-rpc.js +37 -8
- package/dist/srpc/common-rpc.test.js +2 -0
- 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/dist/srpc/packet-codec.test.d.ts +1 -0
- package/dist/srpc/packet-codec.test.js +75 -0
- package/dist/srpc/packet.d.ts +1 -1
- package/dist/srpc/packet.js +11 -1
- package/dist/srpc/server.js +19 -6
- package/dist/srpc/server.test.js +43 -0
- package/dist/srpc/stream.d.ts +4 -1
- package/dist/srpc/stream.js +62 -3
- package/dist/srpc/stream.test.js +110 -1
- package/dist/srpc/termination.d.ts +27 -0
- package/dist/srpc/termination.js +56 -0
- package/dist/srpc/termination.test.d.ts +1 -0
- package/dist/srpc/termination.test.js +24 -0
- package/dist/testdata/packet-codec-vectors.json +64 -0
- package/echo/Cargo.toml +8 -0
- package/echo/client-test.ts +124 -2
- package/echo/echo_e2e_test.cpp +542 -985
- package/echo/echo_pb2.py +40 -0
- package/echo/echo_pb2.pyi +13 -0
- 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 +309 -0
- package/echo/echo_srpc.pyi +85 -0
- package/echo/half_close_test.rs +178 -0
- package/echo/integration_server.rs +14 -21
- package/echo/main.rs +28 -48
- package/go.mod +3 -3
- package/go.sum +6 -8
- 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/integration/cross-language/go-client/main.go +79 -3
- package/integration/cross-language/python-client.py +146 -0
- package/integration/cross-language/python-server.py +140 -0
- package/integration/cross-language/run.bash +190 -65
- package/integration/cross-language/tcp-packet-stream.test.ts +154 -0
- package/integration/cross-language/tcp-packet-stream.ts +121 -0
- package/integration/cross-language/ts-client.ts +62 -40
- package/integration/cross-language/ts-server.ts +1 -45
- package/mock/mock_pb2.py +38 -0
- package/mock/mock_pb2.pyi +11 -0
- 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/mock/mock_srpc.py +71 -0
- package/mock/mock_srpc.pyi +27 -0
- package/package.json +19 -5
- package/srpc/CMakeLists.txt +3 -58
- package/srpc/README-cpp.md +69 -0
- package/srpc/__init__.py +0 -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.ts +20 -4
- package/srpc/client_half_close_test.cc +331 -0
- package/srpc/client_invoker.rs +75 -0
- package/srpc/codec.rs +6 -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.test.ts +2 -0
- 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-codec-vectors_test.go +195 -0
- package/srpc/packet-codec.test.ts +139 -0
- package/srpc/packet-rw.go +9 -2
- package/srpc/packet.cpp +60 -58
- package/srpc/packet.hpp +35 -20
- package/srpc/packet.rs +8 -27
- package/srpc/packet.ts +15 -2
- package/srpc/py.typed +0 -0
- package/srpc/rpc.rs +332 -115
- package/srpc/rpc_lifecycle_test.cc +333 -0
- package/srpc/rpcproto_pb2.py +40 -0
- package/srpc/rpcproto_pb2.pyi +40 -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/server.test.ts +50 -0
- package/srpc/server.ts +22 -6
- 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/stream.test.ts +132 -1
- package/srpc/stream.ts +65 -9
- package/srpc/termination.test.ts +30 -0
- package/srpc/termination.ts +70 -0
- 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/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
|
|
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
|
|
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
|
-
///
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
///
|
|
67
|
-
|
|
78
|
+
/// The remote failed or cancelled the call.
|
|
79
|
+
Remote(String),
|
|
68
80
|
|
|
69
|
-
///
|
|
70
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
|
141
|
+
/// Waits for the remote verdict after preceding payloads have been accepted.
|
|
117
142
|
///
|
|
118
|
-
///
|
|
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().
|
|
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
|
-
|
|
131
|
-
return
|
|
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
|
-
_ =
|
|
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
|
-
///
|
|
150
|
-
///
|
|
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().
|
|
186
|
+
let mut state = self.state.lock().unwrap();
|
|
157
187
|
|
|
158
|
-
if let
|
|
188
|
+
if let Ok(data) = state.data.try_recv() {
|
|
159
189
|
return Ok(data);
|
|
160
190
|
}
|
|
161
191
|
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
//
|
|
175
|
-
let
|
|
176
|
-
|
|
177
|
-
state.
|
|
178
|
-
|
|
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
|
-
_ =
|
|
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
|
-
///
|
|
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
|
-
///
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
312
|
+
// Publish the terminal verdict after its final payload and wake blocked readers.
|
|
265
313
|
if !call_data.error.is_empty() {
|
|
266
|
-
state.
|
|
267
|
-
state.data_closed = true;
|
|
314
|
+
state.end = Some(RpcEnd::Remote(call_data.error));
|
|
268
315
|
} else if call_data.complete {
|
|
269
|
-
state.
|
|
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()))
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if state.
|
|
290
|
-
state.
|
|
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
|
-
///
|
|
359
|
+
/// Ends the call locally and releases its resources.
|
|
305
360
|
///
|
|
306
|
-
///
|
|
307
|
-
///
|
|
308
|
-
///
|
|
309
|
-
async fn
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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().
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
}
|