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
@@ -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,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
@@ -6,15 +6,15 @@ import (
6
6
  "github.com/pkg/errors"
7
7
  )
8
8
 
9
- // ServerRPC represents the server side of an on-going RPC call message stream.
9
+ // ServerRPC runs one remote method and retains its resources until the handler exits.
10
10
  type ServerRPC struct {
11
11
  commonRPC
12
- // invoker is the rpc call invoker
12
+
13
+ // invoker dispatches the selected service and method.
13
14
  invoker Invoker
14
15
  }
15
16
 
16
- // NewServerRPC constructs a new ServerRPC session.
17
- // note: call SetWriter before handling any incoming messages.
17
+ // NewServerRPC attaches the handler and transport for one incoming call.
18
18
  func NewServerRPC(ctx context.Context, invoker Invoker, writer PacketWriter) *ServerRPC {
19
19
  rpc := &ServerRPC{invoker: invoker}
20
20
  initCommonRPC(ctx, &rpc.commonRPC)
@@ -33,6 +33,7 @@ func (r *ServerRPC) HandlePacketData(data []byte) error {
33
33
 
34
34
  // HandlePacket handles an incoming parsed message packet.
35
35
  func (r *ServerRPC) HandlePacket(msg *Packet) error {
36
+ // Ignore an absent packet and reject malformed messages before dispatch.
36
37
  if msg == nil {
37
38
  return nil
38
39
  }
@@ -40,6 +41,7 @@ func (r *ServerRPC) HandlePacket(msg *Packet) error {
40
41
  return err
41
42
  }
42
43
 
44
+ // Route each packet through the shared stream state.
43
45
  switch b := msg.GetBody().(type) {
44
46
  case *Packet_CallStart:
45
47
  return r.HandleCallStart(b.CallStart)
@@ -57,25 +59,20 @@ func (r *ServerRPC) HandlePacket(msg *Packet) error {
57
59
 
58
60
  // HandleCallStart handles the call start packet.
59
61
  func (r *ServerRPC) HandleCallStart(pkt *CallStart) error {
60
- var err error
61
-
62
+ // Accept one start before any terminal stream transition.
62
63
  locked := r.bcast.Lock()
63
- // process start: method and service
64
64
  if r.method != "" || r.service != "" {
65
- err = errors.New("call start must be sent only once")
66
65
  locked.Unlock()
67
- return err
66
+ return errors.New("call start must be sent only once")
68
67
  }
69
68
  if r.dataClosed {
70
- err = ErrCompleted
71
69
  locked.Unlock()
72
- return err
70
+ return ErrCompleted
73
71
  }
74
72
 
73
+ // Retain the request identity and optional first message for the handler.
75
74
  service, method := pkt.GetRpcService(), pkt.GetRpcMethod()
76
75
  r.service, r.method = service, method
77
-
78
- // process first data packet, if included
79
76
  if data := pkt.GetData(); len(data) != 0 || pkt.GetDataIsZero() {
80
77
  r.dataQueue = append(r.dataQueue, data)
81
78
  }
@@ -84,27 +81,32 @@ func (r *ServerRPC) HandleCallStart(pkt *CallStart) error {
84
81
  // Mark the method active before scheduling it so cancellation cannot make
85
82
  // Wait return and release a mux while invokeRPC is still running user code.
86
83
  r.localActive = true
87
-
88
- // invoke the rpc
89
84
  locked.Broadcast()
90
85
  startServerRPCInvoke(func() {
91
86
  r.invokeRPC(service, method)
92
87
  })
93
88
  locked.Unlock()
94
89
 
95
- return err
90
+ return nil
96
91
  }
97
92
 
98
93
  // invokeRPC invokes the RPC after CallStart is received.
99
94
  func (r *ServerRPC) invokeRPC(serviceID, methodID string) {
100
- // on the server side, the writer is closed by invokeRPC.
95
+ // Run the selected handler before publishing its terminal result.
101
96
  strm := NewMsgStream(r.ctx, r, r.cancelContext)
102
97
  ok, err := r.invoker.InvokeMethod(serviceID, methodID, strm)
103
98
  if err == nil && !ok {
104
99
  err = ErrUnimplemented
105
100
  }
106
- r.beginLocalCompletion()
101
+
102
+ // Cancellation has its own packet; a text error would lose its identity.
107
103
  outPkt := NewCallDataPacket(nil, false, true, err)
108
- _ = r.writer.WritePacket(outPkt)
104
+ if errors.Is(err, context.Canceled) {
105
+ outPkt = NewCallCancelPacket()
106
+ }
107
+ r.beginLocalCompletion()
108
+ if err := r.writer.WritePacket(outPkt); err != nil {
109
+ r.HandleStreamClose(err)
110
+ }
109
111
  r.finishLocalCompletion()
110
112
  }