diff --git a/BUILD b/BUILD index 9c0acfdc1ff..4b562e31a7a 100644 --- a/BUILD +++ b/BUILD @@ -2235,6 +2235,17 @@ grpc_cc_library( ], ) +grpc_cc_library( + name = "single_set_ptr", + hdrs = [ + "src/core/lib/gprpp/single_set_ptr.h", + ], + language = "c++", + deps = [ + "gpr_base", + ], +) + grpc_cc_library( name = "channel_stack_builder", srcs = [ diff --git a/CMakeLists.txt b/CMakeLists.txt index e536e19cb27..5791af20154 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -979,6 +979,7 @@ if(gRPC_BUILD_TESTS) add_dependencies(buildtests_cxx settings_timeout_test) add_dependencies(buildtests_cxx shutdown_test) add_dependencies(buildtests_cxx simple_request_bad_client_test) + add_dependencies(buildtests_cxx single_set_ptr_test) add_dependencies(buildtests_cxx sleep_test) add_dependencies(buildtests_cxx smoke_test) add_dependencies(buildtests_cxx sockaddr_utils_test) @@ -14895,6 +14896,98 @@ target_link_libraries(simple_request_bad_client_test ) +endif() +if(gRPC_BUILD_TESTS) + +add_executable(single_set_ptr_test + src/core/ext/upb-generated/google/protobuf/any.upb.c + src/core/ext/upb-generated/google/rpc/status.upb.c + src/core/lib/gpr/alloc.cc + src/core/lib/gpr/atm.cc + src/core/lib/gpr/cpu_iphone.cc + src/core/lib/gpr/cpu_linux.cc + src/core/lib/gpr/cpu_posix.cc + src/core/lib/gpr/cpu_windows.cc + src/core/lib/gpr/env_linux.cc + src/core/lib/gpr/env_posix.cc + src/core/lib/gpr/env_windows.cc + src/core/lib/gpr/log.cc + src/core/lib/gpr/log_android.cc + src/core/lib/gpr/log_linux.cc + src/core/lib/gpr/log_posix.cc + src/core/lib/gpr/log_windows.cc + src/core/lib/gpr/murmur_hash.cc + src/core/lib/gpr/string.cc + src/core/lib/gpr/string_posix.cc + src/core/lib/gpr/string_util_windows.cc + src/core/lib/gpr/string_windows.cc + src/core/lib/gpr/sync.cc + src/core/lib/gpr/sync_abseil.cc + src/core/lib/gpr/sync_posix.cc + src/core/lib/gpr/sync_windows.cc + src/core/lib/gpr/time.cc + src/core/lib/gpr/time_posix.cc + src/core/lib/gpr/time_precise.cc + src/core/lib/gpr/time_windows.cc + src/core/lib/gpr/tmpfile_msys.cc + src/core/lib/gpr/tmpfile_posix.cc + src/core/lib/gpr/tmpfile_windows.cc + src/core/lib/gpr/wrap_memcpy.cc + src/core/lib/gprpp/examine_stack.cc + src/core/lib/gprpp/fork.cc + src/core/lib/gprpp/global_config_env.cc + src/core/lib/gprpp/host_port.cc + src/core/lib/gprpp/mpscq.cc + src/core/lib/gprpp/stat_posix.cc + src/core/lib/gprpp/stat_windows.cc + src/core/lib/gprpp/status_helper.cc + src/core/lib/gprpp/thd_posix.cc + src/core/lib/gprpp/thd_windows.cc + src/core/lib/gprpp/time_util.cc + src/core/lib/profiling/basic_timers.cc + src/core/lib/profiling/stap_timers.cc + test/core/gprpp/single_set_ptr_test.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(single_set_ptr_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(single_set_ptr_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + absl::base + absl::core_headers + absl::memory + absl::random_random + absl::status + absl::cord + absl::str_format + absl::strings + absl::synchronization + absl::time + absl::optional + upb +) + + endif() if(gRPC_BUILD_TESTS) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index d58dd9e1058..7c0f65d2428 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -7375,6 +7375,104 @@ targets: - test/core/end2end/cq_verifier.cc deps: - grpc_test_util +- name: single_set_ptr_test + gtest: true + build: test + language: c++ + headers: + - src/core/ext/upb-generated/google/protobuf/any.upb.h + - src/core/ext/upb-generated/google/rpc/status.upb.h + - src/core/lib/gpr/alloc.h + - src/core/lib/gpr/env.h + - src/core/lib/gpr/murmur_hash.h + - src/core/lib/gpr/spinlock.h + - src/core/lib/gpr/string.h + - src/core/lib/gpr/string_windows.h + - src/core/lib/gpr/time_precise.h + - src/core/lib/gpr/tls.h + - src/core/lib/gpr/tmpfile.h + - src/core/lib/gpr/useful.h + - src/core/lib/gprpp/construct_destruct.h + - src/core/lib/gprpp/debug_location.h + - src/core/lib/gprpp/examine_stack.h + - src/core/lib/gprpp/fork.h + - src/core/lib/gprpp/global_config.h + - src/core/lib/gprpp/global_config_custom.h + - src/core/lib/gprpp/global_config_env.h + - src/core/lib/gprpp/global_config_generic.h + - src/core/lib/gprpp/host_port.h + - src/core/lib/gprpp/manual_constructor.h + - src/core/lib/gprpp/memory.h + - src/core/lib/gprpp/mpscq.h + - src/core/lib/gprpp/single_set_ptr.h + - src/core/lib/gprpp/stat.h + - src/core/lib/gprpp/status_helper.h + - src/core/lib/gprpp/sync.h + - src/core/lib/gprpp/thd.h + - src/core/lib/gprpp/time_util.h + - src/core/lib/profiling/timers.h + src: + - src/core/ext/upb-generated/google/protobuf/any.upb.c + - src/core/ext/upb-generated/google/rpc/status.upb.c + - src/core/lib/gpr/alloc.cc + - src/core/lib/gpr/atm.cc + - src/core/lib/gpr/cpu_iphone.cc + - src/core/lib/gpr/cpu_linux.cc + - src/core/lib/gpr/cpu_posix.cc + - src/core/lib/gpr/cpu_windows.cc + - src/core/lib/gpr/env_linux.cc + - src/core/lib/gpr/env_posix.cc + - src/core/lib/gpr/env_windows.cc + - src/core/lib/gpr/log.cc + - src/core/lib/gpr/log_android.cc + - src/core/lib/gpr/log_linux.cc + - src/core/lib/gpr/log_posix.cc + - src/core/lib/gpr/log_windows.cc + - src/core/lib/gpr/murmur_hash.cc + - src/core/lib/gpr/string.cc + - src/core/lib/gpr/string_posix.cc + - src/core/lib/gpr/string_util_windows.cc + - src/core/lib/gpr/string_windows.cc + - src/core/lib/gpr/sync.cc + - src/core/lib/gpr/sync_abseil.cc + - src/core/lib/gpr/sync_posix.cc + - src/core/lib/gpr/sync_windows.cc + - src/core/lib/gpr/time.cc + - src/core/lib/gpr/time_posix.cc + - src/core/lib/gpr/time_precise.cc + - src/core/lib/gpr/time_windows.cc + - src/core/lib/gpr/tmpfile_msys.cc + - src/core/lib/gpr/tmpfile_posix.cc + - src/core/lib/gpr/tmpfile_windows.cc + - src/core/lib/gpr/wrap_memcpy.cc + - src/core/lib/gprpp/examine_stack.cc + - src/core/lib/gprpp/fork.cc + - src/core/lib/gprpp/global_config_env.cc + - src/core/lib/gprpp/host_port.cc + - src/core/lib/gprpp/mpscq.cc + - src/core/lib/gprpp/stat_posix.cc + - src/core/lib/gprpp/stat_windows.cc + - src/core/lib/gprpp/status_helper.cc + - src/core/lib/gprpp/thd_posix.cc + - src/core/lib/gprpp/thd_windows.cc + - src/core/lib/gprpp/time_util.cc + - src/core/lib/profiling/basic_timers.cc + - src/core/lib/profiling/stap_timers.cc + - test/core/gprpp/single_set_ptr_test.cc + deps: + - absl/base:base + - absl/base:core_headers + - absl/memory:memory + - absl/random:random + - absl/status:status + - absl/strings:cord + - absl/strings:str_format + - absl/strings:strings + - absl/synchronization:synchronization + - absl/time:time + - absl/types:optional + - upb + uses_polling: false - name: sleep_test gtest: true build: test diff --git a/src/core/lib/gprpp/single_set_ptr.h b/src/core/lib/gprpp/single_set_ptr.h new file mode 100644 index 00000000000..8a53a428af8 --- /dev/null +++ b/src/core/lib/gprpp/single_set_ptr.h @@ -0,0 +1,83 @@ +// Copyright 2022 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_GPRPP_SINGLE_SET_PTR_H +#define GRPC_CORE_LIB_GPRPP_SINGLE_SET_PTR_H + +#include + +#include +#include + +#include + +namespace grpc_core { + +template > +class SingleSetPtr { + public: + SingleSetPtr() = default; + ~SingleSetPtr() { Delete(p_.load(std::memory_order_relaxed)); } + + SingleSetPtr(const SingleSetPtr&) = delete; + SingleSetPtr& operator=(const SingleSetPtr&) = delete; + SingleSetPtr(SingleSetPtr&& other) noexcept + : p_(other.p_.exchange(sentinel())) {} + SingleSetPtr& operator=(SingleSetPtr&& other) noexcept { + Set(other.p_.exchange(sentinel(), std::memory_order_acq_rel)); + return *this; + } + + // Set the pointer; + // if already set, return the pre-set value and delete ptr; + // if deleted, return nullptr and delete ptr. + T* Set(T* ptr) { + T* expected = nullptr; + if (!p_.compare_exchange_strong(expected, ptr, std::memory_order_acq_rel, + std::memory_order_acquire)) { + Delete(ptr); + return expected == sentinel() ? nullptr : expected; + } + return ptr; + } + + // Clear the pointer. Cannot be set again. + void Reset() { Delete(p_.exchange(sentinel(), std::memory_order_acq_rel)); } + + bool is_set() const { + T* p = p_.load(std::memory_order_acquire); + return p != nullptr && p != sentinel(); + } + + T* operator->() const { + T* p = p_.load(std::memory_order_acquire); + GPR_DEBUG_ASSERT(p != sentinel()); + GPR_DEBUG_ASSERT(p != nullptr); + return p; + } + + T& operator*() const { return *operator->(); } + + private: + static T* sentinel() { return reinterpret_cast(1); } + static void Delete(T* p) { + if (p == sentinel() || p == nullptr) return; + Deleter()(p); + } + std::atomic p_{nullptr}; +}; + +} // namespace grpc_core + +#endif // GRPC_CORE_LIB_GPRPP_SINGLE_SET_PTR_H diff --git a/test/core/gprpp/BUILD b/test/core/gprpp/BUILD index 501a37914c9..a04846faa2a 100644 --- a/test/core/gprpp/BUILD +++ b/test/core/gprpp/BUILD @@ -337,3 +337,15 @@ grpc_cc_test( "//test/core/util:grpc_suppressions", ], ) + +grpc_cc_test( + name = "single_set_ptr_test", + srcs = ["single_set_ptr_test.cc"], + external_deps = ["gtest"], + language = "c++", + uses_polling = False, + deps = [ + "//:single_set_ptr", + "//test/core/util:grpc_suppressions", + ], +) diff --git a/test/core/gprpp/single_set_ptr_test.cc b/test/core/gprpp/single_set_ptr_test.cc new file mode 100644 index 00000000000..a9410f030c7 --- /dev/null +++ b/test/core/gprpp/single_set_ptr_test.cc @@ -0,0 +1,63 @@ +// Copyright 2022 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/gprpp/single_set_ptr.h" + +#include + +#include + +namespace grpc_core { +namespace testing { + +TEST(SingleSetPtrTest, NoOp) { SingleSetPtr(); } + +TEST(SingleSetPtrTest, CanSet) { + SingleSetPtr p; + EXPECT_FALSE(p.is_set()); + EXPECT_DEATH_IF_SUPPORTED(gpr_log(GPR_ERROR, "%d", *p), ""); + p.Set(new int(42)); + EXPECT_EQ(*p, 42); +} + +TEST(SingleSetPtrTest, CanReset) { + SingleSetPtr p; + EXPECT_FALSE(p.is_set()); + p.Set(new int(42)); + EXPECT_TRUE(p.is_set()); + p.Set(new int(43)); + EXPECT_EQ(*p, 42); + p.Reset(); + EXPECT_FALSE(p.is_set()); +} + +TEST(SingleSetPtrTest, LotsOfSetters) { + SingleSetPtr p; + std::vector threads; + threads.reserve(100); + for (int i = 0; i < 100; i++) { + threads.emplace_back([&p, i]() { p.Set(new int(i)); }); + } + for (auto& t : threads) { + t.join(); + } +} + +} // 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 8d81d9dfcd2..bb636abe653 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -6471,6 +6471,30 @@ ], "uses_polling": true }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "single_set_ptr_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": false,