starpc 0.52.1 → 0.52.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +37 -8
  2. package/dist/srpc/client-rpc.d.ts +1 -0
  3. package/dist/srpc/client-rpc.js +9 -0
  4. package/dist/srpc/client-rpc.test.d.ts +1 -0
  5. package/dist/srpc/client-rpc.test.js +50 -0
  6. package/dist/srpc/common-rpc.d.ts +5 -0
  7. package/dist/srpc/common-rpc.js +37 -8
  8. package/dist/srpc/errors.d.ts +2 -0
  9. package/dist/srpc/errors.js +14 -0
  10. package/dist/srpc/index.d.ts +1 -1
  11. package/dist/srpc/index.js +1 -1
  12. package/echo/Cargo.toml +8 -0
  13. package/echo/echo_e2e_test.cpp +542 -985
  14. package/echo/echo_srpc.pb.cpp +3 -5
  15. package/echo/echo_srpc.pb.go +6 -9
  16. package/echo/echo_srpc.pb.hpp +23 -3
  17. package/echo/echo_srpc.pb.rs +61 -26
  18. package/echo/echo_srpc.py +4 -1
  19. package/echo/half_close_test.rs +178 -0
  20. package/echo/integration_server.rs +14 -21
  21. package/echo/main.rs +28 -48
  22. package/go.mod +1 -1
  23. package/go.sum +2 -18
  24. package/integration/cross-language/cpp-client.cpp +446 -402
  25. package/integration/cross-language/cpp-pipe-server.cpp +139 -0
  26. package/integration/cross-language/cpp-pipe_test.go +114 -0
  27. package/integration/cross-language/cpp-server.cpp +222 -186
  28. package/mock/mock_srpc.pb.cpp +1 -1
  29. package/mock/mock_srpc.pb.hpp +1 -1
  30. package/mock/mock_srpc.pb.rs +10 -9
  31. package/package.json +1 -1
  32. package/srpc/CMakeLists.txt +3 -58
  33. package/srpc/README-cpp.md +69 -0
  34. package/srpc/accept.go +10 -4
  35. package/srpc/accept_test.go +25 -0
  36. package/srpc/build.rs +129 -34
  37. package/srpc/client-half-close_test.go +152 -0
  38. package/srpc/client-invoker.go +32 -42
  39. package/srpc/client-invoker_test.go +53 -0
  40. package/srpc/client-rpc.cpp +65 -80
  41. package/srpc/client-rpc.go +32 -33
  42. package/srpc/client-rpc.hpp +52 -38
  43. package/srpc/client-rpc.test.ts +67 -0
  44. package/srpc/client-rpc.ts +10 -0
  45. package/srpc/client-rpc_test.go +76 -0
  46. package/srpc/client.cpp +99 -87
  47. package/srpc/client.hpp +46 -35
  48. package/srpc/client.rs +78 -38
  49. package/srpc/client_half_close_test.cc +331 -0
  50. package/srpc/client_invoker.rs +75 -0
  51. package/srpc/common-rpc.cpp +186 -146
  52. package/srpc/common-rpc.go +5 -4
  53. package/srpc/common-rpc.hpp +119 -89
  54. package/srpc/common-rpc.ts +43 -8
  55. package/srpc/common-rpc_test.go +23 -0
  56. package/srpc/error.rs +19 -2
  57. package/srpc/errors.hpp +80 -50
  58. package/srpc/errors.ts +16 -0
  59. package/srpc/handler.hpp +9 -7
  60. package/srpc/handler.rs +7 -45
  61. package/srpc/index.ts +2 -0
  62. package/srpc/invoker.hpp +82 -55
  63. package/srpc/invoker.rs +14 -52
  64. package/srpc/lib.rs +2 -0
  65. package/srpc/message.hpp +17 -12
  66. package/srpc/msg-stream.go +1 -0
  67. package/srpc/msg-stream.hpp +108 -61
  68. package/srpc/mux.cpp +84 -84
  69. package/srpc/mux.hpp +48 -38
  70. package/srpc/mux.rs +4 -4
  71. package/srpc/net.go +12 -3
  72. package/srpc/packet.cpp +60 -58
  73. package/srpc/packet.hpp +35 -20
  74. package/srpc/packet.rs +8 -27
  75. package/srpc/rpc.rs +332 -115
  76. package/srpc/rpc_lifecycle_test.cc +333 -0
  77. package/srpc/server-cancel_test.go +39 -0
  78. package/srpc/server-rpc.cpp +83 -73
  79. package/srpc/server-rpc.go +21 -19
  80. package/srpc/server-rpc.hpp +56 -37
  81. package/srpc/server.rs +76 -131
  82. package/srpc/starpc.hpp +4 -2
  83. package/srpc/stream.go +4 -0
  84. package/srpc/stream.hpp +95 -34
  85. package/srpc/stream.rs +7 -2
  86. package/srpc/testing.rs +11 -66
  87. package/srpc/transport.rs +21 -71
  88. package/srpc/websocket.rs +106 -57
  89. package/srpc/writer.hpp +46 -32
  90. package/srpc/yamux.rs +82 -26
  91. package/srpc/tests/integration_test.rs +0 -495
