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
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
//go:build deps_only
|
|
2
|
+
|
|
3
|
+
#include "echo/echo.pb.h"
|
|
4
|
+
#include "rpcstream/rpcstream.hpp"
|
|
5
|
+
#include "srpc/client.hpp"
|
|
6
|
+
#include "srpc/rpcproto.pb.h"
|
|
7
|
+
#include "srpc/server-rpc.hpp"
|
|
8
|
+
|
|
9
|
+
#include <atomic>
|
|
10
|
+
#include <cstdlib>
|
|
11
|
+
#include <future>
|
|
12
|
+
#include <iostream>
|
|
13
|
+
#include <latch>
|
|
14
|
+
|
|
15
|
+
namespace {
|
|
16
|
+
using starpc::Error;
|
|
17
|
+
|
|
18
|
+
void Check(bool value, const char *message)
|
|
19
|
+
{
|
|
20
|
+
if (value)
|
|
21
|
+
return;
|
|
22
|
+
std::cerr << message << '\n';
|
|
23
|
+
std::exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* Writer is a test PacketWriter: optional write and close callbacks, plus a
|
|
28
|
+
* count of closes for assertions.
|
|
29
|
+
*/
|
|
30
|
+
class Writer final : public starpc::PacketWriter {
|
|
31
|
+
public:
|
|
32
|
+
Error WritePacket(const srpc::Packet &packet) override
|
|
33
|
+
{
|
|
34
|
+
if (write)
|
|
35
|
+
return write(packet);
|
|
36
|
+
return Error::OK;
|
|
37
|
+
}
|
|
38
|
+
Error Close() override
|
|
39
|
+
{
|
|
40
|
+
++closes;
|
|
41
|
+
if (close)
|
|
42
|
+
close();
|
|
43
|
+
return Error::OK;
|
|
44
|
+
}
|
|
45
|
+
std::function<Error(const srpc::Packet &)> write;
|
|
46
|
+
std::function<void()> close;
|
|
47
|
+
std::atomic<int> closes = 0;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/* WaitingHandler blocks in MsgRecv until its test releases it. */
|
|
51
|
+
class WaitingHandler final : public starpc::Invoker {
|
|
52
|
+
public:
|
|
53
|
+
std::pair<bool, Error> InvokeMethod(const std::string &,
|
|
54
|
+
const std::string &,
|
|
55
|
+
starpc::Stream *stream) override
|
|
56
|
+
{
|
|
57
|
+
entered.count_down();
|
|
58
|
+
echo::EchoMsg message;
|
|
59
|
+
const auto err = stream->MsgRecv(&message);
|
|
60
|
+
exited = true;
|
|
61
|
+
return {true, err};
|
|
62
|
+
}
|
|
63
|
+
std::latch entered{1};
|
|
64
|
+
std::atomic<bool> exited = false;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/* DestroyWaitingServer proves destruction joins a blocked handler. */
|
|
68
|
+
void DestroyWaitingServer()
|
|
69
|
+
{
|
|
70
|
+
Writer writer;
|
|
71
|
+
WaitingHandler handler;
|
|
72
|
+
{
|
|
73
|
+
starpc::ServerRPC rpc(&handler, &writer);
|
|
74
|
+
srpc::CallStart start;
|
|
75
|
+
start.set_rpc_service("test");
|
|
76
|
+
start.set_rpc_method("wait");
|
|
77
|
+
Check(rpc.HandleCallStart(start) == Error::OK, "start handler");
|
|
78
|
+
handler.entered.wait();
|
|
79
|
+
}
|
|
80
|
+
Check(handler.exited,
|
|
81
|
+
"server destruction must join the canceled handler");
|
|
82
|
+
Check(writer.closes == 1, "writer closes once");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* ReentrantTransport proves a synchronous reply cannot deadlock the call. */
|
|
86
|
+
void ReentrantTransport()
|
|
87
|
+
{
|
|
88
|
+
starpc::ClientRPC rpc("test", "call");
|
|
89
|
+
Writer writer;
|
|
90
|
+
writer.close = [&] { rpc.HandleStreamClose(Error::OK); };
|
|
91
|
+
writer.write = [&](const srpc::Packet &) {
|
|
92
|
+
srpc::CallData data;
|
|
93
|
+
data.set_data("reply");
|
|
94
|
+
data.set_complete(true);
|
|
95
|
+
return rpc.HandleCallData(data);
|
|
96
|
+
};
|
|
97
|
+
Check(rpc.Start(&writer, false, "ignored") == Error::OK,
|
|
98
|
+
"synchronous reply must not deadlock");
|
|
99
|
+
std::string reply;
|
|
100
|
+
Check(rpc.ReadOne(&reply) == Error::OK && reply == "reply",
|
|
101
|
+
"deliver synchronous reply");
|
|
102
|
+
Check(rpc.ReadOne(&reply) == Error::EOF_, "explicit completion is EOF");
|
|
103
|
+
rpc.Close();
|
|
104
|
+
rpc.Close();
|
|
105
|
+
Check(writer.closes == 1,
|
|
106
|
+
"reentrant close must not repeat transport cleanup");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* CancelBlockedWriter proves cancellation interrupts a blocked write. */
|
|
110
|
+
void CancelBlockedWriter()
|
|
111
|
+
{
|
|
112
|
+
Writer writer;
|
|
113
|
+
starpc::ClientRPC rpc("test", "call");
|
|
114
|
+
Check(rpc.Start(&writer, false, "") == Error::OK,
|
|
115
|
+
"start blocked writer test");
|
|
116
|
+
std::latch writing{1}, closed{1};
|
|
117
|
+
writer.write = [&](const srpc::Packet &) {
|
|
118
|
+
writing.count_down();
|
|
119
|
+
closed.wait();
|
|
120
|
+
return Error::Canceled;
|
|
121
|
+
};
|
|
122
|
+
writer.close = [&] { closed.count_down(); };
|
|
123
|
+
std::jthread sender([&] {
|
|
124
|
+
(void)rpc.WriteCallData("pending", false, false, Error::OK);
|
|
125
|
+
});
|
|
126
|
+
writing.wait();
|
|
127
|
+
rpc.Close();
|
|
128
|
+
sender.join();
|
|
129
|
+
Check(writer.closes == 1,
|
|
130
|
+
"cancellation must interrupt a blocked write");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* DisconnectAndErrors pins how disconnects and remote errors surface. */
|
|
134
|
+
void DisconnectAndErrors()
|
|
135
|
+
{
|
|
136
|
+
starpc::ClientRPC rpc("test", "call");
|
|
137
|
+
rpc.HandleStreamClose(Error::EOF_);
|
|
138
|
+
std::string message;
|
|
139
|
+
Check(rpc.ReadOne(&message) == Error::ClosedBeforeCompletion,
|
|
140
|
+
"abrupt EOF is not completion");
|
|
141
|
+
|
|
142
|
+
starpc::ClientRPC failed("test", "call");
|
|
143
|
+
srpc::CallData data;
|
|
144
|
+
data.set_error("specific server failure");
|
|
145
|
+
Check(failed.HandleCallData(data) == Error::OK, "receive server error");
|
|
146
|
+
Check(failed.ReadOne(&message) == Error::RemoteError,
|
|
147
|
+
"preserve remote error kind");
|
|
148
|
+
Check(failed.RemoteErrorMessage() == data.error(),
|
|
149
|
+
"preserve remote diagnostic");
|
|
150
|
+
auto messages = starpc::NewMsgStream(&failed, [&] { failed.Close(); });
|
|
151
|
+
Check(messages->RemoteErrorMessage() == data.error(),
|
|
152
|
+
"stream exposes the remote diagnostic");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* StreamDestruction proves each stream releases its writer exactly once. */
|
|
156
|
+
void StreamDestruction()
|
|
157
|
+
{
|
|
158
|
+
int destroyed = 0;
|
|
159
|
+
struct CountedWriter final : starpc::PacketWriter {
|
|
160
|
+
explicit CountedWriter(int &count)
|
|
161
|
+
: count(count)
|
|
162
|
+
{
|
|
163
|
+
}
|
|
164
|
+
~CountedWriter() override
|
|
165
|
+
{
|
|
166
|
+
++count;
|
|
167
|
+
}
|
|
168
|
+
Error WritePacket(const srpc::Packet &) override
|
|
169
|
+
{
|
|
170
|
+
return Error::OK;
|
|
171
|
+
}
|
|
172
|
+
Error Close() override
|
|
173
|
+
{
|
|
174
|
+
return Error::OK;
|
|
175
|
+
}
|
|
176
|
+
int &count;
|
|
177
|
+
};
|
|
178
|
+
auto client = starpc::NewClient([&](auto, auto) {
|
|
179
|
+
return std::make_pair(
|
|
180
|
+
std::make_unique<CountedWriter>(destroyed), Error::OK);
|
|
181
|
+
});
|
|
182
|
+
{
|
|
183
|
+
auto [stream, err] = client->NewStream("test", "call", nullptr);
|
|
184
|
+
Check(err == Error::OK, "open stream");
|
|
185
|
+
stream->Close();
|
|
186
|
+
stream->Close();
|
|
187
|
+
}
|
|
188
|
+
Check(destroyed == 1, "stream must destroy its writer exactly once");
|
|
189
|
+
{
|
|
190
|
+
auto [stream, err] = client->NewStream("test", "call", nullptr);
|
|
191
|
+
Check(err == Error::OK, "open automatically closed stream");
|
|
192
|
+
}
|
|
193
|
+
Check(destroyed == 2, "dropping a stream must release its writer");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* BoundReceiveQueue pins the receive queue's byte and message bounds. */
|
|
197
|
+
void BoundReceiveQueue()
|
|
198
|
+
{
|
|
199
|
+
starpc::ClientRPC rpc("test", "call");
|
|
200
|
+
srpc::CallData data;
|
|
201
|
+
data.set_data(std::string(4 * 1024 * 1024 + 1, 'x'));
|
|
202
|
+
Check(rpc.HandleCallData(data) == Error::ResourceExhausted,
|
|
203
|
+
"bound unread message bytes");
|
|
204
|
+
starpc::ClientRPC empty("test", "call");
|
|
205
|
+
data.clear_data();
|
|
206
|
+
data.set_data_is_zero(true);
|
|
207
|
+
for (int i = 0; i < 1024; ++i) {
|
|
208
|
+
Check(empty.HandleCallData(data) == Error::OK,
|
|
209
|
+
"queue empty message");
|
|
210
|
+
}
|
|
211
|
+
Check(empty.HandleCallData(data) == Error::ResourceExhausted,
|
|
212
|
+
"bound empty message count");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/*
|
|
216
|
+
* NestedReleaseWaitsForHandler proves the Invoker is released only after the
|
|
217
|
+
* nested handler returns.
|
|
218
|
+
*/
|
|
219
|
+
void NestedReleaseWaitsForHandler()
|
|
220
|
+
{
|
|
221
|
+
WaitingHandler handler;
|
|
222
|
+
class DisconnectStream final : public rpcstream::RpcStream {
|
|
223
|
+
public:
|
|
224
|
+
explicit DisconnectStream(WaitingHandler &handler)
|
|
225
|
+
: handler(handler)
|
|
226
|
+
{
|
|
227
|
+
}
|
|
228
|
+
Error Send(const rpcstream::RpcStreamPacket &) override
|
|
229
|
+
{
|
|
230
|
+
return Error::OK;
|
|
231
|
+
}
|
|
232
|
+
Error CloseSend() override
|
|
233
|
+
{
|
|
234
|
+
return Error::OK;
|
|
235
|
+
}
|
|
236
|
+
Error Close() override
|
|
237
|
+
{
|
|
238
|
+
return Error::OK;
|
|
239
|
+
}
|
|
240
|
+
Error Recv(rpcstream::RpcStreamPacket *packet) override
|
|
241
|
+
{
|
|
242
|
+
packet->Clear();
|
|
243
|
+
if (reads++ == 0) {
|
|
244
|
+
packet->mutable_init()->set_component_id(
|
|
245
|
+
"component");
|
|
246
|
+
return Error::OK;
|
|
247
|
+
}
|
|
248
|
+
if (reads == 2) {
|
|
249
|
+
packet->set_data(
|
|
250
|
+
starpc::NewCallStartPacket(
|
|
251
|
+
"test", "wait", "", false)
|
|
252
|
+
->SerializeAsString());
|
|
253
|
+
return Error::OK;
|
|
254
|
+
}
|
|
255
|
+
handler.entered.wait();
|
|
256
|
+
return Error::EOF_;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private:
|
|
260
|
+
WaitingHandler &handler;
|
|
261
|
+
int reads = 0;
|
|
262
|
+
};
|
|
263
|
+
bool released = false;
|
|
264
|
+
auto stream = std::make_shared<DisconnectStream>(handler);
|
|
265
|
+
const auto err =
|
|
266
|
+
rpcstream::HandleRpcStream(stream, [&](const std::string &) {
|
|
267
|
+
return std::make_tuple(
|
|
268
|
+
&handler,
|
|
269
|
+
[&] {
|
|
270
|
+
Check(handler.exited,
|
|
271
|
+
"nested handler must return "
|
|
272
|
+
"before its service is released");
|
|
273
|
+
released = true;
|
|
274
|
+
},
|
|
275
|
+
Error::OK);
|
|
276
|
+
});
|
|
277
|
+
Check(err == Error::OK && released,
|
|
278
|
+
"nested disconnect must release its service");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* ReceiveThreadJoinsOnDestruction proves writer destruction joins the pump. */
|
|
282
|
+
void ReceiveThreadJoinsOnDestruction()
|
|
283
|
+
{
|
|
284
|
+
class WaitingStream final : public rpcstream::RpcStream {
|
|
285
|
+
public:
|
|
286
|
+
Error Send(const rpcstream::RpcStreamPacket &) override
|
|
287
|
+
{
|
|
288
|
+
return Error::OK;
|
|
289
|
+
}
|
|
290
|
+
Error Recv(rpcstream::RpcStreamPacket *) override
|
|
291
|
+
{
|
|
292
|
+
entered.count_down();
|
|
293
|
+
closed.wait();
|
|
294
|
+
return Error::EOF_;
|
|
295
|
+
}
|
|
296
|
+
Error CloseSend() override
|
|
297
|
+
{
|
|
298
|
+
return Close();
|
|
299
|
+
}
|
|
300
|
+
Error Close() override
|
|
301
|
+
{
|
|
302
|
+
if (!stopped.exchange(true))
|
|
303
|
+
closed.count_down();
|
|
304
|
+
return Error::OK;
|
|
305
|
+
}
|
|
306
|
+
std::latch entered{1}, closed{1};
|
|
307
|
+
std::atomic<bool> stopped{false};
|
|
308
|
+
};
|
|
309
|
+
auto stream = std::make_shared<WaitingStream>();
|
|
310
|
+
std::atomic<bool> returned{false};
|
|
311
|
+
auto [writer, started] = rpcstream::StartReadPump(
|
|
312
|
+
stream, [](const std::string &) { return Error::OK; },
|
|
313
|
+
[&](Error) { returned = true; });
|
|
314
|
+
Check(started == Error::OK, "start receive thread");
|
|
315
|
+
stream->entered.wait();
|
|
316
|
+
writer.reset();
|
|
317
|
+
Check(returned, "writer destruction must join its receive callback");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
} // namespace
|
|
321
|
+
|
|
322
|
+
int main()
|
|
323
|
+
{
|
|
324
|
+
DestroyWaitingServer();
|
|
325
|
+
ReentrantTransport();
|
|
326
|
+
CancelBlockedWriter();
|
|
327
|
+
DisconnectAndErrors();
|
|
328
|
+
StreamDestruction();
|
|
329
|
+
BoundReceiveQueue();
|
|
330
|
+
NestedReleaseWaitsForHandler();
|
|
331
|
+
ReceiveThreadJoinsOnDestruction();
|
|
332
|
+
std::cout << "RPC lifecycle checks passed\n";
|
|
333
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
+
# source: github.com/aperturerobotics/starpc/srpc/rpcproto.proto
|
|
5
|
+
# Protobuf Python Version: 6.33.4
|
|
6
|
+
"""Generated protocol buffer code."""
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
|
+
_runtime_version.Domain.PUBLIC,
|
|
14
|
+
6,
|
|
15
|
+
33,
|
|
16
|
+
4,
|
|
17
|
+
'',
|
|
18
|
+
'github.com/aperturerobotics/starpc/srpc/rpcproto.proto'
|
|
19
|
+
)
|
|
20
|
+
# @@protoc_insertion_point(imports)
|
|
21
|
+
|
|
22
|
+
_sym_db = _symbol_database.Default()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n6github.com/aperturerobotics/starpc/srpc/rpcproto.proto\x12\x04srpc\"s\n\x06Packet\x12%\n\ncall_start\x18\x01 \x01(\x0b\x32\x0f.srpc.CallStartH\x00\x12#\n\tcall_data\x18\x02 \x01(\x0b\x32\x0e.srpc.CallDataH\x00\x12\x15\n\x0b\x63\x61ll_cancel\x18\x03 \x01(\x08H\x00\x42\x06\n\x04\x62ody\"X\n\tCallStart\x12\x13\n\x0brpc_service\x18\x01 \x01(\t\x12\x12\n\nrpc_method\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\x12\x14\n\x0c\x64\x61ta_is_zero\x18\x04 \x01(\x08\"O\n\x08\x43\x61llData\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x14\n\x0c\x64\x61ta_is_zero\x18\x02 \x01(\x08\x12\x10\n\x08\x63omplete\x18\x03 \x01(\x08\x12\r\n\x05\x65rror\x18\x04 \x01(\tb\x06proto3')
|
|
28
|
+
|
|
29
|
+
_globals = globals()
|
|
30
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'github.com.aperturerobotics.starpc.srpc.rpcproto_pb2', _globals)
|
|
32
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
|
+
DESCRIPTOR._loaded_options = None
|
|
34
|
+
_globals['_PACKET']._serialized_start=64
|
|
35
|
+
_globals['_PACKET']._serialized_end=179
|
|
36
|
+
_globals['_CALLSTART']._serialized_start=181
|
|
37
|
+
_globals['_CALLSTART']._serialized_end=269
|
|
38
|
+
_globals['_CALLDATA']._serialized_start=271
|
|
39
|
+
_globals['_CALLDATA']._serialized_end=350
|
|
40
|
+
# @@protoc_insertion_point(module_scope)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from google.protobuf import descriptor as _descriptor
|
|
2
|
+
from google.protobuf import message as _message
|
|
3
|
+
from collections.abc import Mapping as _Mapping
|
|
4
|
+
from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union
|
|
5
|
+
|
|
6
|
+
DESCRIPTOR: _descriptor.FileDescriptor
|
|
7
|
+
|
|
8
|
+
class Packet(_message.Message):
|
|
9
|
+
__slots__ = ("call_start", "call_data", "call_cancel")
|
|
10
|
+
CALL_START_FIELD_NUMBER: _ClassVar[int]
|
|
11
|
+
CALL_DATA_FIELD_NUMBER: _ClassVar[int]
|
|
12
|
+
CALL_CANCEL_FIELD_NUMBER: _ClassVar[int]
|
|
13
|
+
call_start: CallStart
|
|
14
|
+
call_data: CallData
|
|
15
|
+
call_cancel: bool
|
|
16
|
+
def __init__(self, call_start: _Optional[_Union[CallStart, _Mapping]] = ..., call_data: _Optional[_Union[CallData, _Mapping]] = ..., call_cancel: _Optional[bool] = ...) -> None: ...
|
|
17
|
+
|
|
18
|
+
class CallStart(_message.Message):
|
|
19
|
+
__slots__ = ("rpc_service", "rpc_method", "data", "data_is_zero")
|
|
20
|
+
RPC_SERVICE_FIELD_NUMBER: _ClassVar[int]
|
|
21
|
+
RPC_METHOD_FIELD_NUMBER: _ClassVar[int]
|
|
22
|
+
DATA_FIELD_NUMBER: _ClassVar[int]
|
|
23
|
+
DATA_IS_ZERO_FIELD_NUMBER: _ClassVar[int]
|
|
24
|
+
rpc_service: str
|
|
25
|
+
rpc_method: str
|
|
26
|
+
data: bytes
|
|
27
|
+
data_is_zero: bool
|
|
28
|
+
def __init__(self, rpc_service: _Optional[str] = ..., rpc_method: _Optional[str] = ..., data: _Optional[bytes] = ..., data_is_zero: _Optional[bool] = ...) -> None: ...
|
|
29
|
+
|
|
30
|
+
class CallData(_message.Message):
|
|
31
|
+
__slots__ = ("data", "data_is_zero", "complete", "error")
|
|
32
|
+
DATA_FIELD_NUMBER: _ClassVar[int]
|
|
33
|
+
DATA_IS_ZERO_FIELD_NUMBER: _ClassVar[int]
|
|
34
|
+
COMPLETE_FIELD_NUMBER: _ClassVar[int]
|
|
35
|
+
ERROR_FIELD_NUMBER: _ClassVar[int]
|
|
36
|
+
data: bytes
|
|
37
|
+
data_is_zero: bool
|
|
38
|
+
complete: bool
|
|
39
|
+
error: str
|
|
40
|
+
def __init__(self, data: _Optional[bytes] = ..., data_is_zero: _Optional[bool] = ..., complete: _Optional[bool] = ..., error: _Optional[str] = ...) -> None: ...
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
package srpc
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
"testing"
|
|
7
|
+
"time"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestServerCancellationRetainsIdentity checks cancellation across the wire,
|
|
11
|
+
// independently of the client's own context cancellation.
|
|
12
|
+
func TestServerCancellationRetainsIdentity(t *testing.T) {
|
|
13
|
+
// Exercise the real packet codec and pipe with a canceled remote handler.
|
|
14
|
+
ctx, cancel := context.WithTimeout(t.Context(), time.Second)
|
|
15
|
+
defer cancel()
|
|
16
|
+
server := NewServer(InvokerFunc(func(string, string, Stream) (bool, error) {
|
|
17
|
+
return true, context.Canceled
|
|
18
|
+
}))
|
|
19
|
+
rpc := NewClientRPC(ctx, "service", "method")
|
|
20
|
+
defer rpc.Close()
|
|
21
|
+
writer, err := NewServerPipe(server)(ctx, rpc.HandlePacketData, rpc.HandleStreamClose)
|
|
22
|
+
if err != nil {
|
|
23
|
+
t.Fatal(err)
|
|
24
|
+
}
|
|
25
|
+
if err := rpc.Start(writer, false, nil); err != nil {
|
|
26
|
+
t.Fatal(err)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Both receive and completion must retain the remote cancellation sentinel.
|
|
30
|
+
if _, err := rpc.ReadOne(); !errors.Is(err, context.Canceled) {
|
|
31
|
+
t.Fatalf("receive cancellation = %v", err)
|
|
32
|
+
}
|
|
33
|
+
if err := rpc.Wait(ctx); !errors.Is(err, context.Canceled) {
|
|
34
|
+
t.Fatalf("completion cancellation = %v", err)
|
|
35
|
+
}
|
|
36
|
+
if err := ctx.Err(); err != nil {
|
|
37
|
+
t.Fatal("client deadline hid the remote cancellation", err)
|
|
38
|
+
}
|
|
39
|
+
}
|
package/srpc/server-rpc.cpp
CHANGED
|
@@ -5,93 +5,103 @@
|
|
|
5
5
|
#include "packet.hpp"
|
|
6
6
|
#include "rpcproto.pb.h"
|
|
7
7
|
|
|
8
|
+
#include <system_error>
|
|
9
|
+
|
|
8
10
|
namespace starpc {
|
|
9
11
|
|
|
10
12
|
ServerRPC::ServerRPC(Invoker *invoker, PacketWriter *writer)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
: invoker(invoker)
|
|
14
|
+
{
|
|
15
|
+
this->writer = writer;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
ServerRPC::~ServerRPC()
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
ServerRPC::~ServerRPC()
|
|
19
|
+
{
|
|
20
|
+
Cancel();
|
|
21
|
+
if (invoke_thread.joinable())
|
|
22
|
+
invoke_thread.join();
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
Error ServerRPC::HandlePacketData(const std::string &data)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
Error ServerRPC::HandlePacketData(const std::string &data)
|
|
26
|
+
{
|
|
27
|
+
srpc::Packet packet;
|
|
28
|
+
if (!packet.ParseFromString(data))
|
|
29
|
+
return Error::InvalidMessage;
|
|
30
|
+
return HandlePacket(packet);
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
Error ServerRPC::HandlePacket(const srpc::Packet &
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
33
|
+
Error ServerRPC::HandlePacket(const srpc::Packet &packet)
|
|
34
|
+
{
|
|
35
|
+
const auto err = ValidatePacket(packet);
|
|
36
|
+
if (err != Error::OK)
|
|
37
|
+
return err;
|
|
38
|
+
|
|
39
|
+
switch (packet.body_case()) {
|
|
40
|
+
case srpc::Packet::kCallStart:
|
|
41
|
+
return HandleCallStart(packet.call_start());
|
|
42
|
+
case srpc::Packet::kCallData:
|
|
43
|
+
if (service.empty())
|
|
44
|
+
return Error::UnrecognizedPacket;
|
|
45
|
+
return HandleCallData(packet.call_data());
|
|
46
|
+
case srpc::Packet::kCallCancel:
|
|
47
|
+
return packet.call_cancel() ? HandleCallCancel() : Error::OK;
|
|
48
|
+
default:
|
|
49
|
+
return Error::UnrecognizedPacket;
|
|
50
|
+
}
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
Error ServerRPC::HandleCallStart(const srpc::CallStart &
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
53
|
+
Error ServerRPC::HandleCallStart(const srpc::CallStart &packet)
|
|
54
|
+
{
|
|
55
|
+
const auto valid = ValidateCallStart(packet);
|
|
56
|
+
if (valid != Error::OK)
|
|
57
|
+
return valid;
|
|
58
|
+
|
|
59
|
+
{
|
|
60
|
+
std::lock_guard lock(state_mutex);
|
|
61
|
+
if (writer == nullptr)
|
|
62
|
+
return Error::NilWriter;
|
|
63
|
+
if (invoker == nullptr)
|
|
64
|
+
return Error::Unimplemented;
|
|
65
|
+
if (!service.empty() || canceled || data_closed)
|
|
66
|
+
return Error::Completed;
|
|
67
|
+
service = packet.rpc_service();
|
|
68
|
+
method = packet.rpc_method();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!packet.data().empty() || packet.data_is_zero()) {
|
|
72
|
+
srpc::CallData first;
|
|
73
|
+
first.set_data(packet.data());
|
|
74
|
+
first.set_data_is_zero(packet.data_is_zero());
|
|
75
|
+
const auto queued = HandleCallData(first);
|
|
76
|
+
if (queued != Error::OK)
|
|
77
|
+
return queued;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* Thread creation is the sole throwing runtime boundary in this
|
|
82
|
+
* component; a stop callback cancels the call when the thread ends.
|
|
83
|
+
*/
|
|
84
|
+
try {
|
|
85
|
+
invoke_thread = std::jthread([this](std::stop_token stop) {
|
|
86
|
+
std::stop_callback canceled(stop, [this] { Cancel(); });
|
|
87
|
+
InvokeRPC(service, method);
|
|
88
|
+
});
|
|
89
|
+
} catch (const std::system_error &) {
|
|
90
|
+
Cancel();
|
|
91
|
+
return Error::ResourceExhausted;
|
|
92
|
+
}
|
|
93
|
+
return Error::OK;
|
|
79
94
|
}
|
|
80
95
|
|
|
81
96
|
void ServerRPC::InvokeRPC(const std::string &service_id,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
auto out_pkt = NewCallDataPacket("", false, true, err);
|
|
92
|
-
writer_->WritePacket(*out_pkt);
|
|
93
|
-
writer_->Close();
|
|
94
|
-
Cancel();
|
|
97
|
+
const std::string &method_id)
|
|
98
|
+
{
|
|
99
|
+
auto stream = NewMsgStream(this, [this] { Cancel(); }, StopToken());
|
|
100
|
+
auto [found, err] =
|
|
101
|
+
invoker->InvokeMethod(service_id, method_id, stream.get());
|
|
102
|
+
if (!found && err == Error::OK)
|
|
103
|
+
err = Error::Unimplemented;
|
|
104
|
+
Finish(err);
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
} // namespace starpc
|