diff --git a/BUILD b/BUILD index 80b747f7520..9c4fcd71b1b 100644 --- a/BUILD +++ b/BUILD @@ -783,6 +783,20 @@ grpc_cc_library( ], ) +grpc_cc_library( + name = "config", + srcs = [ + "src/core/lib/config/core_configuration.cc", + ], + language = "c++", + public_hdrs = [ + "src/core/lib/config/core_configuration.h", + ], + deps = [ + "gpr_platform", + ], +) + grpc_cc_library( name = "debug_location", language = "c++", diff --git a/CMakeLists.txt b/CMakeLists.txt index 07fb2ca6c0e..0a1ea0011a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -843,6 +843,7 @@ if(gRPC_BUILD_TESTS) add_dependencies(buildtests_cxx connectivity_state_test) add_dependencies(buildtests_cxx context_allocator_end2end_test) add_dependencies(buildtests_cxx context_list_test) + add_dependencies(buildtests_cxx core_configuration_test) add_dependencies(buildtests_cxx delegating_channel_test) add_dependencies(buildtests_cxx destroy_grpclb_channel_with_active_connect_stress_test) add_dependencies(buildtests_cxx dual_ref_counted_test) @@ -10233,6 +10234,41 @@ target_link_libraries(context_list_test ) +endif() +if(gRPC_BUILD_TESTS) + +add_executable(core_configuration_test + src/core/lib/config/core_configuration.cc + test/core/config/core_configuration_test.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(core_configuration_test + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} + ${_gRPC_RE2_INCLUDE_DIR} + ${_gRPC_SSL_INCLUDE_DIR} + ${_gRPC_UPB_GENERATED_DIR} + ${_gRPC_UPB_GRPC_GENERATED_DIR} + ${_gRPC_UPB_INCLUDE_DIR} + ${_gRPC_XXHASH_INCLUDE_DIR} + ${_gRPC_ZLIB_INCLUDE_DIR} + third_party/googletest/googletest/include + third_party/googletest/googletest + third_party/googletest/googlemock/include + third_party/googletest/googlemock + ${_gRPC_PROTO_GENS_DIR} +) + +target_link_libraries(core_configuration_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} +) + + endif() if(gRPC_BUILD_TESTS) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index 06212380bfc..ac53f9b0e17 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -5119,6 +5119,17 @@ targets: deps: - grpc_test_util uses_polling: false +- name: core_configuration_test + gtest: true + build: test + language: c++ + headers: + - src/core/lib/config/core_configuration.h + src: + - src/core/lib/config/core_configuration.cc + - test/core/config/core_configuration_test.cc + deps: [] + uses_polling: false - name: delegating_channel_test gtest: true build: test diff --git a/src/core/lib/config/core_configuration.cc b/src/core/lib/config/core_configuration.cc new file mode 100644 index 00000000000..d5ad4c38866 --- /dev/null +++ b/src/core/lib/config/core_configuration.cc @@ -0,0 +1,53 @@ +// Copyright 2021 gRPC authors. +// +// Licensed 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 + +#include "src/core/lib/config/core_configuration.h" + +namespace grpc_core { + +std::atomic CoreConfiguration::config_; + +CoreConfiguration::Builder::Builder() = default; + +CoreConfiguration* CoreConfiguration::Builder::Build() { + return new CoreConfiguration; +} + +CoreConfiguration::CoreConfiguration() = default; + +const CoreConfiguration& CoreConfiguration::BuildNewAndMaybeSet() { + // Construct builder, pass it up to code that knows about build configuration + Builder builder; + BuildCoreConfiguration(&builder); + // Use builder to construct a confguration + CoreConfiguration* p = builder.Build(); + // Try to set configuration global - it's possible another thread raced us + // here, in which case we drop the work we did and use the one that got set + // first + CoreConfiguration* expected = nullptr; + if (!config_.compare_exchange_strong(expected, p, + std::memory_order_release)) { + delete p; + return *expected; + } + return *p; +} + +void CoreConfiguration::Reset() { + delete config_.exchange(nullptr, std::memory_order_acquire); +} + +} // namespace grpc_core diff --git a/src/core/lib/config/core_configuration.h b/src/core/lib/config/core_configuration.h new file mode 100644 index 00000000000..4927899102d --- /dev/null +++ b/src/core/lib/config/core_configuration.h @@ -0,0 +1,74 @@ +// Copyright 2021 gRPC authors. +// +// Licensed 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. + +#ifndef GRPC_CORE_LIB_CONFIG_CORE_CONFIGURATION_H +#define GRPC_CORE_LIB_CONFIG_CORE_CONFIGURATION_H + +#include + +#include + +namespace grpc_core { + +// Global singleton that stores library configuration - factories, etc... +// that plugins might choose to extend. +class CoreConfiguration { + public: + CoreConfiguration(const CoreConfiguration&) = delete; + CoreConfiguration& operator=(const CoreConfiguration&) = delete; + + // Builder is passed to plugins, etc... at initialization time to collect + // their configuration and assemble the published CoreConfiguration. + class Builder { + public: + private: + friend class CoreConfiguration; + + Builder(); + CoreConfiguration* Build(); + }; + + // Lifetime methods + + // Get the core configuration; if it does not exist, create it. + static const CoreConfiguration& Get() { + CoreConfiguration* p = config_.load(std::memory_order_acquire); + if (p != nullptr) { + return *p; + } + return BuildNewAndMaybeSet(); + } + + // Drop the core configuration. Users must ensure no other threads are + // accessing the configuration. + static void Reset(); + + // Accessors + + private: + CoreConfiguration(); + + // Create a new CoreConfiguration, and either set it or throw it away. + // We allow multiple CoreConfiguration's to be created in parallel. + static const CoreConfiguration& BuildNewAndMaybeSet(); + + // The configuration + static std::atomic config_; +}; + +extern void BuildCoreConfiguration(CoreConfiguration::Builder* builder); + +} // namespace grpc_core + +#endif /* GRPC_CORE_LIB_CONFIG_CORE_CONFIGURATION_H */ diff --git a/test/core/config/BUILD b/test/core/config/BUILD new file mode 100644 index 00000000000..0d4ac2ca5ea --- /dev/null +++ b/test/core/config/BUILD @@ -0,0 +1,31 @@ +# Copyright 2021 gRPC authors. +# +# Licensed 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. + +load("//bazel:grpc_build_system.bzl", "grpc_cc_test", "grpc_package") + +licenses(["notice"]) + +grpc_package(name = "test/core/config") + +grpc_cc_test( + name = "core_configuration_test", + srcs = ["core_configuration_test.cc"], + external_deps = ["gtest"], + language = "c++", + uses_polling = False, + deps = [ + "//:config", + "//test/core/util:grpc_suppressions", + ], +) diff --git a/test/core/config/core_configuration_test.cc b/test/core/config/core_configuration_test.cc new file mode 100644 index 00000000000..af27528521d --- /dev/null +++ b/test/core/config/core_configuration_test.cc @@ -0,0 +1,71 @@ +// Copyright 2021 gRPC authors. +// +// Licensed 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 "src/core/lib/config/core_configuration.h" +#include +#include +#include + +namespace grpc_core { + +// Allow substitution of config builder - in real code this would iterate +// through all plugins +namespace testing { +using ConfigBuilderFunction = std::function; +static ConfigBuilderFunction g_mock_builder; +} // namespace testing + +void BuildCoreConfiguration(CoreConfiguration::Builder* builder) { + ::grpc_core::testing::g_mock_builder(builder); +} + +namespace testing { +// Helper for testing - clear out any state, rebuild configuration with fn being +// the initializer +void InitConfigWithBuilder(ConfigBuilderFunction fn) { + CoreConfiguration::Reset(); + g_mock_builder = fn; + CoreConfiguration::Get(); + g_mock_builder = nullptr; +} + +TEST(ConfigTest, NoopConfig) { + InitConfigWithBuilder([](CoreConfiguration::Builder*) {}); + CoreConfiguration::Get(); +} + +TEST(ConfigTest, ThreadedInit) { + CoreConfiguration::Reset(); + g_mock_builder = [](CoreConfiguration::Builder*) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + }; + std::vector threads; + threads.reserve(64); + for (int i = 0; i < 64; i++) { + threads.push_back(std::thread([]() { CoreConfiguration::Get(); })); + } + for (auto& t : threads) { + t.join(); + } + g_mock_builder = nullptr; + CoreConfiguration::Get(); +} +} // namespace testing + +} // namespace grpc_core + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index 3da05659499..6c3d0322ec1 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -4369,6 +4369,30 @@ ], "uses_polling": false }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "core_configuration_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": false,