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/transport.rs
CHANGED
|
@@ -12,6 +12,7 @@ use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
|
|
|
12
12
|
use tokio::sync::Mutex;
|
|
13
13
|
use tokio_util::codec::{Encoder, FramedRead};
|
|
14
14
|
|
|
15
|
+
use crate::client::PacketReceiver;
|
|
15
16
|
use crate::codec::PacketCodec;
|
|
16
17
|
use crate::error::{Error, Result};
|
|
17
18
|
use crate::proto::Packet;
|
|
@@ -68,65 +69,13 @@ impl<W: AsyncWrite + Send + Unpin + 'static> PacketWriter for TransportPacketWri
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
///
|
|
72
|
-
|
|
72
|
+
/// Default bounded request capacity for multiplexed transport admission.
|
|
73
|
+
#[cfg(feature = "yamux")]
|
|
74
|
+
pub(crate) const DEFAULT_CHANNEL_BUFFER: usize = 32;
|
|
73
75
|
|
|
74
|
-
///
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
/// Default channel buffer size for packet channels.
|
|
78
|
-
pub const DEFAULT_CHANNEL_BUFFER: usize = 32;
|
|
79
|
-
|
|
80
|
-
/// Spawns a task that reads packets from a transport and sends them through a channel.
|
|
81
|
-
///
|
|
82
|
-
/// This is a common pattern used by both client and server to handle incoming packets.
|
|
83
|
-
/// The task will run until the transport is closed or an error occurs.
|
|
84
|
-
///
|
|
85
|
-
/// # Arguments
|
|
86
|
-
/// * `reader` - The async reader to read packets from
|
|
87
|
-
/// * `sender` - The channel sender to forward packets to
|
|
88
|
-
///
|
|
89
|
-
/// # Returns
|
|
90
|
-
/// A `JoinHandle` for the spawned task.
|
|
91
|
-
pub fn spawn_packet_reader<R>(
|
|
92
|
-
reader: R,
|
|
93
|
-
sender: PacketSender,
|
|
94
|
-
) -> tokio::task::JoinHandle<()>
|
|
95
|
-
where
|
|
96
|
-
R: AsyncRead + Send + Unpin + 'static,
|
|
97
|
-
{
|
|
98
|
-
tokio::spawn(async move {
|
|
99
|
-
let mut framed = FramedRead::new(reader, PacketCodec::new());
|
|
100
|
-
while let Some(result) = framed.next().await {
|
|
101
|
-
match result {
|
|
102
|
-
Ok(packet) => {
|
|
103
|
-
if sender.send(packet).await.is_err() {
|
|
104
|
-
// Receiver dropped, stop reading
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
Err(_) => {
|
|
109
|
-
// Read error, stop reading
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/// Creates a packet writer and receiver from a split transport.
|
|
118
|
-
///
|
|
119
|
-
/// This is a convenience function that:
|
|
120
|
-
/// 1. Creates a `TransportPacketWriter` from the write half
|
|
121
|
-
/// 2. Spawns a packet reader task for the read half
|
|
122
|
-
/// 3. Returns the writer and receiver channel
|
|
123
|
-
///
|
|
124
|
-
/// # Arguments
|
|
125
|
-
/// * `read_half` - The read half of the transport
|
|
126
|
-
/// * `write_half` - The write half of the transport
|
|
127
|
-
///
|
|
128
|
-
/// # Returns
|
|
129
|
-
/// A tuple of (packet writer, packet receiver).
|
|
76
|
+
/// Creates a packet writer and an owned incoming packet stream from a split transport.
|
|
77
|
+
/// The receiver reads on demand; dropping it immediately releases the read half.
|
|
78
|
+
/// No background task or additional packet queue is created.
|
|
130
79
|
pub fn create_packet_channel<R, W>(
|
|
131
80
|
read_half: R,
|
|
132
81
|
write_half: W,
|
|
@@ -135,10 +84,18 @@ where
|
|
|
135
84
|
R: AsyncRead + Send + Unpin + 'static,
|
|
136
85
|
W: AsyncWrite + Send + Unpin + 'static,
|
|
137
86
|
{
|
|
87
|
+
// Framing belongs to the returned receive capability for its complete lifetime.
|
|
88
|
+
let reader = FramedRead::new(read_half, PacketCodec::new());
|
|
89
|
+
let packets = futures::stream::unfold(reader, |mut reader| async move {
|
|
90
|
+
match reader.next().await {
|
|
91
|
+
Some(Ok(packet)) => Some((packet, reader)),
|
|
92
|
+
_ => None,
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// The peer-facing writer retains its own half of this same transport.
|
|
138
97
|
let writer: Arc<dyn PacketWriter> = Arc::new(TransportPacketWriter::new(write_half));
|
|
139
|
-
|
|
140
|
-
spawn_packet_reader(read_half, tx);
|
|
141
|
-
(writer, rx)
|
|
98
|
+
(writer, Box::pin(packets))
|
|
142
99
|
}
|
|
143
100
|
|
|
144
101
|
/// Encodes optional data for protobuf messages.
|
|
@@ -147,9 +104,6 @@ where
|
|
|
147
104
|
/// - `None` -> empty data, `data_is_zero = false`
|
|
148
105
|
/// - `Some(empty)` -> empty data, `data_is_zero = true`
|
|
149
106
|
/// - `Some(data)` -> data bytes, `data_is_zero = false`
|
|
150
|
-
///
|
|
151
|
-
/// # Returns
|
|
152
|
-
/// A tuple of (data bytes, data_is_zero flag).
|
|
153
107
|
pub fn encode_optional_data(data: Option<Bytes>) -> (Vec<u8>, bool) {
|
|
154
108
|
match data {
|
|
155
109
|
Some(d) if d.is_empty() => (vec![], true),
|
|
@@ -158,13 +112,9 @@ pub fn encode_optional_data(data: Option<Bytes>) -> (Vec<u8>, bool) {
|
|
|
158
112
|
}
|
|
159
113
|
}
|
|
160
114
|
|
|
161
|
-
/// Decodes optional data from protobuf messages
|
|
162
|
-
///
|
|
163
|
-
///
|
|
164
|
-
///
|
|
165
|
-
/// # Returns
|
|
166
|
-
/// `Some(Bytes)` if data was present (including empty data with `data_is_zero`),
|
|
167
|
-
/// `None` if no data was included.
|
|
115
|
+
/// Decodes optional data from protobuf messages, inverting
|
|
116
|
+
/// `encode_optional_data`: `Some(Bytes)` when data was present, including
|
|
117
|
+
/// empty data with `data_is_zero` set.
|
|
168
118
|
pub fn decode_optional_data(data: Vec<u8>, data_is_zero: bool) -> Option<Bytes> {
|
|
169
119
|
if !data.is_empty() || data_is_zero {
|
|
170
120
|
Some(Bytes::from(data))
|
package/srpc/websocket.rs
CHANGED
|
@@ -1,70 +1,119 @@
|
|
|
1
|
-
//! WebSocket transport
|
|
1
|
+
//! Owned WebSocket byte transport for StarPC.
|
|
2
2
|
|
|
3
|
-
use bytes::Bytes;
|
|
4
|
-
use futures::{
|
|
5
|
-
use
|
|
6
|
-
use
|
|
3
|
+
use bytes::{Buf, Bytes};
|
|
4
|
+
use futures::{Sink, Stream};
|
|
5
|
+
use std::io;
|
|
6
|
+
use std::pin::Pin;
|
|
7
|
+
use std::task::{ready, Context, Poll};
|
|
8
|
+
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
|
9
|
+
use tokio_tungstenite::{tungstenite, tungstenite::Message, WebSocketStream};
|
|
7
10
|
|
|
8
|
-
///
|
|
9
|
-
pub const WEBSOCKET_BYTE_STREAM_BUFFER: usize = 64 * 1024;
|
|
10
|
-
|
|
11
|
-
/// WEBSOCKET_MESSAGE_BUFFER is the maximum byte chunk sent in one WebSocket message.
|
|
11
|
+
/// Maximum byte chunk sent in one WebSocket message.
|
|
12
12
|
pub const WEBSOCKET_MESSAGE_BUFFER: usize = 16 * 1024;
|
|
13
13
|
|
|
14
|
-
///
|
|
15
|
-
///
|
|
16
|
-
///
|
|
17
|
-
///
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
let (mut peer_reader, mut peer_writer) = tokio::io::split(peer);
|
|
14
|
+
/// Adapts binary WebSocket messages to byte I/O without background tasks.
|
|
15
|
+
/// Incoming binary messages form one byte stream. Text and control messages
|
|
16
|
+
/// carry no application bytes; a close message ends input. Dropping this
|
|
17
|
+
/// capability drops its socket, including when input is idle.
|
|
18
|
+
pub fn websocket_byte_stream<S>(socket: WebSocketStream<S>) -> WebSocketByteStream<S> {
|
|
19
|
+
WebSocketByteStream {
|
|
20
|
+
socket,
|
|
21
|
+
buffered: Bytes::new(),
|
|
22
|
+
input: Input::Open,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
26
|
+
/// Byte I/O that owns its WebSocket and the unread part of one incoming message.
|
|
27
|
+
pub struct WebSocketByteStream<S> {
|
|
28
|
+
socket: WebSocketStream<S>,
|
|
29
|
+
buffered: Bytes,
|
|
30
|
+
input: Input,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
enum Input {
|
|
34
|
+
Open,
|
|
35
|
+
Closing,
|
|
36
|
+
Ended,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
impl<S: AsyncRead + AsyncWrite + Unpin> AsyncRead for WebSocketByteStream<S> {
|
|
40
|
+
fn poll_read(
|
|
41
|
+
self: Pin<&mut Self>,
|
|
42
|
+
cx: &mut Context<'_>,
|
|
43
|
+
output: &mut ReadBuf<'_>,
|
|
44
|
+
) -> Poll<io::Result<()>> {
|
|
45
|
+
let this = self.get_mut();
|
|
46
|
+
if output.remaining() == 0 {
|
|
47
|
+
return Poll::Ready(Ok(()));
|
|
43
48
|
}
|
|
44
|
-
let _ = peer_writer.shutdown().await;
|
|
45
|
-
});
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
let mut buf = vec![0; WEBSOCKET_MESSAGE_BUFFER];
|
|
50
|
+
// Retain each message until all its bytes have reached the reader.
|
|
49
51
|
loop {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
let _ = socket_writer.close().await;
|
|
63
|
-
break;
|
|
52
|
+
if !this.buffered.is_empty() {
|
|
53
|
+
let count = output.remaining().min(this.buffered.len());
|
|
54
|
+
output.put_slice(&this.buffered[..count]);
|
|
55
|
+
this.buffered.advance(count);
|
|
56
|
+
return Poll::Ready(Ok(()));
|
|
57
|
+
}
|
|
58
|
+
match this.input {
|
|
59
|
+
Input::Closing => {
|
|
60
|
+
// Flush Tungstenite's close acknowledgment before reporting EOF.
|
|
61
|
+
ready!(Pin::new(&mut this.socket).poll_flush(cx)).map_err(socket_error)?;
|
|
62
|
+
this.input = Input::Ended;
|
|
63
|
+
return Poll::Ready(Ok(()));
|
|
64
64
|
}
|
|
65
|
+
Input::Ended => return Poll::Ready(Ok(())),
|
|
66
|
+
Input::Open => {}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Tungstenite owns WebSocket control handling and pending control writes.
|
|
70
|
+
match ready!(Pin::new(&mut this.socket).poll_next(cx)) {
|
|
71
|
+
Some(Ok(Message::Binary(data))) => this.buffered = data,
|
|
72
|
+
Some(Ok(Message::Close(_))) => this.input = Input::Closing,
|
|
73
|
+
None => this.input = Input::Ended,
|
|
74
|
+
Some(Ok(_)) => {}
|
|
75
|
+
Some(Err(error)) => return Poll::Ready(Err(socket_error(error))),
|
|
65
76
|
}
|
|
66
77
|
}
|
|
67
|
-
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
impl<S: AsyncRead + AsyncWrite + Unpin> AsyncWrite for WebSocketByteStream<S> {
|
|
82
|
+
fn poll_write(
|
|
83
|
+
self: Pin<&mut Self>,
|
|
84
|
+
cx: &mut Context<'_>,
|
|
85
|
+
input: &[u8],
|
|
86
|
+
) -> Poll<io::Result<usize>> {
|
|
87
|
+
if input.is_empty() {
|
|
88
|
+
return Poll::Ready(Ok(0));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Respect socket backpressure before accepting another bounded message.
|
|
92
|
+
let socket = &mut self.get_mut().socket;
|
|
93
|
+
ready!(Pin::new(&mut *socket).poll_ready(cx)).map_err(socket_error)?;
|
|
94
|
+
let count = input.len().min(WEBSOCKET_MESSAGE_BUFFER);
|
|
95
|
+
Pin::new(socket)
|
|
96
|
+
.start_send(Message::Binary(Bytes::copy_from_slice(&input[..count])))
|
|
97
|
+
.map_err(socket_error)?;
|
|
98
|
+
Poll::Ready(Ok(count))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
102
|
+
Pin::new(&mut self.get_mut().socket)
|
|
103
|
+
.poll_flush(cx)
|
|
104
|
+
.map_err(socket_error)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
|
108
|
+
Pin::new(&mut self.get_mut().socket)
|
|
109
|
+
.poll_close(cx)
|
|
110
|
+
.map_err(socket_error)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
68
113
|
|
|
69
|
-
|
|
114
|
+
fn socket_error(error: tungstenite::Error) -> io::Error {
|
|
115
|
+
match error {
|
|
116
|
+
tungstenite::Error::Io(error) => error,
|
|
117
|
+
other => io::Error::other(other),
|
|
118
|
+
}
|
|
70
119
|
}
|
package/srpc/writer.hpp
CHANGED
|
@@ -1,60 +1,74 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
+
#include "errors.hpp"
|
|
4
|
+
|
|
3
5
|
#include <functional>
|
|
4
6
|
#include <memory>
|
|
5
7
|
|
|
6
|
-
#include "errors.hpp"
|
|
7
|
-
|
|
8
8
|
namespace srpc {
|
|
9
9
|
class Packet;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
namespace starpc {
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
/*
|
|
15
|
+
* PacketWriter writes one packet to the transport. Close may run from a
|
|
16
|
+
* receive callback or concurrently with a write, and must interrupt blocked
|
|
17
|
+
* I/O without joining callbacks. Destruction joins any transport receive
|
|
18
|
+
* threads.
|
|
19
|
+
*/
|
|
16
20
|
class PacketWriter {
|
|
17
21
|
public:
|
|
18
|
-
|
|
22
|
+
virtual ~PacketWriter() = default;
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
/* WritePacket serializes pkt onto the transport. */
|
|
25
|
+
virtual Error WritePacket(const srpc::Packet &pkt) = 0;
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
/* Close interrupts the transport. Repeated calls are safe. */
|
|
28
|
+
virtual Error Close() = 0;
|
|
25
29
|
};
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
/*
|
|
32
|
+
* PacketWriterWithClose forwards writes to an owned writer and runs an extra
|
|
33
|
+
* callback on the first Close. Matches packetWriterWithClose in writer.go.
|
|
34
|
+
*/
|
|
29
35
|
class PacketWriterWithClose : public PacketWriter {
|
|
30
36
|
public:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
/* close_fn runs once, after inner->Close. */
|
|
38
|
+
PacketWriterWithClose(std::unique_ptr<PacketWriter> inner,
|
|
39
|
+
std::function<Error()> close_fn)
|
|
40
|
+
: inner(std::move(inner)),
|
|
41
|
+
close_fn(std::move(close_fn))
|
|
42
|
+
{
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Error WritePacket(const srpc::Packet &pkt) override
|
|
46
|
+
{
|
|
47
|
+
return inner->WritePacket(pkt);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Error Close() override
|
|
51
|
+
{
|
|
52
|
+
/* Report the inner close even when the callback also fails. */
|
|
53
|
+
Error err = inner->Close();
|
|
54
|
+
Error err2 = close_fn();
|
|
55
|
+
if (err != Error::OK)
|
|
56
|
+
return err;
|
|
57
|
+
return err2;
|
|
58
|
+
}
|
|
46
59
|
|
|
47
60
|
private:
|
|
48
|
-
|
|
49
|
-
|
|
61
|
+
std::unique_ptr<PacketWriter> inner;
|
|
62
|
+
std::function<Error()> close_fn;
|
|
50
63
|
};
|
|
51
64
|
|
|
52
|
-
|
|
65
|
+
/* NewPacketWriterWithClose wraps prw so close_fn runs on the first Close. */
|
|
53
66
|
inline std::unique_ptr<PacketWriter>
|
|
54
67
|
NewPacketWriterWithClose(std::unique_ptr<PacketWriter> prw,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
68
|
+
std::function<Error()> close_fn)
|
|
69
|
+
{
|
|
70
|
+
return std::make_unique<PacketWriterWithClose>(std::move(prw),
|
|
71
|
+
std::move(close_fn));
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
} // namespace starpc
|
package/srpc/yamux.rs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
//! Yamux transport adapters for starpc.
|
|
2
2
|
|
|
3
3
|
use async_trait::async_trait;
|
|
4
|
-
use futures::future::poll_fn;
|
|
4
|
+
use futures::{future::poll_fn, stream::FuturesUnordered, StreamExt};
|
|
5
5
|
use std::io;
|
|
6
6
|
use std::pin::Pin;
|
|
7
7
|
use std::sync::Arc;
|
|
8
8
|
use tokio::io::{AsyncRead, AsyncWrite};
|
|
9
|
-
use tokio::sync::{mpsc, oneshot};
|
|
9
|
+
use tokio::sync::{mpsc, oneshot, Mutex};
|
|
10
|
+
use tokio::task::JoinHandle;
|
|
10
11
|
use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
|
|
12
|
+
use tokio_util::sync::CancellationToken;
|
|
11
13
|
|
|
12
14
|
use crate::client::{OpenStream, PacketReceiver};
|
|
13
15
|
use crate::error::{Error, Result};
|
|
@@ -19,14 +21,30 @@ use crate::transport::{create_packet_channel, DEFAULT_CHANNEL_BUFFER};
|
|
|
19
21
|
type OpenResult = std::result::Result<::yamux::Stream, ::yamux::ConnectionError>;
|
|
20
22
|
type OpenRequest = oneshot::Sender<OpenResult>;
|
|
21
23
|
|
|
22
|
-
///
|
|
24
|
+
/// Opens StarPC packet streams over one owned yamux connection.
|
|
25
|
+
/// Clones share the connection. Dropping the last opener cancels its driver;
|
|
26
|
+
/// explicit close cancels and joins the driver before returning.
|
|
23
27
|
#[derive(Clone)]
|
|
24
28
|
pub struct YamuxStreamOpener {
|
|
29
|
+
driver: Arc<ClientDriver>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
struct ClientDriver {
|
|
25
33
|
requests: mpsc::Sender<OpenRequest>,
|
|
34
|
+
task: Mutex<Option<JoinHandle<()>>>,
|
|
35
|
+
closed: CancellationToken,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl Drop for ClientDriver {
|
|
39
|
+
fn drop(&mut self) {
|
|
40
|
+
if let Some(task) = self.task.get_mut().take() {
|
|
41
|
+
task.abort();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
26
44
|
}
|
|
27
45
|
|
|
28
46
|
impl YamuxStreamOpener {
|
|
29
|
-
///
|
|
47
|
+
/// Creates a client-mode yamux opener over a Tokio transport.
|
|
30
48
|
pub fn client<T>(transport: T) -> Self
|
|
31
49
|
where
|
|
32
50
|
T: AsyncRead + AsyncWrite + Send + Unpin + 'static,
|
|
@@ -34,17 +52,41 @@ impl YamuxStreamOpener {
|
|
|
34
52
|
Self::client_with_config(transport, ::yamux::Config::default())
|
|
35
53
|
}
|
|
36
54
|
|
|
37
|
-
///
|
|
55
|
+
/// Creates a client-mode yamux opener with a custom configuration.
|
|
38
56
|
pub fn client_with_config<T>(transport: T, config: ::yamux::Config) -> Self
|
|
39
57
|
where
|
|
40
58
|
T: AsyncRead + AsyncWrite + Send + Unpin + 'static,
|
|
41
59
|
{
|
|
42
60
|
let (requests, request_rx) = mpsc::channel(DEFAULT_CHANNEL_BUFFER);
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
let closed = CancellationToken::new();
|
|
62
|
+
let task = spawn_client_driver(transport.compat(), config, request_rx, closed.clone());
|
|
63
|
+
Self {
|
|
64
|
+
driver: Arc::new(ClientDriver {
|
|
65
|
+
requests,
|
|
66
|
+
task: Mutex::new(Some(task)),
|
|
67
|
+
closed,
|
|
68
|
+
}),
|
|
69
|
+
}
|
|
45
70
|
}
|
|
46
71
|
|
|
47
|
-
///
|
|
72
|
+
/// Cancels the shared connection and joins its driver.
|
|
73
|
+
/// Concurrent callers wait for the same completed teardown.
|
|
74
|
+
pub async fn close(&self) {
|
|
75
|
+
let mut task = self.driver.task.lock().await;
|
|
76
|
+
if let Some(task) = task.as_mut() {
|
|
77
|
+
task.abort();
|
|
78
|
+
let _ = task.await;
|
|
79
|
+
}
|
|
80
|
+
task.take();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Waits for the connection driver to end, including peer closure and protocol failure.
|
|
84
|
+
/// Observers do not prevent a concurrent explicit close from joining the driver.
|
|
85
|
+
pub async fn closed(&self) {
|
|
86
|
+
self.driver.closed.cancelled().await;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Creates a client-mode yamux opener over a WebSocket.
|
|
48
90
|
#[cfg(feature = "websocket")]
|
|
49
91
|
pub fn client_websocket<S>(socket: tokio_tungstenite::WebSocketStream<S>) -> Self
|
|
50
92
|
where
|
|
@@ -53,7 +95,7 @@ impl YamuxStreamOpener {
|
|
|
53
95
|
Self::client(crate::websocket::websocket_byte_stream(socket))
|
|
54
96
|
}
|
|
55
97
|
|
|
56
|
-
///
|
|
98
|
+
/// Creates a client-mode yamux WebSocket opener with a custom configuration.
|
|
57
99
|
#[cfg(feature = "websocket")]
|
|
58
100
|
pub fn client_websocket_with_config<S>(
|
|
59
101
|
socket: tokio_tungstenite::WebSocketStream<S>,
|
|
@@ -70,7 +112,8 @@ impl YamuxStreamOpener {
|
|
|
70
112
|
impl OpenStream for YamuxStreamOpener {
|
|
71
113
|
async fn open_stream(&self) -> Result<(Arc<dyn PacketWriter>, PacketReceiver)> {
|
|
72
114
|
let (tx, rx) = oneshot::channel();
|
|
73
|
-
self.
|
|
115
|
+
self.driver
|
|
116
|
+
.requests
|
|
74
117
|
.send(tx)
|
|
75
118
|
.await
|
|
76
119
|
.map_err(|_| Error::StreamClosed)?;
|
|
@@ -85,7 +128,7 @@ impl OpenStream for YamuxStreamOpener {
|
|
|
85
128
|
}
|
|
86
129
|
}
|
|
87
130
|
|
|
88
|
-
///
|
|
131
|
+
/// Serves yamux substreams within this connection's owned lifetime.
|
|
89
132
|
pub async fn handle_server_connection<I, T>(
|
|
90
133
|
server: &Server<I>,
|
|
91
134
|
transport: T,
|
|
@@ -98,18 +141,23 @@ where
|
|
|
98
141
|
let mut connection =
|
|
99
142
|
::yamux::Connection::new(transport.compat(), config, ::yamux::Mode::Server);
|
|
100
143
|
|
|
144
|
+
// Keep accepted calls within the connection's lifetime. Losing the connection
|
|
145
|
+
// or dropping this future drops every call and its cancellation guard.
|
|
146
|
+
let mut calls = FuturesUnordered::new();
|
|
101
147
|
loop {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
148
|
+
tokio::select! {
|
|
149
|
+
incoming = poll_fn(|cx| connection.poll_next_inbound(cx)) => {
|
|
150
|
+
match incoming {
|
|
151
|
+
Some(Ok(stream)) => calls.push(server.handle_stream(stream.compat())),
|
|
152
|
+
Some(Err(err)) => return Err(connection_error(err)),
|
|
153
|
+
None => return Ok(()),
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
Some(result) = calls.next(), if !calls.is_empty() => {
|
|
157
|
+
if let Err(err) = result {
|
|
158
|
+
server.report_error(err);
|
|
159
|
+
}
|
|
110
160
|
}
|
|
111
|
-
Some(Err(err)) => return Err(connection_error(err)),
|
|
112
|
-
None => return Ok(()),
|
|
113
161
|
}
|
|
114
162
|
}
|
|
115
163
|
}
|
|
@@ -126,21 +174,29 @@ fn spawn_client_driver<T>(
|
|
|
126
174
|
transport: T,
|
|
127
175
|
config: ::yamux::Config,
|
|
128
176
|
mut requests: mpsc::Receiver<OpenRequest>,
|
|
129
|
-
|
|
177
|
+
closed: CancellationToken,
|
|
178
|
+
) -> JoinHandle<()>
|
|
130
179
|
where
|
|
131
180
|
T: futures::io::AsyncRead + futures::io::AsyncWrite + Send + Unpin + 'static,
|
|
132
181
|
{
|
|
182
|
+
// Construct the guard before spawning so cancellation before the first poll also notifies.
|
|
183
|
+
let ended = closed.drop_guard();
|
|
133
184
|
tokio::spawn(async move {
|
|
185
|
+
let _ended = ended;
|
|
134
186
|
let mut connection = ::yamux::Connection::new(transport, config, ::yamux::Mode::Client);
|
|
135
187
|
let mut pending = None;
|
|
136
|
-
let mut requests_closed = false;
|
|
137
188
|
|
|
138
189
|
loop {
|
|
139
190
|
let event = poll_fn(|cx| {
|
|
140
|
-
if pending.is_none()
|
|
191
|
+
if pending.is_none() {
|
|
141
192
|
match Pin::new(&mut requests).poll_recv(cx) {
|
|
142
193
|
std::task::Poll::Ready(Some(request)) => pending = Some(request),
|
|
143
|
-
std::task::Poll::Ready(None) =>
|
|
194
|
+
std::task::Poll::Ready(None) => {
|
|
195
|
+
return std::task::Poll::Ready(DriverEvent::Closed {
|
|
196
|
+
pending: None,
|
|
197
|
+
err: None,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
144
200
|
std::task::Poll::Pending => {}
|
|
145
201
|
}
|
|
146
202
|
}
|
|
@@ -193,6 +249,6 @@ where
|
|
|
193
249
|
fn connection_error(err: ::yamux::ConnectionError) -> Error {
|
|
194
250
|
match err {
|
|
195
251
|
::yamux::ConnectionError::Io(err) => Error::Io(err),
|
|
196
|
-
other => Error::Io(io::Error::
|
|
252
|
+
other => Error::Io(io::Error::other(other.to_string())),
|
|
197
253
|
}
|
|
198
254
|
}
|