[promises] Add a promise-based match operator (#37981)

Closes #37981

COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/37981 from ctiller:match-promise 8341b09901
PiperOrigin-RevId: 689223259
pull/37996/head
Craig Tiller 3 months ago committed by Copybara-Service
parent 53c7d45dd0
commit 8b7496172d
  1. 43
      CMakeLists.txt
  2. 19
      build_autogenerated.yaml
  3. 17
      src/core/BUILD
  4. 105
      src/core/lib/promise/match_promise.h
  5. 18
      test/core/promise/BUILD
  6. 66
      test/core/promise/match_promise_test.cc
  7. 24
      tools/run_tests/generated/tests.json

43
CMakeLists.txt generated

@ -1629,6 +1629,7 @@ if(gRPC_BUILD_TESTS)
add_dependencies(buildtests_cxx loop_test)
add_dependencies(buildtests_cxx lru_cache_test)
add_dependencies(buildtests_cxx map_pipe_test)
add_dependencies(buildtests_cxx match_promise_test)
add_dependencies(buildtests_cxx match_test)
add_dependencies(buildtests_cxx matchers_test)
add_dependencies(buildtests_cxx max_concurrent_streams_test)
@ -19871,6 +19872,48 @@ target_link_libraries(map_pipe_test
)
endif()
if(gRPC_BUILD_TESTS)
add_executable(match_promise_test
test/core/promise/match_promise_test.cc
)
if(WIN32 AND MSVC)
if(BUILD_SHARED_LIBS)
target_compile_definitions(match_promise_test
PRIVATE
"GPR_DLL_IMPORTS"
)
endif()
endif()
target_compile_features(match_promise_test PUBLIC cxx_std_14)
target_include_directories(match_promise_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(match_promise_test
${_gRPC_ALLTARGETS_LIBRARIES}
gtest
absl::type_traits
gpr
)
endif()
if(gRPC_BUILD_TESTS)

@ -12822,6 +12822,25 @@ targets:
- absl/status:statusor
- gpr
uses_polling: false
- name: match_promise_test
gtest: true
build: test
language: c++
headers:
- src/core/lib/promise/detail/promise_factory.h
- src/core/lib/promise/detail/promise_like.h
- src/core/lib/promise/match_promise.h
- src/core/lib/promise/poll.h
- src/core/lib/promise/promise.h
- src/core/util/overload.h
- test/core/promise/poll_matcher.h
src:
- test/core/promise/match_promise_test.cc
deps:
- gtest
- absl/meta:type_traits
- gpr
uses_polling: false
- name: match_test
gtest: true
build: test

@ -681,6 +681,23 @@ grpc_cc_library(
],
)
grpc_cc_library(
name = "match_promise",
external_deps = [
"absl/strings",
"absl/types:variant",
],
language = "c++",
public_hdrs = ["lib/promise/match_promise.h"],
deps = [
"overload",
"poll",
"promise_factory",
"promise_like",
"//:gpr_platform",
],
)
grpc_cc_library(
name = "sleep",
srcs = [

@ -0,0 +1,105 @@
// Copyright 2024 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_SRC_CORE_LIB_PROMISE_MATCH_PROMISE_H
#define GRPC_SRC_CORE_LIB_PROMISE_MATCH_PROMISE_H
#include "absl/types/variant.h"
#include "src/core/lib/promise/detail/promise_factory.h"
#include "src/core/lib/promise/detail/promise_like.h"
#include "src/core/util/overload.h"
namespace grpc_core {
namespace promise_detail {
// This types job is to visit a supplied variant, and apply a mapping
// Constructor from input types to promises, returning a variant full of
// promises.
template <typename Constructor, typename... Ts>
struct ConstructPromiseVariantVisitor {
// Factory functions supplied to the top level `Match` object, wrapped by
// OverloadType to become overloaded members.
Constructor constructor;
// Helper function... only callable once.
// Given a value, construct a Promise Factory that accepts that value type,
// and uses the constructor type above to map from that type to a promise
// returned by the factory.
// We use the Promise Factory infrastructure to deal with all the common
// variants of factory signatures that we've found to be convenient.
template <typename T>
auto CallConstructorThenFactory(T x) {
OncePromiseFactory<T, Constructor> factory(std::move(constructor));
return factory.Make(std::move(x));
}
// Polling operator.
// Given a visited type T, construct a Promise Factory, use it, and then cast
// the result into a variant type that covers ALL of the possible return types
// given the input types listed in Ts...
template <typename T>
auto operator()(T x)
-> absl::variant<promise_detail::PromiseLike<
decltype(CallConstructorThenFactory(std::declval<Ts>()))>...> {
return CallConstructorThenFactory(x);
}
};
// Visitor function for PromiseVariant - calls the poll operator on the inner
// type
class PollVisitor {
public:
template <typename T>
auto operator()(T& x) {
return x();
}
};
// Helper type - given a variant V, provides the poll operator (which simply
// visits the inner type on the variant with PollVisitor)
template <typename V>
class PromiseVariant {
public:
explicit PromiseVariant(V variant) : variant_(std::move(variant)) {}
auto operator()() { return absl::visit(PollVisitor(), variant_); }
private:
V variant_;
};
} // namespace promise_detail
// Match for promises
// Like the Match function takes a variant of some set of types,
// and a set of functions - one per variant type.
// We use these functions as Promise Factories, and return a Promise that can be
// polled selected by the type that was in the variant.
template <typename... Fs, typename... Ts>
auto MatchPromise(absl::variant<Ts...> value, Fs... fs) {
// Construct a variant of promises using the factory functions fs, selected by
// the type held by value.
auto body = absl::visit(
promise_detail::ConstructPromiseVariantVisitor<OverloadType<Fs...>,
Ts...>{
OverloadType<Fs...>(std::move(fs)...)},
std::move(value));
// Wrap that in a PromiseVariant that provides the promise API on the wrapped
// variant.
return promise_detail::PromiseVariant<decltype(body)>(std::move(body));
}
} // namespace grpc_core
#endif // GRPC_SRC_CORE_LIB_PROMISE_MATCH_PROMISE_H

@ -152,6 +152,24 @@ grpc_cc_test(
],
)
grpc_cc_test(
name = "match_promise_test",
srcs = ["match_promise_test.cc"],
external_deps = [
"absl/functional:any_invocable",
"gtest",
],
language = "c++",
tags = ["promise_test"],
uses_event_engine = False,
uses_polling = False,
deps = [
"poll_matcher",
"//:promise",
"//src/core:match_promise",
],
)
grpc_cc_test(
name = "race_test",
srcs = ["race_test.cc"],

@ -0,0 +1,66 @@
// 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/match_promise.h"
#include <memory>
#include "absl/strings/str_cat.h"
#include "gtest/gtest.h"
#include "src/core/lib/promise/promise.h"
#include "test/core/promise/poll_matcher.h"
namespace grpc_core {
TEST(MatchPromiseTest, Works) {
struct Int {
int x;
};
struct Float {
float x;
};
using V = absl::variant<Int, Float, std::string>;
auto make_promise = [](V v) -> Promise<std::string> {
return MatchPromise(
std::move(v),
[](Float x) mutable {
return [n = 3, x]() mutable -> Poll<std::string> {
--n;
if (n > 0) return Pending{};
return absl::StrCat(x.x);
};
},
[](Int x) {
return []() mutable -> Poll<std::string> { return Pending{}; };
},
[](std::string x) { return x; });
};
auto promise = make_promise(V(Float{3.0f}));
EXPECT_THAT(promise(), IsPending());
EXPECT_THAT(promise(), IsPending());
EXPECT_THAT(promise(), IsReady("3"));
promise = make_promise(V(Int{42}));
for (int i = 0; i < 10000; i++) {
EXPECT_THAT(promise(), IsPending());
}
promise = make_promise(V("hello"));
EXPECT_THAT(promise(), IsReady("hello"));
}
} // namespace grpc_core
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

@ -5753,6 +5753,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": "match_promise_test",
"platforms": [
"linux",
"mac",
"posix",
"windows"
],
"uses_polling": false
},
{
"args": [],
"benchmark": false,

Loading…
Cancel
Save