package/srpc/testing.rs CHANGED
@@ -13,22 +13,8 @@ use crate::error::{Error, Result};
13
13
  use crate::rpc::PacketWriter;
14
14
  use crate::transport::create_packet_channel;
15
15
 
16
- /// Creates a pair of connected in-memory streams.
17
- ///
18
- /// Returns (client_stream, server_stream) that can be used for testing
19
- /// client-server communication.
20
- ///
21
- /// # Arguments
22
- /// * `buffer_size` - Size of the internal buffer for each direction
23
- ///
24
- /// # Example
25
- ///
26
- /// ```ignore
27
- /// let (client_stream, server_stream) = create_pipe(64 * 1024);
28
- ///
29
- /// // Use client_stream with a client
30
- /// // Use server_stream with a server
31
- /// ```
16
+ /// Creates a pair of connected in-memory streams for testing client-server
17
+ /// communication, with `buffer_size` bytes of buffer in each direction.
32
18
  pub fn create_pipe(buffer_size: usize) -> (DuplexStream, DuplexStream) {
33
19
  duplex(buffer_size)
34
20
  }
@@ -38,21 +24,8 @@ pub fn create_pipe_default() -> (DuplexStream, DuplexStream) {
38
24
  create_pipe(64 * 1024)
39
25
  }
40
26
 
41
- /// In-memory stream opener for testing.
42
- ///
43
- /// This opener can hold multiple streams and returns them one at a time
44
- /// on each call to `open_stream`.
45
- ///
46
- /// # Example
47
- ///
48
- /// ```ignore
49
- /// let (client_stream, server_stream) = create_pipe_default();
50
- ///
51
- /// let opener = InMemoryOpener::new(vec![client_stream]);
52
- /// let client = SrpcClient::new(opener);
53
- ///
54
- /// // The client will use client_stream for its RPC
55
- /// ```
27
+ /// In-memory stream opener for testing that holds multiple streams and
28
+ /// returns them one at a time on each call to `open_stream`.
56
29
  pub struct InMemoryOpener {
57
30
  streams: Arc<Mutex<Vec<DuplexStream>>>,
58
31
  }
