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
@@ -1,7 +1,14 @@
1
1
  //go:build deps_only
2
2
 
3
- // C++ TCP integration server for cross-language testing.
4
- // Listens on TCP, handles one RPC per connection using length-prefixed packets.
3
+ /*
4
+ * C++ TCP integration server for cross-language testing: it listens on TCP
5
+ * and serves one RPC per connection over length-prefixed packets.
6
+ */
7
+
8
+ #include "echo/echo_srpc.pb.hpp"
9
+ #include "rpcstream/rpcstream.hpp"
10
+ #include "srpc/rpcproto.pb.h"
11
+ #include "srpc/starpc.hpp"
5
12
 
6
13
  #include <arpa/inet.h>
7
14
  #include <csignal>
@@ -13,209 +20,238 @@
13
20
  #include <sys/socket.h>
14
21
  #include <thread>
15
22
  #include <unistd.h>
16
-
17
- #include "echo/echo_srpc.pb.hpp"
18
- #include "rpcstream/rpcstream.hpp"
19
- #include "srpc/rpcproto.pb.h"
20
- #include "srpc/starpc.hpp"
23
+ #include <vector>
21
24
 
22
25
  namespace {
23
26
 
24
- // ReadFull reads exactly n bytes from fd.
25
- bool ReadFull(int fd, void *buf, size_t n) {
26
- size_t total = 0;
27
- while (total < n) {
28
- ssize_t r = read(fd, static_cast<char *>(buf) + total, n - total);
29
- if (r <= 0)
30
- return false;
31
- total += r;
32
- }
33
- return true;
27
+ /* ReadFull reads exactly n bytes from fd, reporting short reads as false. */
28
+ bool ReadFull(int fd, void *buf, size_t n)
29
+ {
30
+ size_t total = 0;
31
+ while (total < n) {
32
+ ssize_t r =
33
+ read(fd, static_cast<char *>(buf) + total, n - total);
34
+ if (r <= 0)
35
+ return false;
36
+ total += r;
37
+ }
38
+ return true;
34
39
  }
35
40
 
36
- // WriteFull writes exactly n bytes to fd.
37
- bool WriteFull(int fd, const void *buf, size_t n) {
38
- size_t total = 0;
39
- while (total < n) {
40
- ssize_t w = write(fd, static_cast<const char *>(buf) + total, n - total);
41
- if (w <= 0)
42
- return false;
43
- total += w;
44
- }
45
- return true;
41
+ /* WriteFull writes exactly n bytes to fd, reporting short writes as false. */
42
+ bool WriteFull(int fd, const void *buf, size_t n)
43
+ {
44
+ size_t total = 0;
45
+ while (total < n) {
46
+ ssize_t w = write(fd, static_cast<const char *>(buf) + total,
47
+ n - total);
48
+ if (w <= 0)
49
+ return false;
50
+ total += w;
51
+ }
52
+ return true;
46
53
  }
47
54
 
48
- // TcpPacketWriter writes length-prefixed packets to a TCP socket.
55
+ /*
56
+ * TcpPacketWriter writes length-prefixed packets to a TCP socket. The fd is
57
+ * borrowed and closed by the connection handler, not the writer.
58
+ */
49
59
  class TcpPacketWriter : public starpc::PacketWriter {
50
60
  public:
51
- explicit TcpPacketWriter(int fd) : fd_(fd) {}
52
-
53
- starpc::Error WritePacket(const srpc::Packet &pkt) override {
54
- std::lock_guard<std::mutex> lock(mtx_);
55
- std::string data;
56
- if (!pkt.SerializeToString(&data))
57
- return starpc::Error::InvalidMessage;
58
-
59
- uint32_t len = static_cast<uint32_t>(data.size());
60
- // Write LE uint32 length prefix.
61
- if (!WriteFull(fd_, &len, 4))
62
- return starpc::Error::EOF_;
63
- if (!WriteFull(fd_, data.data(), data.size()))
64
- return starpc::Error::EOF_;
65
- return starpc::Error::OK;
66
- }
67
-
68
- starpc::Error Close() override {
69
- shutdown(fd_, SHUT_WR);
70
- return starpc::Error::OK;
71
- }
61
+ explicit TcpPacketWriter(int fd)
62
+ : fd(fd)
63
+ {
64
+ }
65
+
66
+ starpc::Error WritePacket(const srpc::Packet &pkt) override
67
+ {
68
+ std::lock_guard<std::mutex> lock(mu);
69
+
70
+ std::string data;
71
+ if (!pkt.SerializeToString(&data))
72
+ return starpc::Error::InvalidMessage;
73
+
74
+ /* The length prefix is a little-endian uint32. */
75
+ uint32_t len = static_cast<uint32_t>(data.size());
76
+ if (!WriteFull(fd, &len, 4))
77
+ return starpc::Error::EOF_;
78
+ if (!WriteFull(fd, data.data(), data.size()))
79
+ return starpc::Error::EOF_;
80
+ return starpc::Error::OK;
81
+ }
82
+
83
+ starpc::Error Close() override
84
+ {
85
+ shutdown(fd, SHUT_WR);
86
+ return starpc::Error::OK;
87
+ }
72
88
 
73
89
  private:
74
- int fd_;
75
- std::mutex mtx_;
90
+ int fd;
91
+ std::mutex mu;
76
92
  };
77
93
 
78
- // EchoServerImpl implements the echo service.
94
+ /* EchoServerImpl implements the echo service under test. */
79
95
  class EchoServerImpl : public echo::SRPCEchoerServer {
80
96
  public:
81
- starpc::Error Echo(const echo::EchoMsg &req, echo::EchoMsg *resp) override {
82
- resp->set_body(req.body());
83
- return starpc::Error::OK;
84
- }
85
-
86
- starpc::Error
87
- EchoServerStream(const echo::EchoMsg &req,
88
- echo::SRPCEchoer_EchoServerStreamStream *strm) override {
89
- for (int i = 0; i < 5; i++) {
90
- echo::EchoMsg msg;
91
- msg.set_body(req.body());
92
- starpc::Error err = strm->Send(msg);
93
- if (err != starpc::Error::OK)
94
- return err;
95
- }
96
- return starpc::Error::OK;
97
- }
98
-
99
- starpc::Error EchoClientStream(echo::SRPCEchoer_EchoClientStreamStream *strm,
100
- echo::EchoMsg *resp) override {
101
- echo::EchoMsg msg;
102
- starpc::Error err = strm->Recv(&msg);
103
- if (err != starpc::Error::OK)
104
- return err;
105
- resp->set_body(msg.body());
106
- return starpc::Error::OK;
107
- }
108
-
109
- starpc::Error
110
- EchoBidiStream(echo::SRPCEchoer_EchoBidiStreamStream *strm) override {
111
- // Send initial message (matches Go server behavior).
112
- echo::EchoMsg init;
113
- init.set_body("hello from server");
114
- starpc::Error err = strm->Send(init);
115
- if (err != starpc::Error::OK)
116
- return err;
117
-
118
- while (true) {
119
- echo::EchoMsg msg;
120
- err = strm->Recv(&msg);
121
- if (err == starpc::Error::EOF_)
122
- break;
123
- if (err != starpc::Error::OK)
124
- return err;
125
- err = strm->Send(msg);
126
- if (err != starpc::Error::OK)
127
- return err;
128
- }
129
- return starpc::Error::OK;
130
- }
131
-
132
- starpc::Error RpcStream(echo::SRPCEchoer_RpcStreamStream *) override {
133
- return starpc::Error::Unimplemented;
134
- }
135
-
136
- starpc::Error DoNothing(const google::protobuf::Empty &,
137
- google::protobuf::Empty *) override {
138
- return starpc::Error::OK;
139
- }
97
+ starpc::Error Echo(const echo::EchoMsg &req,
98
+ echo::EchoMsg *resp) override
99
+ {
100
+ resp->set_body(req.body());
101
+ return starpc::Error::OK;
102
+ }
103
+
104
+ starpc::Error
105
+ EchoServerStream(const echo::EchoMsg &req,
106
+ echo::SRPCEchoer_EchoServerStreamStream *strm) override
107
+ {
108
+ for (int i = 0; i < 5; i++) {
109
+ echo::EchoMsg msg;
110
+ msg.set_body(req.body());
111
+ starpc::Error err = strm->Send(msg);
112
+ if (err != starpc::Error::OK)
113
+ return err;
114
+ }
115
+ return starpc::Error::OK;
116
+ }
117
+
118
+ starpc::Error
119
+ EchoClientStream(echo::SRPCEchoer_EchoClientStreamStream *strm,
120
+ echo::EchoMsg *resp) override
121
+ {
122
+ echo::EchoMsg msg;
123
+ starpc::Error err = strm->Recv(&msg);
124
+ if (err != starpc::Error::OK)
125
+ return err;
126
+ resp->set_body(msg.body());
127
+ return starpc::Error::OK;
128
+ }
129
+
130
+ starpc::Error
131
+ EchoBidiStream(echo::SRPCEchoer_EchoBidiStreamStream *strm) override
132
+ {
133
+ /* Send the initial message the other clients expect. */
134
+ echo::EchoMsg init;
135
+ init.set_body("hello from server");
136
+ starpc::Error err = strm->Send(init);
137
+ if (err != starpc::Error::OK)
138
+ return err;
139
+
140
+ while (true) {
141
+ echo::EchoMsg msg;
142
+ err = strm->Recv(&msg);
143
+ if (err == starpc::Error::EOF_)
144
+ break;
145
+ if (err != starpc::Error::OK)
146
+ return err;
147
+ err = strm->Send(msg);
148
+ if (err != starpc::Error::OK)
149
+ return err;
150
+ }
151
+ return starpc::Error::OK;
152
+ }
153
+
154
+ starpc::Error RpcStream(echo::SRPCEchoer_RpcStreamStream *) override
155
+ {
156
+ return starpc::Error::Unimplemented;
157
+ }
158
+
159
+ starpc::Error DoNothing(const google::protobuf::Empty &,
160
+ google::protobuf::Empty *) override
161
+ {
162
+ return starpc::Error::OK;
163
+ }
140
164
  };
141
165
 
142
- // HandleConnection handles one TCP connection (one RPC).
143
- void HandleConnection(int fd, starpc::Mux *mux) {
144
- auto writer = std::make_unique<TcpPacketWriter>(fd);
145
- auto serverRpc = starpc::NewServerRPC(mux, writer.get());
146
-
147
- while (true) {
148
- // Read 4-byte LE uint32 length prefix.
149
- uint32_t len = 0;
150
- if (!ReadFull(fd, &len, 4))
151
- break;
152
-
153
- // Read the packet data.
154
- std::string data(len, '\0');
155
- if (!ReadFull(fd, data.data(), len))
156
- break;
157
-
158
- starpc::Error err = serverRpc->HandlePacketData(data);
159
- if (err != starpc::Error::OK && err != starpc::Error::Completed)
160
- break;
161
- }
162
-
163
- close(fd);
166
+ /*
167
+ * HandleConnection serves one TCP connection: it reads length-prefixed
168
+ * packets until the socket ends, then closes the fd.
169
+ */
170
+ void HandleConnection(int fd, starpc::Mux *mux)
171
+ {
172
+ auto writer = std::make_unique<TcpPacketWriter>(fd);
173
+ auto server_rpc = starpc::NewServerRPC(mux, writer.get());
174
+
175
+ while (true) {
176
+ /* The length prefix is a little-endian uint32. */
177
+ uint32_t len = 0;
178
+ if (!ReadFull(fd, &len, 4))
179
+ break;
180
+
181
+ std::string data(len, '\0');
182
+ if (!ReadFull(fd, data.data(), len))
183
+ break;
184
+
185
+ starpc::Error err = server_rpc->HandlePacketData(data);
186
+ if (err != starpc::Error::OK && err != starpc::Error::Completed)
187
+ break;
188
+ }
189
+
190
+ close(fd);
164
191
  }
165
192
 
166
193
  } // namespace
167
194
 
168
- int main() {
169
- signal(SIGPIPE, SIG_IGN);
170
-
171
- auto mux = starpc::NewMux();
172
- EchoServerImpl server;
173
- auto [handler, err] = echo::SRPCRegisterEchoer(mux.get(), &server);
174
- (void)handler;
175
- if (err != starpc::Error::OK) {
176
- std::cerr << "register error: " << starpc::ErrorString(err) << std::endl;
177
- return 1;
178
- }
179
-
180
- int sockfd = socket(AF_INET, SOCK_STREAM, 0);
181
- if (sockfd < 0) {
182
- std::cerr << "socket error" << std::endl;
183
- return 1;
184
- }
185
-
186
- int opt = 1;
187
- setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
188
-
189
- struct sockaddr_in addr {};
190
- addr.sin_family = AF_INET;
191
- addr.sin_port = 0; // OS-assigned port.
192
- addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
193
-
194
- if (bind(sockfd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) <
195
- 0) {
196
- std::cerr << "bind error" << std::endl;
197
- close(sockfd);
198
- return 1;
199
- }
200
-
201
- if (listen(sockfd, 10) < 0) {
202
- std::cerr << "listen error" << std::endl;
203
- close(sockfd);
204
- return 1;
205
- }
206
-
207
- // Get the assigned port.
208
- socklen_t addrLen = sizeof(addr);
209
- getsockname(sockfd, reinterpret_cast<struct sockaddr *>(&addr), &addrLen);
210
- std::cout << "LISTENING 127.0.0.1:" << ntohs(addr.sin_port) << std::endl;
211
-
212
- while (true) {
213
- int clientFd = accept(sockfd, nullptr, nullptr);
214
- if (clientFd < 0)
215
- break;
216
- std::thread(HandleConnection, clientFd, mux.get()).detach();
217
- }
218
-
219
- close(sockfd);
220
- return 0;
195
+ int main()
196
+ {
197
+ signal(SIGPIPE, SIG_IGN);
198
+
199
+ auto mux = starpc::NewMux();
200
+ EchoServerImpl server;
201
+ auto [handler, err] = echo::SRPCRegisterEchoer(mux.get(), &server);
202
+ (void)handler;
203
+ if (err != starpc::Error::OK) {
204
+ std::cerr << "register error: " << starpc::ErrorString(err)
205
+ << std::endl;
206
+ return 1;
207
+ }
208
+
209
+ int sockfd = socket(AF_INET, SOCK_STREAM, 0);
210
+ if (sockfd < 0) {
211
+ std::cerr << "socket error" << std::endl;
212
+ return 1;
213
+ }
214
+
215
+ int opt = 1;
216
+ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
217
+
218
+ struct sockaddr_in addr{};
219
+ addr.sin_family = AF_INET;
220
+ addr.sin_port = 0; /* OS-assigned port. */
221
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
222
+
223
+ if (bind(sockfd, reinterpret_cast<struct sockaddr *>(&addr),
224
+ sizeof(addr)) < 0) {
225
+ std::cerr << "bind error" << std::endl;
226
+ close(sockfd);
227
+ return 1;
228
+ }
229
+
230
+ if (listen(sockfd, 10) < 0) {
231
+ std::cerr << "listen error" << std::endl;
232
+ close(sockfd);
233
+ return 1;
234
+ }
235
+
236
+ /* Report the assigned port so the test driver can connect. */
237
+ socklen_t addr_len = sizeof(addr);
238
+ getsockname(sockfd, reinterpret_cast<struct sockaddr *>(&addr),
239
+ &addr_len);
240
+ std::cout << "LISTENING 127.0.0.1:" << ntohs(addr.sin_port)
241
+ << std::endl;
242
+
243
+ /* Connection threads borrow mux, so join them before it ends. */
244
+ std::vector<std::thread> connections;
245
+ while (true) {
246
+ int client_fd = accept(sockfd, nullptr, nullptr);
247
+ if (client_fd < 0)
248
+ break;
249
+ connections.emplace_back(HandleConnection, client_fd,
250
+ mux.get());
251
+ }
252
+
253
+ close(sockfd);
254
+ for (auto &connection : connections)
255
+ connection.join();
256
+ return 0;
221
257
  }
@@ -6,19 +6,23 @@ import (
6
6
  "io"
7
7
  "net"
8
8
  "os"
9
+ "strings"
9
10
 
10
11
  "github.com/aperturerobotics/starpc/echo"
12
+ "github.com/aperturerobotics/starpc/rpcstream"
11
13
  "github.com/aperturerobotics/starpc/srpc"
12
14
  )
13
15
 
14
16
  const bodyTxt = "hello world via starpc cross-language e2e test"
15
17
 
16
18
  func main() {
17
- if len(os.Args) <= 1 {
18
- fmt.Fprintf(os.Stderr, "usage: go-client <addr>\n")
19
+ nested := len(os.Args) >= 3 && os.Args[1] == "--nested"
20
+ nestedRelease := len(os.Args) >= 3 && os.Args[1] == "--nested-release"
21
+ if len(os.Args) != 2 && !nested && !nestedRelease {
22
+ fmt.Fprintf(os.Stderr, "usage: go-client [--nested] <addr>\n")
19
23
  os.Exit(1)
20
24
  }
21
- addr := os.Args[1]
25
+ addr := os.Args[len(os.Args)-1]
22
26
  openStream := func(ctx context.Context, msgHandler srpc.PacketDataHandler, closeHandler srpc.CloseHandler) (srpc.PacketWriter, error) {
23
27
  conn, err := net.Dial("tcp", addr) //nolint:gosec
24
28
  if err != nil {
@@ -48,6 +52,12 @@ func main() {
48
52
  fmt.Fprintf(os.Stderr, "bidi stream test failed: %v\n", err)
49
53
  os.Exit(1)
50
54
  }
55
+ if nested || nestedRelease {
56
+ if err := testNested(ctx, echoClient, nestedRelease); err != nil {
57
+ fmt.Fprintf(os.Stderr, "nested lifecycle test failed: %v\n", err)
58
+ os.Exit(1)
59
+ }
60
+ }
51
61
  fmt.Println("All tests passed.")
52
62
  }
53
63
 
@@ -143,3 +153,69 @@ func testBidiStream(ctx context.Context, client echo.SRPCEchoerClient) error {
143
153
  fmt.Println(" PASSED")
144
154
  return nil
145
155
  }
156
+
157
+ func testNested(ctx context.Context, parent echo.SRPCEchoerClient, release bool) error {
158
+ proxy := rpcstream.NewRpcStreamClient(parent.RpcStream, "test", true)
159
+ if err := testUnary(ctx, echo.NewSRPCEchoerClient(proxy)); err != nil {
160
+ return fmt.Errorf("nested unary: %w", err)
161
+ }
162
+
163
+ if release {
164
+ missing := rpcstream.NewRpcStreamClient(parent.RpcStream, "missing", true)
165
+ var missingOut echo.EchoMsg
166
+ if err := missing.ExecCall(ctx, "echo.Echoer", "Echo", &echo.EchoMsg{}, &missingOut); err == nil || !strings.Contains(err.Error(), "unknown component: missing") {
167
+ return fmt.Errorf("unknown component returned wrong result: %v", err)
168
+ }
169
+ }
170
+
171
+ stream, err := proxy.NewStream(ctx, "echo.Echoer", "EchoBidiStream", nil)
172
+ if err != nil {
173
+ return fmt.Errorf("nested bidi: %w", err)
174
+ }
175
+ if err := stream.MsgSend(&echo.EchoMsg{Body: "nested later data"}); err != nil {
176
+ return fmt.Errorf("nested later data: %w", err)
177
+ }
178
+ if err := stream.Close(); err != nil {
179
+ return fmt.Errorf("nested cancel: %w", err)
180
+ }
181
+
182
+ var terminal echo.EchoMsg
183
+ if err := proxy.ExecCall(ctx, "missing.Service", "Missing", &echo.EchoMsg{}, &terminal); err == nil || !strings.Contains(err.Error(), "missing.Service") {
184
+ return fmt.Errorf("unknown nested method returned wrong result: %v", err)
185
+ }
186
+
187
+ if release {
188
+ var terminalError echo.EchoMsg
189
+ if err := proxy.ExecCall(ctx, "echo.Echoer", "Echo", &echo.EchoMsg{Body: "__nested_error__"}, &terminalError); err == nil || !strings.Contains(err.Error(), "nested terminal error") {
190
+ return fmt.Errorf("terminal nested error returned wrong result: %v", err)
191
+ }
192
+ }
193
+
194
+ if release {
195
+ releaseClient := rpcstream.NewRpcStreamClient(parent.RpcStream, "release", true)
196
+ var released echo.EchoMsg
197
+ err := releaseClient.ExecCall(ctx, "echo.Echoer", "Echo", &echo.EchoMsg{Body: "__nested_release__"}, &released)
198
+ if err == nil || (!strings.Contains(err.Error(), "stream closed before the remote reported completion") && !strings.Contains(err.Error(), "EOF") && !strings.Contains(err.Error(), "canceled")) {
199
+ return fmt.Errorf("release during active call returned wrong result: %v", err)
200
+ }
201
+ status, err := parent.Echo(ctx, &echo.EchoMsg{Body: "__nested_release_status__"})
202
+ if err != nil {
203
+ return fmt.Errorf("release completion status failed: %w", err)
204
+ }
205
+ if status.GetBody() != "released" {
206
+ return fmt.Errorf("release completion returned %q", status.GetBody())
207
+ }
208
+ if err := releaseClient.ExecCall(ctx, "echo.Echoer", "Echo", &echo.EchoMsg{}, &released); err == nil || !strings.Contains(err.Error(), "unknown component: release") {
209
+ return fmt.Errorf("released component returned wrong result: %v", err)
210
+ }
211
+ }
212
+
213
+ direct, err := rpcstream.OpenRpcStream(ctx, parent.RpcStream, "test", true)
214
+ if err != nil {
215
+ return fmt.Errorf("nested direct open: %w", err)
216
+ }
217
+ if err := direct.Close(); err != nil {
218
+ return fmt.Errorf("nested abrupt close: %w", err)
219
+ }
220
+ return nil
221
+ }