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
@@ -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
+ }
@@ -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
- : invoker_(invoker) {
12
- Init();
13
- writer_ = writer;
13
+ : invoker(invoker)
14
+ {
15
+ this->writer = writer;
14
16
  }
15
17
 
16
- ServerRPC::~ServerRPC() {
17
- if (invoke_thread_.joinable()) {
18
- invoke_thread_.join();
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
- srpc::Packet msg;
24
- if (!msg.ParseFromString(data)) {
25
- return Error::InvalidMessage;
26
- }
27
- return HandlePacket(msg);
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 &msg) {
31
- Error err = ValidatePacket(msg);
32
- if (err != Error::OK) {
33
- return err;
34
- }
35
-
36
- switch (msg.body_case()) {
37
- case srpc::Packet::kCallStart:
38
- return HandleCallStart(msg.call_start());
39
- case srpc::Packet::kCallData:
40
- return HandleCallData(msg.call_data());
41
- case srpc::Packet::kCallCancel:
42
- if (msg.call_cancel()) {
43
- return HandleCallCancel();
44
- }
45
- return Error::OK;
46
- default:
47
- return Error::OK;
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 &pkt) {
52
- std::lock_guard<std::mutex> lock(mtx_);
53
-
54
- // process start: method and service
55
- if (!method_.empty() || !service_.empty()) {
56
- return Error::Completed; // call start must be sent only once
57
- }
58
- if (data_closed_) {
59
- return Error::Completed;
60
- }
61
-
62
- service_ = pkt.rpc_service();
63
- method_ = pkt.rpc_method();
64
-
65
- // process first data packet, if included
66
- if (!pkt.data().empty() || pkt.data_is_zero()) {
67
- data_queue_.push_back(pkt.data());
68
- }
69
-
70
- // invoke the rpc in a separate thread (matches Go goroutine)
71
- std::string service_id = service_;
72
- std::string method_id = method_;
73
- cv_.notify_all();
74
-
75
- invoke_thread_ = std::thread(
76
- [this, service_id, method_id]() { InvokeRPC(service_id, method_id); });
77
-
78
- return Error::OK;
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
- const std::string &method_id) {
83
- // On the server side, the writer is closed by invokeRPC.
84
- auto strm = NewMsgStream(this, [this]() { Cancel(); });
85
-
86
- auto [ok, err] = invoker_->InvokeMethod(service_id, method_id, strm.get());
87
- if (err == Error::OK && !ok) {
88
- err = Error::Unimplemented;
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