@@ -108,40 +81,15 @@ impl SingleInMemoryOpener {
108
81
  #[async_trait]
109
82
  impl OpenStream for SingleInMemoryOpener {
110
83
  async fn open_stream(&self) -> Result<(Arc<dyn PacketWriter>, PacketReceiver)> {
111
- let stream = self
112
- .stream
113
- .lock()
114
- .await
115
- .take()
116
- .ok_or(Error::StreamClosed)?;
84
+ let stream = self.stream.lock().await.take().ok_or(Error::StreamClosed)?;
117
85
 
118
86
  let (read_half, write_half) = tokio::io::split(stream);
119
87
  Ok(create_packet_channel(read_half, write_half))
120
88
  }
121
89
  }
122
90
 
123
- /// Creates a connected client-server test setup.
124
- ///
125
- /// Returns (client_opener, server_stream) where:
126
- /// - `client_opener` can be used to create a `SrpcClient`
127
- /// - `server_stream` can be passed to `Server::handle_stream`
128
- ///
129
- /// # Example
130
- ///
131
- /// ```ignore
132
- /// let (opener, server_stream) = create_test_pair();
133
- ///
134
- /// let client = SrpcClient::new(opener);
135
- /// let server = Server::new(mux);
136
- ///
137
- /// // Spawn server
138
- /// tokio::spawn(async move {
139
- /// server.handle_stream(server_stream).await
140
- /// });
141
- ///
142
- /// // Use client
143
- /// let response = client.exec_call("svc", "method", &request).await?;
144
- /// ```
91
+ /// Creates a connected client-server test setup: an opener for a
92
+ /// `SrpcClient` and the server stream for `Server::handle_stream`.
145
93
  pub fn create_test_pair() -> (SingleInMemoryOpener, DuplexStream) {
146
94
  let (client_stream, server_stream) = create_pipe_default();
147
95
  (SingleInMemoryOpener::new(client_stream), server_stream)
@@ -225,13 +173,10 @@ mod tests {
225
173
  stream.close_send().await.unwrap();
226
174
 
227
175
  // Read response with a timeout to handle potential races.
228
- let response = tokio::time::timeout(
229
- std::time::Duration::from_secs(5),
230
- stream.recv_bytes(),
231
- )
232
- .await
233
- .expect("timeout")
234
- .expect("recv_bytes failed");
176
+ let response = tokio::time::timeout(std::time::Duration::from_secs(5), stream.recv_bytes())
177
+ .await
178
+ .expect("timeout")
179
+ .expect("recv_bytes failed");
235
180
  assert_eq!(&response[..], b"hello");
236
181
 
237
182
  // Wait for server to complete.
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
- /// Receiver for incoming packets from a transport.
72
- pub type PacketReceiver = tokio::sync::mpsc::Receiver<Packet>;
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
- /// Sender for incoming packets to be processed.
75
- pub type PacketSender = tokio::sync::mpsc::Sender<Packet>;
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
- let (tx, rx) = tokio::sync::mpsc::channel(DEFAULT_CHANNEL_BUFFER);
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
- /// Inverse of `encode_optional_data`.
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 adapters for starpc.
1
+ //! Owned WebSocket byte transport for StarPC.
2
2
 
3
- use bytes::Bytes;
4
- use futures::{SinkExt, StreamExt};
5
- use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, DuplexStream};
6
- use tokio_tungstenite::{tungstenite::Message, WebSocketStream};
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
- /// WEBSOCKET_BYTE_STREAM_BUFFER is the duplex buffer used by WebSocket byte adapters.
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
- /// websocket_byte_stream adapts a binary WebSocket stream to Tokio byte I/O.
15
- ///
16
- /// The adapter treats incoming binary WebSocket messages as consecutive bytes
17
- /// and writes outbound byte chunks as binary WebSocket messages. Control and
18
- /// text messages are ignored except that close terminates the byte stream.
19
- pub fn websocket_byte_stream<S>(socket: WebSocketStream<S>) -> DuplexStream
20
- where
21
- S: AsyncRead + AsyncWrite + Send + Unpin + 'static,
22
- {
23
- let (mut socket_writer, mut socket_reader) = socket.split();
24
- let (stream, peer) = tokio::io::duplex(WEBSOCKET_BYTE_STREAM_BUFFER);
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
- tokio::spawn(async move {
28
- while let Some(result) = socket_reader.next().await {
29
- match result {
30
- Ok(message) if message.is_binary() => {
31
- let data = message.into_data();
32
- if peer_writer.write_all(data.as_ref()).await.is_err() {
33
- break;
34
- }
35
- if peer_writer.flush().await.is_err() {
36
- break;
37
- }
38
- }
39
- Ok(message) if message.is_close() => break,
40
- Ok(_) => {}
41
- Err(_) => break,
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
- tokio::spawn(async move {
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
- match peer_reader.read(&mut buf).await {
51
- Ok(0) => {
52
- let _ = socket_writer.close().await;
53
- break;
54
- }
55
- Ok(n) => {
56
- let message = Message::Binary(Bytes::copy_from_slice(&buf[..n]));
57
- if socket_writer.send(message).await.is_err() {
58
- break;
59
- }
60
- }
61
- Err(_) => {
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
- stream
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
- // PacketWriter is the interface used to write messages to a PacketStream.
15
- // Matches Go interface in writer.go
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
- virtual ~PacketWriter() = default;
22
+ virtual ~PacketWriter() = default;
19
23
 
20
- // WritePacket writes a packet to the remote.
21
- virtual Error WritePacket(const srpc::Packet &pkt) = 0;
24
+ /* WritePacket serializes pkt onto the transport. */
25
+ virtual Error WritePacket(const srpc::Packet &pkt) = 0;
22
26
 
23
- // Close closes the writer.
24
- virtual Error Close() = 0;
27
+ /* Close interrupts the transport. Repeated calls are safe. */
28
+ virtual Error Close() = 0;
25
29
  };
26
30
 
27
- // PacketWriterWithClose wraps a PacketWriter with an additional close function.
28
- // Matches packetWriterWithClose in writer.go
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
- PacketWriterWithClose(std::unique_ptr<PacketWriter> inner,
32
- std::function<Error()> close_fn)
33
- : inner_(std::move(inner)), close_fn_(std::move(close_fn)) {}
34
-
35
- Error WritePacket(const srpc::Packet &pkt) override {
36
- return inner_->WritePacket(pkt);
37
- }
38
-
39
- Error Close() override {
40
- Error err = inner_->Close();
41
- Error err2 = close_fn_();
42
- if (err != Error::OK)
43
- return err;
44
- return err2;
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
- std::unique_ptr<PacketWriter> inner_;
49
- std::function<Error()> close_fn_;
61
+ std::unique_ptr<PacketWriter> inner;
62
+ std::function<Error()> close_fn;
50
63
  };
51
64
 
52
- // NewPacketWriterWithClose wraps a PacketWriter with a close function.
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
- std::function<Error()> close_fn) {
56
- return std::make_unique<PacketWriterWithClose>(std::move(prw),
57
- std::move(close_fn));
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