From f292f001eef0810ac15de7c5e4c3ce2927d86537 Mon Sep 17 00:00:00 2001 From: Craig Tiller Date: Thu, 19 Aug 2021 21:06:38 -0700 Subject: [PATCH] Promise join combinator (#26918) * Add construct/destruct helper functions Will be used in upcoming promise implementation * Poll type for promises library * Library to talk about things that look like promises if you squint * Helper code for promises to deal with status types generically * Library to talk about things that make promises * build * Join combinator for promise library * Changes to sync required for promise activities * sanitized * Automated change: Fix sanity tests * Automated change: Fix sanity tests * Update basic_join.h * Automated change: Fix sanity tests Co-authored-by: ctiller --- BUILD | 40 +++++ CMakeLists.txt | 74 ++++++++ build_autogenerated.yaml | 37 ++++ src/core/lib/promise/detail/basic_join.h | 191 +++++++++++++++++++++ src/core/lib/promise/detail/promise_like.h | 2 +- src/core/lib/promise/join.h | 53 ++++++ src/core/lib/promise/try_join.h | 76 ++++++++ test/core/promise/BUILD | 24 +++ test/core/promise/join_test.cc | 40 +++++ test/core/promise/try_join_test.cc | 79 +++++++++ tools/run_tests/generated/tests.json | 48 ++++++ 11 files changed, 663 insertions(+), 1 deletion(-) create mode 100644 src/core/lib/promise/detail/basic_join.h create mode 100644 src/core/lib/promise/join.h create mode 100644 src/core/lib/promise/try_join.h create mode 100644 test/core/promise/join_test.cc create mode 100644 test/core/promise/try_join_test.cc diff --git a/BUILD b/BUILD index 7f2ed69732d..e333f537d64 100644 --- a/BUILD +++ b/BUILD @@ -996,6 +996,46 @@ grpc_cc_library( deps = ["gpr_platform"], ) +grpc_cc_library( + name = "basic_join", + language = "c++", + public_hdrs = [ + "src/core/lib/promise/detail/basic_join.h", + ], + deps = [ + "bitset", + "construct_destruct", + "gpr_platform", + "poll", + "promise_factory", + ], +) + +grpc_cc_library( + name = "join", + language = "c++", + public_hdrs = [ + "src/core/lib/promise/join.h", + ], + deps = [ + "basic_join", + "gpr_platform", + ], +) + +grpc_cc_library( + name = "try_join", + language = "c++", + public_hdrs = [ + "src/core/lib/promise/try_join.h", + ], + deps = [ + "basic_join", + "gpr_platform", + "promise_status", + ], +) + grpc_cc_library( name = "ref_counted", language = "c++", diff --git a/CMakeLists.txt b/CMakeLists.txt index e1ac5ed7cae..222e41ad41e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -896,6 +896,7 @@ if(gRPC_BUILD_TESTS) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) add_dependencies(buildtests_cxx interop_test) endif() + add_dependencies(buildtests_cxx join_test) add_dependencies(buildtests_cxx json_test) add_dependencies(buildtests_cxx large_metadata_bad_client_test) add_dependencies(buildtests_cxx lb_get_cpu_stats_test) @@ -987,6 +988,7 @@ if(gRPC_BUILD_TESTS) add_dependencies(buildtests_cxx timer_test) add_dependencies(buildtests_cxx tls_security_connector_test) add_dependencies(buildtests_cxx too_many_pings_test) + add_dependencies(buildtests_cxx try_join_test) add_dependencies(buildtests_cxx unknown_frame_bad_client_test) add_dependencies(buildtests_cxx uri_parser_test) add_dependencies(buildtests_cxx window_overflow_bad_client_test) @@ -12296,6 +12298,41 @@ endif() endif() if(gRPC_BUILD_TESTS) +add_executable(join_test + test/core/promise/join_test.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(join_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(join_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + absl::variant +) + + +endif() +if(gRPC_BUILD_TESTS) + add_executable(json_test test/core/json/json_test.cc third_party/googletest/googletest/src/gtest-all.cc @@ -15239,6 +15276,43 @@ target_link_libraries(too_many_pings_test ) +endif() +if(gRPC_BUILD_TESTS) + +add_executable(try_join_test + test/core/promise/try_join_test.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(try_join_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(try_join_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + absl::status + absl::statusor + absl::variant +) + + endif() if(gRPC_BUILD_TESTS) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index 2015e13033b..eefe519a7bb 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -5805,6 +5805,23 @@ targets: - linux - posix - mac +- name: join_test + gtest: true + build: test + language: c++ + headers: + - src/core/lib/gprpp/bitset.h + - src/core/lib/gprpp/construct_destruct.h + - src/core/lib/promise/detail/basic_join.h + - src/core/lib/promise/detail/promise_factory.h + - src/core/lib/promise/detail/promise_like.h + - src/core/lib/promise/join.h + - src/core/lib/promise/poll.h + src: + - test/core/promise/join_test.cc + deps: + - absl/types:variant + uses_polling: false - name: json_fuzzer_test build: fuzzer language: c++ @@ -6848,6 +6865,26 @@ targets: deps: - grpc++_test_config - grpc++_test_util +- name: try_join_test + gtest: true + build: test + language: c++ + headers: + - src/core/lib/gprpp/bitset.h + - src/core/lib/gprpp/construct_destruct.h + - src/core/lib/promise/detail/basic_join.h + - src/core/lib/promise/detail/promise_factory.h + - src/core/lib/promise/detail/promise_like.h + - src/core/lib/promise/detail/status.h + - src/core/lib/promise/poll.h + - src/core/lib/promise/try_join.h + src: + - test/core/promise/try_join_test.cc + deps: + - absl/status:status + - absl/status:statusor + - absl/types:variant + uses_polling: false - name: unknown_frame_bad_client_test gtest: true build: test diff --git a/src/core/lib/promise/detail/basic_join.h b/src/core/lib/promise/detail/basic_join.h new file mode 100644 index 00000000000..453d281bc43 --- /dev/null +++ b/src/core/lib/promise/detail/basic_join.h @@ -0,0 +1,191 @@ +// 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_PROMISE_DETAIL_BASIC_JOIN_H +#define GRPC_CORE_LIB_PROMISE_DETAIL_BASIC_JOIN_H + +#include + +#include +#include + +#include "absl/utility/utility.h" +#include "src/core/lib/gprpp/bitset.h" +#include "src/core/lib/gprpp/construct_destruct.h" +#include "src/core/lib/promise/detail/promise_factory.h" +#include "src/core/lib/promise/poll.h" + +namespace grpc_core { +namespace promise_detail { + +// This union can either be a functor, or the result of the functor (after +// mapping via a trait). Allows us to remember the result of one joined functor +// until the rest are ready. +template +union Fused { + explicit Fused(F&& f) : f(std::forward(f)) {} + explicit Fused(PromiseLike&& f) : f(std::forward>(f)) {} + ~Fused() {} + // Wrap the functor in a PromiseLike to handle immediately returning functors + // and the like. + using Promise = PromiseLike; + GPR_NO_UNIQUE_ADDRESS Promise f; + // Compute the result type: We take the result of the promise, and pass it via + // our traits, so that, for example, TryJoin and take a StatusOr and just + // store a T. + using Result = typename Traits::template ResultType; + GPR_NO_UNIQUE_ADDRESS Result result; +}; + +// A join gets composed of joints... these are just wrappers around a Fused for +// their data, with some machinery as methods to get the system working. +template +struct Joint : public Joint { + // The index into Fs for this Joint + static constexpr size_t kIdx = sizeof...(Fs) - kRemaining; + // The next join (the one we derive from) + using NextJoint = Joint; + // From Fs, extract the functor for this joint. + using F = typename std::tuple_element>::type; + // Generate the Fused type for this functor. + using Fsd = Fused; + GPR_NO_UNIQUE_ADDRESS Fsd fused; + // Figure out what kind of bitmask will be used by the outer join. + using Bits = BitSet; + // Initialize from a tuple of pointers to Fs + explicit Joint(std::tuple fs) + : NextJoint(fs), fused(std::move(*std::get(fs))) {} + // Copy: assume that the Fuse is still in the promise state (since it's not + // legal to copy after the first poll!) + Joint(const Joint& j) : NextJoint(j), fused(j.fused.f) {} + // Move: assume that the Fuse is still in the promise state (since it's not + // legal to move after the first poll!) + Joint(Joint&& j) noexcept + : NextJoint(std::forward(j)), fused(std::move(j.fused.f)) {} + // Destruct: check bits to see if we're in promise or result state, and call + // the appropriate destructor. Recursively, call up through the join. + void DestructAll(const Bits& bits) { + if (!bits.is_set(kIdx)) { + Destruct(&fused.f); + } else { + Destruct(&fused.result); + } + NextJoint::DestructAll(bits); + } + // Poll all joints up, and then call finally. + template + auto Run(Bits* bits, F finally) -> decltype(finally()) { + // If we're still in the promise state... + if (!bits->is_set(kIdx)) { + // Poll the promise + auto r = fused.f(); + if (auto* p = absl::get_if(&r)) { + // If it's done, then ask the trait to unwrap it and store that result + // in the Fused, and continue the iteration. Note that OnResult could + // instead choose to return a value instead of recursing through the + // iteration, in that case we continue returning the same result up. + // Here is where TryJoin can escape out. + return Traits::OnResult( + std::move(*p), [this, bits, &finally](typename Fsd::Result result) { + bits->set(kIdx); + Destruct(&fused.f); + Construct(&fused.result, std::move(result)); + return NextJoint::Run(bits, std::move(finally)); + }); + } + } + // That joint is still pending... we'll still poll the result of the joints. + return NextJoint::Run(bits, std::move(finally)); + } +}; + +// Terminating joint... for each of the recursions, do the thing we're supposed +// to do at the end. +template +struct Joint { + explicit Joint(std::tuple) {} + Joint(const Joint&) {} + Joint(Joint&&) noexcept {} + template + void DestructAll(const T&) {} + template + auto Run(BitSet*, F finally) -> decltype(finally()) { + return finally(); + } +}; + +template +class BasicJoin { + private: + // How many things are we joining? + static constexpr size_t N = sizeof...(Fs); + // Bitset: if a bit is 0, that joint is still in promise state. If it's 1, + // then the joint has a result. + GPR_NO_UNIQUE_ADDRESS BitSet state_; + // The actual joints, wrapped in an anonymous union to give us control of + // construction/destruction. + union { + GPR_NO_UNIQUE_ADDRESS Joint joints_; + }; + + // Access joint index I + template + Joint* GetJoint() { + return static_cast*>(&joints_); + } + + // The tuple of results of all our promises + using Tuple = std::tuple::Result...>; + + // Collect up all the results and construct a tuple. + template + Tuple Finish(absl::index_sequence) { + return Tuple(std::move(GetJoint()->fused.result)...); + } + + public: + explicit BasicJoin(Fs&&... fs) : joints_(std::tuple(&fs...)) {} + BasicJoin& operator=(const BasicJoin&) = delete; + // Copy a join - only available before polling. + BasicJoin(const BasicJoin& other) { + assert(other.state_.none()); + Construct(&joints_, other.joints_); + } + // Move a join - only available before polling. + BasicJoin(BasicJoin&& other) noexcept { + assert(other.state_.none()); + Construct(&joints_, std::move(other.joints_)); + } + ~BasicJoin() { joints_.DestructAll(state_); } + using Result = decltype(Traits::Wrap(std::declval())); + // Poll the join + Poll operator()() { + // Poll the joints... + return joints_.Run(&state_, [this]() -> Poll { + // If all of them are completed, collect the results, and then ask our + // traits to wrap them - allowing for example TryJoin to turn tuple + // into StatusOr>. + if (state_.all()) { + return Traits::Wrap(Finish(absl::make_index_sequence())); + } else { + return Pending(); + } + }); + } +}; + +} // namespace promise_detail +} // namespace grpc_core + +#endif // GRPC_CORE_LIB_PROMISE_DETAIL_BASIC_JOIN_H diff --git a/src/core/lib/promise/detail/promise_like.h b/src/core/lib/promise/detail/promise_like.h index c1890822165..eeaf768dbf3 100644 --- a/src/core/lib/promise/detail/promise_like.h +++ b/src/core/lib/promise/detail/promise_like.h @@ -54,7 +54,7 @@ struct PollWrapper { template struct PollWrapper> { - static Poll Wrap(Poll&& x) { return x; } + static Poll Wrap(Poll&& x) { return std::forward>(x); } }; template diff --git a/src/core/lib/promise/join.h b/src/core/lib/promise/join.h new file mode 100644 index 00000000000..d41840b2040 --- /dev/null +++ b/src/core/lib/promise/join.h @@ -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. + +#ifndef GRPC_CORE_LIB_PROMISE_JOIN_H +#define GRPC_CORE_LIB_PROMISE_JOIN_H + +#include + +#include "src/core/lib/promise/detail/basic_join.h" + +namespace grpc_core { +namespace promise_detail { + +struct JoinTraits { + template + using ResultType = absl::remove_reference_t; + template + static auto OnResult(T result, F kontinue) + -> decltype(kontinue(std::move(result))) { + return kontinue(std::move(result)); + } + template + static T Wrap(T x) { + return x; + } +}; + +template +using Join = BasicJoin; + +} // namespace promise_detail + +/// Combinator to run all promises to completion, and return a tuple +/// of their results. +template +promise_detail::Join Join(Promise... promises) { + return promise_detail::Join(std::move(promises)...); +} + +} // namespace grpc_core + +#endif // GRPC_CORE_LIB_PROMISE_JOIN_H diff --git a/src/core/lib/promise/try_join.h b/src/core/lib/promise/try_join.h new file mode 100644 index 00000000000..c15a6ee9608 --- /dev/null +++ b/src/core/lib/promise/try_join.h @@ -0,0 +1,76 @@ +// 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_PROMISE_TRY_JOIN_H +#define GRPC_CORE_LIB_PROMISE_TRY_JOIN_H + +#include + +#include "src/core/lib/promise/detail/basic_join.h" +#include "src/core/lib/promise/detail/status.h" + +namespace grpc_core { + +namespace promise_detail { + +// Extract the T from a StatusOr +template +T IntoResult(absl::StatusOr* status) { + return std::move(**status); +} + +// TryJoin returns a StatusOr> for f()->Poll>, +// g()->Poll>, h()->Poll>. If one of those should be a +// Status instead, we need a placeholder type to return, and this is it. +struct Empty {}; +inline Empty IntoResult(absl::Status*) { return Empty{}; } + +// Traits object to pass to BasicJoin +struct TryJoinTraits { + template + using ResultType = + decltype(IntoResult(std::declval*>())); + template + static auto OnResult(T result, F kontinue) + -> decltype(kontinue(IntoResult(&result))) { + using Result = + typename PollTraits::Type; + if (!result.ok()) { + return Result(IntoStatus(&result)); + } + return kontinue(IntoResult(&result)); + } + template + static absl::StatusOr Wrap(T x) { + return absl::StatusOr(std::move(x)); + } +}; + +// Implementation of TryJoin combinator. +template +using TryJoin = BasicJoin; + +} // namespace promise_detail + +// Run all promises. +// If any fail, cancel the rest and return the failure. +// If all succeed, return Ok(tuple-of-results). +template +promise_detail::TryJoin TryJoin(Promises... promises) { + return promise_detail::TryJoin(std::move(promises)...); +} + +} // namespace grpc_core + +#endif // GRPC_CORE_LIB_PROMISE_TRY_JOIN_H diff --git a/test/core/promise/BUILD b/test/core/promise/BUILD index 6ab0fd6fef9..2d4c0fb2c1e 100644 --- a/test/core/promise/BUILD +++ b/test/core/promise/BUILD @@ -124,3 +124,27 @@ grpc_cc_test( "//test/core/util:grpc_suppressions", ], ) + +grpc_cc_test( + name = "join_test", + srcs = ["join_test.cc"], + external_deps = ["gtest"], + language = "c++", + uses_polling = False, + deps = [ + "//:join", + "//test/core/util:grpc_suppressions", + ], +) + +grpc_cc_test( + name = "try_join_test", + srcs = ["try_join_test.cc"], + external_deps = ["gtest"], + language = "c++", + uses_polling = False, + deps = [ + "//:try_join", + "//test/core/util:grpc_suppressions", + ], +) diff --git a/test/core/promise/join_test.cc b/test/core/promise/join_test.cc new file mode 100644 index 00000000000..1a45b62ac69 --- /dev/null +++ b/test/core/promise/join_test.cc @@ -0,0 +1,40 @@ +// 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/promise/join.h" +#include + +namespace grpc_core { + +TEST(JoinTest, Join1) { + EXPECT_EQ(Join([] { return 3; })(), + (Poll>(std::make_tuple(3)))); +} + +TEST(JoinTest, Join2) { + EXPECT_EQ(Join([] { return 3; }, [] { return 4; })(), + (Poll>(std::make_tuple(3, 4)))); +} + +TEST(JoinTest, Join3) { + EXPECT_EQ(Join([] { return 3; }, [] { return 4; }, [] { return 5; })(), + (Poll>(std::make_tuple(3, 4, 5)))); +} + +} // namespace grpc_core + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/core/promise/try_join_test.cc b/test/core/promise/try_join_test.cc new file mode 100644 index 00000000000..64af30458b3 --- /dev/null +++ b/test/core/promise/try_join_test.cc @@ -0,0 +1,79 @@ +// 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/promise/try_join.h" +#include + +namespace grpc_core { + +template +using P = std::function>()>; + +template +P instant_ok(T x) { + return [x] { return absl::StatusOr(x); }; +} + +template +P instant_fail() { + return [] { return absl::StatusOr(); }; +} + +template +Poll>> ok(T... x) { + return absl::StatusOr>(absl::in_place, x...); +} + +template +Poll>> fail() { + return absl::StatusOr>(); +} + +template +P pending() { + return []() -> Poll> { return Pending(); }; +} + +TEST(TryJoinTest, Join1) { EXPECT_EQ(TryJoin(instant_ok(1))(), ok(1)); } + +TEST(TryJoinTest, Join1Fail) { + EXPECT_EQ(TryJoin(instant_fail())(), fail()); +} + +TEST(TryJoinTest, Join2Success) { + EXPECT_EQ(TryJoin(instant_ok(1), instant_ok(2))(), ok(1, 2)); +} + +TEST(TryJoinTest, Join2Fail1) { + EXPECT_EQ(TryJoin(instant_ok(1), instant_fail())(), (fail())); +} + +TEST(TryJoinTest, Join2Fail2) { + EXPECT_EQ(TryJoin(instant_fail(), instant_ok(2))(), (fail())); +} + +TEST(TryJoinTest, Join2Fail1P) { + EXPECT_EQ(TryJoin(pending(), instant_fail())(), (fail())); +} + +TEST(TryJoinTest, Join2Fail2P) { + EXPECT_EQ(TryJoin(instant_fail(), pending())(), (fail())); +} + +} // 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 a394caef23f..e8df02b4858 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -5249,6 +5249,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": "join_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": false, @@ -6889,6 +6913,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": "try_join_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": false,