starpc 0.43.1 → 0.45.0

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.
@@ -0,0 +1,182 @@
1
+ // go:build deps_only
2
+
3
+ #include "common-rpc.hpp"
4
+
5
+ #include "packet.hpp"
6
+ #include "rpcproto.pb.h"
7
+
8
+ namespace starpc {
9
+
10
+ CommonRPC::CommonRPC() = default;
11
+
12
+ CommonRPC::~CommonRPC() = default;
13
+
14
+ void CommonRPC::Init() {
15
+ canceled_.store(false);
16
+ local_completed_.store(false);
17
+ data_closed_ = false;
18
+ remote_err_ = Error::OK;
19
+ data_queue_.clear();
20
+ }
21
+
22
+ void CommonRPC::Cancel() {
23
+ canceled_.store(true);
24
+ cv_.notify_all();
25
+ }
26
+
27
+ bool CommonRPC::IsCanceled() const { return canceled_.load(); }
28
+
29
+ void CommonRPC::SetWriter(PacketWriter *writer) {
30
+ std::lock_guard<std::mutex> lock(mtx_);
31
+ writer_ = writer;
32
+ }
33
+
34
+ Error CommonRPC::ReadOne(std::string *out) {
35
+ bool ctx_done = false;
36
+
37
+ while (true) {
38
+ std::unique_lock<std::mutex> lock(mtx_);
39
+
40
+ if (ctx_done && !data_closed_) {
41
+ CloseLocked();
42
+ return Error::Canceled;
43
+ }
44
+
45
+ if (!data_queue_.empty()) {
46
+ *out = std::move(data_queue_.front());
47
+ data_queue_.pop_front();
48
+ return Error::OK;
49
+ }
50
+
51
+ if (data_closed_ || remote_err_ != Error::OK) {
52
+ if (remote_err_ != Error::OK) {
53
+ return remote_err_;
54
+ }
55
+ return Error::EOF_;
56
+ }
57
+
58
+ cv_.wait(lock, [this, &ctx_done]() {
59
+ if (canceled_.load()) {
60
+ ctx_done = true;
61
+ return true;
62
+ }
63
+ return !data_queue_.empty() || data_closed_ || remote_err_ != Error::OK;
64
+ });
65
+
66
+ if (canceled_.load()) {
67
+ ctx_done = true;
68
+ }
69
+ }
70
+ }
71
+
72
+ Error CommonRPC::WriteCallData(const std::string &data, bool data_is_zero,
73
+ bool complete, Error err) {
74
+ // Check if already completed
75
+ if (local_completed_.load()) {
76
+ // If we're just marking completion and already completed, allow it (no-op)
77
+ if (complete && data.empty() && !data_is_zero) {
78
+ return Error::OK;
79
+ }
80
+ // Otherwise, return error for trying to send data after completion
81
+ return Error::Completed;
82
+ }
83
+
84
+ // Mark as completed if this call completes the RPC
85
+ if (complete || err != Error::OK) {
86
+ local_completed_.store(true);
87
+ }
88
+
89
+ std::lock_guard<std::mutex> lock(mtx_);
90
+ if (writer_ == nullptr) {
91
+ return Error::NilWriter;
92
+ }
93
+
94
+ auto pkt =
95
+ NewCallDataPacket(data, data.empty() && data_is_zero, complete, err);
96
+ return writer_->WritePacket(*pkt);
97
+ }
98
+
99
+ void CommonRPC::HandleStreamClose(Error close_err) {
100
+ std::lock_guard<std::mutex> lock(mtx_);
101
+ if (close_err != Error::OK && remote_err_ == Error::OK) {
102
+ remote_err_ = close_err;
103
+ }
104
+ data_closed_ = true;
105
+ canceled_.store(true);
106
+ if (writer_ != nullptr) {
107
+ writer_->Close();
108
+ }
109
+ cv_.notify_all();
110
+ }
111
+
112
+ Error CommonRPC::HandleCallCancel() {
113
+ HandleStreamClose(Error::Canceled);
114
+ return Error::OK;
115
+ }
116
+
117
+ Error CommonRPC::HandleCallData(const srpc::CallData &pkt) {
118
+ std::lock_guard<std::mutex> lock(mtx_);
119
+
120
+ if (data_closed_) {
121
+ // If the packet is just indicating the call is complete, ignore it.
122
+ if (pkt.complete()) {
123
+ return Error::OK;
124
+ }
125
+ // Otherwise, return ErrCompleted (unexpected packet).
126
+ return Error::Completed;
127
+ }
128
+
129
+ // Queue data if present
130
+ if (!pkt.data().empty() || pkt.data_is_zero()) {
131
+ data_queue_.push_back(pkt.data());
132
+ }
133
+
134
+ bool complete = pkt.complete();
135
+ if (!pkt.error().empty()) {
136
+ complete = true;
137
+ // Store remote error - in a full implementation we might parse the error
138
+ // string
139
+ remote_err_ = Error::Unimplemented; // Generic remote error
140
+ }
141
+
142
+ if (complete) {
143
+ data_closed_ = true;
144
+ }
145
+
146
+ cv_.notify_all();
147
+ return Error::OK;
148
+ }
149
+
150
+ Error CommonRPC::WriteCallCancel() {
151
+ std::lock_guard<std::mutex> lock(mtx_);
152
+ return WriteCallCancelLocked();
153
+ }
154
+
155
+ Error CommonRPC::WriteCallCancelLocked() {
156
+ // Use atomic swap to check and set completion atomically
157
+ if (local_completed_.exchange(true)) {
158
+ return Error::Completed;
159
+ }
160
+
161
+ if (writer_ == nullptr) {
162
+ return Error::NilWriter;
163
+ }
164
+
165
+ auto pkt = NewCallCancelPacket();
166
+ return writer_->WritePacket(*pkt);
167
+ }
168
+
169
+ void CommonRPC::CloseLocked() {
170
+ data_closed_ = true;
171
+ local_completed_.store(true);
172
+ if (remote_err_ == Error::OK) {
173
+ remote_err_ = Error::Canceled;
174
+ }
175
+ if (writer_ != nullptr) {
176
+ writer_->Close();
177
+ }
178
+ cv_.notify_all();
179
+ canceled_.store(true);
180
+ }
181
+
182
+ } // namespace starpc
@@ -0,0 +1,109 @@
1
+ #pragma once
2
+
3
+ #include <atomic>
4
+ #include <condition_variable>
5
+ #include <deque>
6
+ #include <mutex>
7
+ #include <string>
8
+
9
+ #include "errors.hpp"
10
+ #include "writer.hpp"
11
+
12
+ namespace srpc {
13
+ class CallData;
14
+ }
15
+
16
+ namespace starpc {
17
+
18
+ // CommonRPC contains common logic between server/client RPC.
19
+ // Matches Go commonRPC struct in common-rpc.go
20
+ class CommonRPC {
21
+ public:
22
+ CommonRPC();
23
+ virtual ~CommonRPC();
24
+
25
+ // Init initializes the CommonRPC (matches initCommonRPC in Go).
26
+ void Init();
27
+
28
+ // Cancel cancels the RPC context.
29
+ void Cancel();
30
+
31
+ // IsCanceled returns true if the RPC has been canceled.
32
+ bool IsCanceled() const;
33
+
34
+ // GetService returns the service name.
35
+ const std::string &GetService() const { return service_; }
36
+
37
+ // GetMethod returns the method name.
38
+ const std::string &GetMethod() const { return method_; }
39
+
40
+ // ReadOne reads a single message and returns.
41
+ // Returns EOF_ if the stream ended without a packet.
42
+ // Matches Go ReadOne in common-rpc.go
43
+ Error ReadOne(std::string *out);
44
+
45
+ // WriteCallData writes a call data packet.
46
+ // Matches Go WriteCallData in common-rpc.go
47
+ Error WriteCallData(const std::string &data, bool data_is_zero, bool complete,
48
+ Error err);
49
+
50
+ // HandleStreamClose handles the incoming stream closing with optional error.
51
+ // Matches Go HandleStreamClose in common-rpc.go
52
+ void HandleStreamClose(Error close_err);
53
+
54
+ // HandleCallCancel handles the call cancel packet.
55
+ // Matches Go HandleCallCancel in common-rpc.go
56
+ Error HandleCallCancel();
57
+
58
+ // HandleCallData handles the call data packet.
59
+ // Matches Go HandleCallData in common-rpc.go
60
+ Error HandleCallData(const srpc::CallData &pkt);
61
+
62
+ // WriteCallCancel writes a call cancel packet.
63
+ // Matches Go WriteCallCancel in common-rpc.go
64
+ Error WriteCallCancel();
65
+
66
+ // WriteCallCancelLocked is the same as WriteCallCancel but assumes mtx_ is
67
+ // held.
68
+ Error WriteCallCancelLocked();
69
+
70
+ protected:
71
+ // CloseLocked releases resources held by the RPC.
72
+ // Must be called with mutex held.
73
+ // Matches Go closeLocked in common-rpc.go
74
+ void CloseLocked();
75
+
76
+ // SetWriter sets the packet writer.
77
+ void SetWriter(PacketWriter *writer);
78
+
79
+ // Mutex and condition variable for synchronization (replaces Go broadcast)
80
+ mutable std::mutex mtx_;
81
+ std::condition_variable cv_;
82
+
83
+ // Service and method names
84
+ std::string service_;
85
+ std::string method_;
86
+
87
+ // localCompleted tracks if we have sent a completion or cancel locally.
88
+ // Note: not guarded by mutex (atomic)
89
+ std::atomic<bool> local_completed_{false};
90
+
91
+ // Writer to write messages to
92
+ PacketWriter *writer_ = nullptr;
93
+
94
+ // dataQueue contains incoming data packets.
95
+ // Note: packets may be empty
96
+ std::deque<std::string> data_queue_;
97
+
98
+ // dataClosed is a flag set after dataQueue is closed.
99
+ // Controlled by HandlePacket.
100
+ bool data_closed_ = false;
101
+
102
+ // remoteErr is an error set by the remote.
103
+ Error remote_err_ = Error::OK;
104
+
105
+ // canceled tracks if the context has been canceled
106
+ std::atomic<bool> canceled_{false};
107
+ };
108
+
109
+ } // namespace starpc
@@ -0,0 +1,71 @@
1
+ #pragma once
2
+
3
+ #include <stdexcept>
4
+ #include <string>
5
+
6
+ namespace starpc {
7
+
8
+ // Error codes matching Go starpc errors (see errors.go)
9
+ enum class Error {
10
+ OK = 0,
11
+ Unimplemented, // ErrUnimplemented - RPC method was not implemented
12
+ Completed, // ErrCompleted - unexpected packet after rpc was completed
13
+ UnrecognizedPacket, // ErrUnrecognizedPacket - unrecognized packet type
14
+ EmptyPacket, // ErrEmptyPacket - invalid empty packet
15
+ InvalidMessage, // ErrInvalidMessage - message failed to parse
16
+ EmptyMethodID, // ErrEmptyMethodID - method id empty
17
+ EmptyServiceID, // ErrEmptyServiceID - service id empty
18
+ NoAvailableClients, // ErrNoAvailableClients - no available rpc clients
19
+ NilWriter, // ErrNilWriter - writer cannot be nil
20
+ Canceled, // context.Canceled equivalent
21
+ EOF_, // io.EOF equivalent (named EOF_ to avoid macro collision)
22
+ };
23
+
24
+ // Convert Error to string (matches Go error messages)
25
+ inline const char *ErrorString(Error err) {
26
+ switch (err) {
27
+ case Error::OK:
28
+ return "ok";
29
+ case Error::Unimplemented:
30
+ return "unimplemented";
31
+ case Error::Completed:
32
+ return "unexpected packet after rpc was completed";
33
+ case Error::UnrecognizedPacket:
34
+ return "unrecognized packet type";
35
+ case Error::EmptyPacket:
36
+ return "invalid empty packet";
37
+ case Error::InvalidMessage:
38
+ return "invalid message";
39
+ case Error::EmptyMethodID:
40
+ return "method id empty";
41
+ case Error::EmptyServiceID:
42
+ return "service id empty";
43
+ case Error::NoAvailableClients:
44
+ return "no available rpc clients";
45
+ case Error::NilWriter:
46
+ return "writer cannot be nil";
47
+ case Error::Canceled:
48
+ return "canceled";
49
+ case Error::EOF_:
50
+ return "EOF";
51
+ default:
52
+ return "unknown error";
53
+ }
54
+ }
55
+
56
+ // StarpcError exception for error propagation
57
+ class StarpcError : public std::runtime_error {
58
+ public:
59
+ explicit StarpcError(Error code)
60
+ : std::runtime_error(ErrorString(code)), code_(code) {}
61
+
62
+ StarpcError(Error code, const std::string &message)
63
+ : std::runtime_error(message), code_(code) {}
64
+
65
+ Error code() const noexcept { return code_; }
66
+
67
+ private:
68
+ Error code_;
69
+ };
70
+
71
+ } // namespace starpc
@@ -0,0 +1,23 @@
1
+ #pragma once
2
+
3
+ #include <string>
4
+ #include <vector>
5
+
6
+ #include "invoker.hpp"
7
+
8
+ namespace starpc {
9
+
10
+ // Handler describes a SRPC call handler implementation.
11
+ // Matches Go Handler interface in handler.go
12
+ class Handler : public Invoker {
13
+ public:
14
+ ~Handler() override = default;
15
+
16
+ // GetServiceID returns the ID of the service.
17
+ virtual const std::string &GetServiceID() const = 0;
18
+
19
+ // GetMethodIDs returns the list of methods for the service.
20
+ virtual std::vector<std::string> GetMethodIDs() const = 0;
21
+ };
22
+
23
+ } // namespace starpc
@@ -0,0 +1,91 @@
1
+ #pragma once
2
+
3
+ #include <string>
4
+ #include <utility>
5
+ #include <vector>
6
+
7
+ #include "errors.hpp"
8
+ #include "stream.hpp"
9
+
10
+ namespace starpc {
11
+
12
+ // Invoker is a function for invoking SRPC service methods.
13
+ // Matches Go Invoker interface in invoker.go
14
+ class Invoker {
15
+ public:
16
+ virtual ~Invoker() = default;
17
+
18
+ // InvokeMethod invokes the method matching the service & method ID.
19
+ // Returns {found, error} - found is false if method not found.
20
+ // If service string is empty, ignore it.
21
+ virtual std::pair<bool, Error> InvokeMethod(const std::string &service_id,
22
+ const std::string &method_id,
23
+ Stream *strm) = 0;
24
+ };
25
+
26
+ // QueryableInvoker can be used to check if a service and method is implemented.
27
+ // Matches Go QueryableInvoker interface in invoker.go
28
+ class QueryableInvoker {
29
+ public:
30
+ virtual ~QueryableInvoker() = default;
31
+
32
+ // HasService checks if the service ID exists in the handlers.
33
+ virtual bool HasService(const std::string &service_id) const = 0;
34
+
35
+ // HasServiceMethod checks if <service-id, method-id> exists in the handlers.
36
+ virtual bool HasServiceMethod(const std::string &service_id,
37
+ const std::string &method_id) const = 0;
38
+ };
39
+
40
+ // InvokerSlice is a list of invokers.
41
+ // Matches Go InvokerSlice in invoker.go
42
+ class InvokerSlice : public Invoker {
43
+ public:
44
+ InvokerSlice() = default;
45
+ explicit InvokerSlice(std::vector<Invoker *> invokers)
46
+ : invokers_(std::move(invokers)) {}
47
+
48
+ void Add(Invoker *invoker) { invokers_.push_back(invoker); }
49
+
50
+ std::pair<bool, Error> InvokeMethod(const std::string &service_id,
51
+ const std::string &method_id,
52
+ Stream *strm) override {
53
+ for (auto *invoker : invokers_) {
54
+ if (invoker == nullptr)
55
+ continue;
56
+ auto [found, err] = invoker->InvokeMethod(service_id, method_id, strm);
57
+ if (found || err != Error::OK) {
58
+ return {true, err};
59
+ }
60
+ }
61
+ return {false, Error::OK};
62
+ }
63
+
64
+ private:
65
+ std::vector<Invoker *> invokers_;
66
+ };
67
+
68
+ // InvokerFunc is a function implementing InvokeMethod.
69
+ // Matches Go InvokerFunc in invoker.go
70
+ using InvokerFunc = std::function<std::pair<bool, Error>(
71
+ const std::string &service_id, const std::string &method_id, Stream *strm)>;
72
+
73
+ // InvokerFuncWrapper wraps an InvokerFunc as an Invoker.
74
+ class InvokerFuncWrapper : public Invoker {
75
+ public:
76
+ explicit InvokerFuncWrapper(InvokerFunc fn) : fn_(std::move(fn)) {}
77
+
78
+ std::pair<bool, Error> InvokeMethod(const std::string &service_id,
79
+ const std::string &method_id,
80
+ Stream *strm) override {
81
+ if (!fn_) {
82
+ return {false, Error::OK};
83
+ }
84
+ return fn_(service_id, method_id, strm);
85
+ }
86
+
87
+ private:
88
+ InvokerFunc fn_;
89
+ };
90
+
91
+ } // namespace starpc
@@ -0,0 +1,28 @@
1
+ #pragma once
2
+
3
+ #include <string>
4
+
5
+ #include "google/protobuf/message_lite.h"
6
+
7
+ namespace starpc {
8
+
9
+ // Message is the interface for protobuf messages.
10
+ // Matches Go Message interface in message.go
11
+ // In C++, we use google::protobuf::MessageLite as the base.
12
+ using Message = google::protobuf::MessageLite;
13
+
14
+ // MarshalVT serializes the message to bytes.
15
+ // (VT suffix matches vtprotobuf convention used in Go)
16
+ inline bool MarshalVT(const Message &msg, std::string *out) {
17
+ return msg.SerializeToString(out);
18
+ }
19
+
20
+ // UnmarshalVT deserializes the message from bytes.
21
+ inline bool UnmarshalVT(Message *msg, const std::string &data) {
22
+ return msg->ParseFromString(data);
23
+ }
24
+
25
+ // SizeVT returns the serialized size of the message.
26
+ inline size_t SizeVT(const Message &msg) { return msg.ByteSizeLong(); }
27
+
28
+ } // namespace starpc
@@ -0,0 +1,86 @@
1
+ #pragma once
2
+
3
+ #include <functional>
4
+ #include <string>
5
+
6
+ #include "errors.hpp"
7
+ #include "message.hpp"
8
+ #include "stream.hpp"
9
+
10
+ namespace starpc {
11
+
12
+ // MsgStreamRw is the read-write interface for MsgStream.
13
+ // Matches Go MsgStreamRw interface in msg-stream.go
14
+ class MsgStreamRw {
15
+ public:
16
+ virtual ~MsgStreamRw() = default;
17
+
18
+ // ReadOne reads a single message and returns.
19
+ // Returns EOF_ if the stream ended.
20
+ virtual Error ReadOne(std::string *out) = 0;
21
+
22
+ // WriteCallData writes a call data packet.
23
+ virtual Error WriteCallData(const std::string &data, bool data_is_zero,
24
+ bool complete, Error err) = 0;
25
+
26
+ // WriteCallCancel writes a call cancel (close) packet.
27
+ virtual Error WriteCallCancel() = 0;
28
+ };
29
+
30
+ // MsgStream implements the stream interface passed to implementations.
31
+ // Matches Go MsgStream struct in msg-stream.go
32
+ class MsgStream : public Stream {
33
+ public:
34
+ // Constructor matching NewMsgStream in Go
35
+ MsgStream(MsgStreamRw *rw, std::function<void()> close_cb)
36
+ : rw_(rw), close_cb_(std::move(close_cb)) {}
37
+
38
+ // MsgSend sends the message to the remote.
39
+ Error MsgSend(const Message &msg) override {
40
+ std::string msg_data;
41
+ if (!msg.SerializeToString(&msg_data)) {
42
+ return Error::InvalidMessage;
43
+ }
44
+ return rw_->WriteCallData(msg_data, msg_data.empty(), false, Error::OK);
45
+ }
46
+
47
+ // MsgRecv receives an incoming message from the remote.
48
+ Error MsgRecv(Message *msg) override {
49
+ std::string data;
50
+ Error err = rw_->ReadOne(&data);
51
+ if (err != Error::OK) {
52
+ return err;
53
+ }
54
+ if (!msg->ParseFromString(data)) {
55
+ return Error::InvalidMessage;
56
+ }
57
+ return Error::OK;
58
+ }
59
+
60
+ // CloseSend signals to the remote that we will no longer send any messages.
61
+ Error CloseSend() override {
62
+ return rw_->WriteCallData("", false, true, Error::OK);
63
+ }
64
+
65
+ // Close closes the stream.
66
+ Error Close() override {
67
+ Error err = rw_->WriteCallCancel();
68
+ if (close_cb_) {
69
+ close_cb_();
70
+ }
71
+ return err;
72
+ }
73
+
74
+ private:
75
+ MsgStreamRw *rw_;
76
+ std::function<void()> close_cb_;
77
+ };
78
+
79
+ // NewMsgStream constructs a new Stream with a MsgStreamRw.
80
+ // Matches Go NewMsgStream function in msg-stream.go
81
+ inline std::unique_ptr<MsgStream> NewMsgStream(MsgStreamRw *rw,
82
+ std::function<void()> close_cb) {
83
+ return std::make_unique<MsgStream>(rw, std::move(close_cb));
84
+ }
85
+
86
+ } // namespace starpc
package/srpc/mux.cpp ADDED
@@ -0,0 +1,109 @@
1
+ // go:build deps_only
2
+
3
+ #include "mux.hpp"
4
+ #include <mutex>
5
+
6
+ namespace starpc {
7
+
8
+ Mux::Mux(std::vector<Invoker *> fallback_invokers)
9
+ : fallback_(std::move(fallback_invokers)) {}
10
+
11
+ Error Mux::Register(Handler *handler) {
12
+ const std::string &service_id = handler->GetServiceID();
13
+ auto method_ids = handler->GetMethodIDs();
14
+
15
+ if (service_id.empty()) {
16
+ return Error::EmptyServiceID;
17
+ }
18
+
19
+ std::unique_lock<std::shared_mutex> lock(mtx_);
20
+
21
+ auto &service_methods = services_[service_id];
22
+ for (const auto &method_id : method_ids) {
23
+ if (!method_id.empty()) {
24
+ service_methods[method_id] = handler;
25
+ }
26
+ }
27
+
28
+ return Error::OK;
29
+ }
30
+
31
+ bool Mux::HasService(const std::string &service_id) const {
32
+ if (service_id.empty()) {
33
+ return false;
34
+ }
35
+
36
+ std::shared_lock<std::shared_mutex> lock(mtx_);
37
+ auto it = services_.find(service_id);
38
+ return it != services_.end() && !it->second.empty();
39
+ }
40
+
41
+ bool Mux::HasServiceMethod(const std::string &service_id,
42
+ const std::string &method_id) const {
43
+ if (service_id.empty() || method_id.empty()) {
44
+ return false;
45
+ }
46
+
47
+ std::shared_lock<std::shared_mutex> lock(mtx_);
48
+ auto svc_it = services_.find(service_id);
49
+ if (svc_it == services_.end()) {
50
+ return false;
51
+ }
52
+
53
+ for (const auto &[mid, handler] : svc_it->second) {
54
+ for (const auto &handler_method : handler->GetMethodIDs()) {
55
+ if (handler_method == method_id) {
56
+ return true;
57
+ }
58
+ }
59
+ }
60
+
61
+ return false;
62
+ }
63
+
64
+ std::pair<bool, Error> Mux::InvokeMethod(const std::string &service_id,
65
+ const std::string &method_id,
66
+ Stream *strm) {
67
+ Handler *handler = nullptr;
68
+
69
+ {
70
+ std::shared_lock<std::shared_mutex> lock(mtx_);
71
+
72
+ if (service_id.empty()) {
73
+ // If service string is empty, search all services
74
+ for (const auto &[svc_id, svc_methods] : services_) {
75
+ auto it = svc_methods.find(method_id);
76
+ if (it != svc_methods.end()) {
77
+ handler = it->second;
78
+ break;
79
+ }
80
+ }
81
+ } else {
82
+ auto svc_it = services_.find(service_id);
83
+ if (svc_it != services_.end()) {
84
+ auto method_it = svc_it->second.find(method_id);
85
+ if (method_it != svc_it->second.end()) {
86
+ handler = method_it->second;
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ if (handler != nullptr) {
93
+ return handler->InvokeMethod(service_id, method_id, strm);
94
+ }
95
+
96
+ // Try fallback invokers
97
+ for (auto *invoker : fallback_) {
98
+ if (invoker != nullptr) {
99
+ auto [handled, err] = invoker->InvokeMethod(service_id, method_id, strm);
100
+ if (err != Error::OK || handled) {
101
+ return {handled, err};
102
+ }
103
+ }
104
+ }
105
+
106
+ return {false, Error::OK};
107
+ }
108
+
109
+ } // namespace starpc