| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you under the Apache License, Version 2.0 (the | |
| // "License"); you may not use this file except in compliance | |
| // with the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| // KIND, either express or implied. See the License for the | |
| // specific language governing permissions and limitations | |
| // under the License. | |
| #include "echo.brpc.fb.h" | |
| #include <atomic> | |
| #include <functional> | |
| #include <iostream> | |
| #include <stdexcept> | |
| #include <string> | |
| #include <thread> | |
| #include <vector> | |
| namespace { | |
| #define CODEGEN_CHECK(condition) \ | |
| do { \ | |
| if (!(condition)) { \ | |
| throw std::runtime_error("check failed: " #condition); \ | |
| } \ | |
| } while (0) | |
| using brpc::flatbuffers::Message; | |
| using brpc::flatbuffers::MessageBuilder; | |
| using brpc::flatbuffers::MethodDescriptor; | |
| using brpc::flatbuffers::ServiceDescriptor; | |
| using codegen::example::Echo; | |
| using codegen::example::Other; | |
| using codegen::example::Request; | |
| using codegen::example::Response; | |
| using google::protobuf::Closure; | |
| using google::protobuf::RpcController; | |
| class Controller : public RpcController { | |
| public: | |
| void Reset() override { failures = 0; error.clear(); } | |
| bool Failed() const override { return failures != 0; } | |
| std::string ErrorText() const override { return error; } | |
| void StartCancel() override {} | |
| void SetFailed(const std::string& reason) override { ++failures; error = reason; } | |
| bool IsCanceled() const override { return false; } | |
| void NotifyOnCancel(Closure*) override {} | |
| int failures = 0; | |
| std::string error; | |
| }; | |
| class Done : public Closure { | |
| public: | |
| void Run() override { ++runs; } | |
| int runs = 0; | |
| }; | |
| class DeletingDone : public Closure { | |
| public: | |
| explicit DeletingDone(int* runs) : runs_(runs) {} | |
| void Run() override { ++*runs_; delete this; } | |
| private: | |
| int* runs_; | |
| }; | |
| Message MakeRequest(const char* text) { | |
| MessageBuilder builder; | |
| const auto value = text ? builder.CreateString(text) : | |
| ::flatbuffers::Offset<::flatbuffers::String>(); | |
| builder.Finish(codegen::example::CreateRequest(builder, value)); | |
| return builder.ReleaseMessage(); | |
| } | |
| class Implementation : public Echo { | |
| public: | |
| void Repeat(RpcController*, const Message* request, | |
| Message* response, Closure* done) override { | |
| ++calls; | |
| selected = 41; | |
| Reply(request, response, done); | |
| } | |
| void Inspect(RpcController*, const Message* request, | |
| Message* response, Closure* done) override { | |
| ++calls; | |
| selected = 7; | |
| Reply(request, response, done); | |
| } | |
| void Reply(const Message* request, Message* response, Closure* done) { | |
| const auto* input = request->GetRoot<Request>(); | |
| MessageBuilder builder; | |
| const auto value = input->text() ? builder.CreateString(input->text()->str()) : | |
| ::flatbuffers::Offset<::flatbuffers::String>(); | |
| builder.Finish(codegen::example::CreateResponse(builder, value)); | |
| *response = builder.ReleaseMessage(); | |
| if (done) { | |
| done->Run(); | |
| } | |
| } | |
| int calls = 0; | |
| int selected = -1; | |
| }; | |
| class LocalChannel : public brpc::flatbuffers::RpcChannel { | |
| public: | |
| explicit LocalChannel(Echo* service, int* destroyed = nullptr) | |
| : service_(service), destroyed_(destroyed) {} | |
| ~LocalChannel() override { if (destroyed_) { ++*destroyed_; } } | |
| void FBCallMethod(const MethodDescriptor* method, RpcController* controller, | |
| const Message* request, Message* response, Closure* done) override { | |
| last_method = method; | |
| service_->FBCallMethod(method, controller, request, response, done); | |
| } | |
| const MethodDescriptor* last_method = nullptr; | |
| private: | |
| Echo* service_; | |
| int* destroyed_; | |
| }; | |
| void TestConcurrentDescriptors() { | |
| const int thread_count = 24; | |
| std::atomic<bool> start(false); | |
| std::atomic<int> invalid(0); | |
| std::vector<const ServiceDescriptor*> descriptors(thread_count); | |
| std::vector<std::thread> threads; | |
| for (int i = 0; i < thread_count; ++i) { | |
| threads.emplace_back([&, i] { | |
| while (!start.load()) { | |
| std::this_thread::yield(); | |
| } | |
| for (int n = 0; n < 1000; ++n) { | |
| descriptors[i] = Echo::descriptor(); | |
| if (descriptors[i]->method_count() != 2 || | |
| descriptors[i]->method(0)->index() != 41 || | |
| descriptors[i]->method(1)->index() != 7) { | |
| ++invalid; | |
| } | |
| } | |
| }); | |
| } | |
| start.store(true); | |
| for (auto& thread : threads) { | |
| thread.join(); | |
| } | |
| CODEGEN_CHECK(invalid.load() == 0); | |
| for (const auto* descriptor : descriptors) { | |
| CODEGEN_CHECK(descriptor == descriptors.front()); | |
| } | |
| const auto* descriptor = descriptors.front(); | |
| CODEGEN_CHECK(descriptor->full_name() == "codegen.example.Echo"); | |
| CODEGEN_CHECK(descriptor->FindMethodByIndex(41) == descriptor->method(0)); | |
| CODEGEN_CHECK(descriptor->FindMethodByIndex(7) == descriptor->method(1)); | |
| CODEGEN_CHECK(descriptor->FindMethodByIndex(0) == nullptr); | |
| CODEGEN_CHECK(descriptor->method(0)->service() == descriptor); | |
| } | |
| void ExpectFailure(const std::function<void(Controller*, Done*)>& call) { | |
| Controller controller; | |
| Done done; | |
| call(&controller, &done); | |
| CODEGEN_CHECK(controller.Failed()); | |
| CODEGEN_CHECK(controller.failures == 1); | |
| CODEGEN_CHECK(!controller.ErrorText().empty()); | |
| CODEGEN_CHECK(done.runs == 1); | |
| } | |
| void TestDispatchAndErrors() { | |
| Implementation service; | |
| LocalChannel channel(&service); | |
| Echo::Stub stub(&channel); | |
| const auto* descriptor = Echo::descriptor(); | |
| CODEGEN_CHECK(stub.GetDescriptor() == descriptor); | |
| CODEGEN_CHECK(stub.channel() == &channel); | |
| for (const char* text : {static_cast<const char*>(nullptr), "hello"}) { | |
| Message request = MakeRequest(text); | |
| for (int position = 0; position < 2; ++position) { | |
| Controller controller; | |
| Done done; | |
| Message response; | |
| if (position == 0) { | |
| stub.Repeat(&controller, &request, &response, &done); | |
| } else { | |
| stub.Inspect(&controller, &request, &response, &done); | |
| } | |
| CODEGEN_CHECK(!controller.Failed()); | |
| CODEGEN_CHECK(done.runs == 1); | |
| CODEGEN_CHECK(channel.last_method == descriptor->method(position)); | |
| CODEGEN_CHECK(service.selected == (position == 0 ? 41 : 7)); | |
| CODEGEN_CHECK(response.Verify<Response>()); | |
| const auto* output = response.GetRoot<Response>(); | |
| CODEGEN_CHECK(text ? output->text() && output->text()->str() == text : | |
| output->text() == nullptr); | |
| } | |
| } | |
| const int calls = service.calls; | |
| Message request = MakeRequest(nullptr); | |
| Message response; | |
| Message invalid; | |
| const MethodDescriptor unknown("Unknown", descriptor, 99); | |
| const MethodDescriptor forged("Forged", descriptor, 41); | |
| for (const auto* method : {static_cast<const MethodDescriptor*>(nullptr), | |
| Other::descriptor()->method(0), &unknown, &forged}) { | |
| ExpectFailure([&](Controller* controller, Done* done) { | |
| service.FBCallMethod(method, controller, &request, &response, done); | |
| }); | |
| } | |
| for (int position = 0; position < 2; ++position) { | |
| ExpectFailure([&](Controller* controller, Done* done) { | |
| service.FBCallMethod(descriptor->method(position), controller, | |
| &invalid, &response, done); | |
| }); | |
| } | |
| ExpectFailure([&](Controller* controller, Done* done) { | |
| service.FBCallMethod(descriptor->method(0), controller, nullptr, &response, done); | |
| }); | |
| ExpectFailure([&](Controller* controller, Done* done) { | |
| service.FBCallMethod(descriptor->method(0), controller, &request, nullptr, done); | |
| }); | |
| CODEGEN_CHECK(service.calls == calls); | |
| Echo unimplemented; | |
| for (int position = 0; position < 2; ++position) { | |
| ExpectFailure([&](Controller* controller, Done* done) { | |
| unimplemented.FBCallMethod(descriptor->method(position), controller, | |
| &request, &response, done); | |
| }); | |
| } | |
| ExpectFailure([&](Controller* controller, Done* done) { | |
| unimplemented.Repeat(controller, &request, &response, done); | |
| }); | |
| Echo::Stub disconnected(nullptr); | |
| ExpectFailure([&](Controller* controller, Done* done) { | |
| disconnected.Repeat(controller, &request, &response, done); | |
| }); | |
| // The brpc::Channel overload goes through the library adapter; a null | |
| // channel must leave the stub disconnected rather than crash. | |
| Echo::Stub null_channel(static_cast<brpc::Channel*>(nullptr), | |
| brpc::flatbuffers::Service::STUB_OWNS_CHANNEL); | |
| CODEGEN_CHECK(null_channel.channel() == nullptr); | |
| ExpectFailure([&](Controller* controller, Done* done) { | |
| null_channel.Repeat(controller, &request, &response, done); | |
| }); | |
| ExpectFailure([&](Controller* controller, Done* done) { | |
| disconnected.Inspect(controller, &request, &response, done); | |
| }); | |
| Controller controller; | |
| service.FBCallMethod(nullptr, &controller, &request, &response, nullptr); | |
| CODEGEN_CHECK(controller.failures == 1); | |
| Done done; | |
| service.FBCallMethod(nullptr, nullptr, &request, &response, &done); | |
| CODEGEN_CHECK(done.runs == 1); | |
| int deleted_runs = 0; | |
| service.FBCallMethod(nullptr, nullptr, &request, &response, | |
| new DeletingDone(&deleted_runs)); | |
| CODEGEN_CHECK(deleted_runs == 1); | |
| } | |
| void TestOwnershipAndAsyncCompletion() { | |
| Implementation service; | |
| int destroyed = 0; | |
| { | |
| Echo::Stub owner(new LocalChannel(&service, &destroyed), | |
| brpc::flatbuffers::Service::STUB_OWNS_CHANNEL); | |
| } | |
| CODEGEN_CHECK(destroyed == 1); | |
| { | |
| LocalChannel borrowed(&service, &destroyed); | |
| { | |
| Echo::Stub stub(&borrowed); | |
| } | |
| CODEGEN_CHECK(destroyed == 1); | |
| } | |
| CODEGEN_CHECK(destroyed == 2); | |
| class Deferred : public Echo { | |
| public: | |
| void Repeat(RpcController*, const Message*, Message*, Closure* done) override { | |
| pending = done; | |
| } | |
| Closure* pending = nullptr; | |
| } deferred; | |
| Message request = MakeRequest(nullptr); | |
| Message response; | |
| Controller controller; | |
| Done done; | |
| deferred.FBCallMethod(Echo::descriptor()->method(0), &controller, | |
| &request, &response, &done); | |
| CODEGEN_CHECK(!controller.Failed()); | |
| CODEGEN_CHECK(done.runs == 0); | |
| CODEGEN_CHECK(deferred.pending == &done); | |
| deferred.pending->Run(); | |
| CODEGEN_CHECK(done.runs == 1); | |
| } | |
| void TestEscapedTableTypes() { | |
| class EscapedImplementation : public codegen::example::module { | |
| public: | |
| void RoundTrip(RpcController*, const Message* request, | |
| Message* response, Closure* done) override { | |
| value = request->GetRoot<codegen::example::module_>()->value(); | |
| response->CopyFrom(*request); | |
| if (done) { | |
| done->Run(); | |
| } | |
| } | |
| int value = 0; | |
| } service; | |
| MessageBuilder builder; | |
| builder.Finish(codegen::example::Createmodule_(builder, 73)); | |
| Message request = builder.ReleaseMessage(); | |
| Message response; | |
| Controller controller; | |
| Done done; | |
| const auto* descriptor = codegen::example::module::descriptor(); | |
| CODEGEN_CHECK(descriptor->full_name() == "codegen.example.module"); | |
| service.FBCallMethod(descriptor->FindMethodByIndex(17), &controller, | |
| &request, &response, &done); | |
| CODEGEN_CHECK(!controller.Failed()); | |
| CODEGEN_CHECK(service.value == 73); | |
| CODEGEN_CHECK(done.runs == 1); | |
| CODEGEN_CHECK(response.Verify<codegen::example::module_>()); | |
| CODEGEN_CHECK(response.GetRoot<codegen::example::module_>()->value() == 73); | |
| } | |
| } // namespace | |
| int main() { | |
| try { | |
| TestConcurrentDescriptors(); | |
| TestDispatchAndErrors(); | |
| TestOwnershipAndAsyncCompletion(); | |
| TestEscapedTableTypes(); | |
| std::cout << "Descriptor, sparse dispatch, verifier, callback and ownership checks passed\n"; | |
| return 0; | |
| } catch (const std::exception& error) { | |
| std::cerr << error.what() << '\n'; | |
| return 1; | |
| } | |
| } |