Promise factories (#26990)

* Poll type for promises library

* Library to talk about things that look like promises if you squint

* Library to talk about things that make promises

* build

* Changes to sync required for promise activities

* sanitized

* remove bad comment

* possible windows fix?

* fix

* ugh

* comment fix

* fix build

* Automated change: Fix sanity tests

Co-authored-by: ctiller <ctiller@users.noreply.github.com>
pull/27005/head
Craig Tiller 4 years ago committed by GitHub
parent caf62639eb
commit 71b2042c56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      BUILD
  2. 39
      CMakeLists.txt
  3. 18
      build_autogenerated.yaml
  4. 201
      src/core/lib/promise/detail/promise_factory.h
  5. 3
      src/core/lib/promise/detail/promise_like.h
  6. 17
      test/core/promise/BUILD
  7. 69
      test/core/promise/promise_factory_test.cc
  8. 24
      tools/run_tests/generated/tests.json

13
BUILD

@ -884,6 +884,19 @@ grpc_cc_library(
],
)
grpc_cc_library(
name = "promise_factory",
language = "c++",
public_hdrs = [
"src/core/lib/promise/detail/promise_factory.h",
],
deps = [
"gpr_platform",
"poll",
"promise_like",
],
)
grpc_cc_library(
name = "promise_status",
external_deps = [

39
CMakeLists.txt generated

@ -914,6 +914,7 @@ if(gRPC_BUILD_TESTS)
add_dependencies(buildtests_cxx poll_test)
add_dependencies(buildtests_cxx popularity_count_test)
add_dependencies(buildtests_cxx port_sharing_end2end_test)
add_dependencies(buildtests_cxx promise_factory_test)
add_dependencies(buildtests_cxx promise_test)
add_dependencies(buildtests_cxx proto_server_reflection_test)
add_dependencies(buildtests_cxx proto_utils_test)
@ -12971,6 +12972,44 @@ target_link_libraries(port_sharing_end2end_test
)
endif()
if(gRPC_BUILD_TESTS)
add_executable(promise_factory_test
test/core/promise/promise_factory_test.cc
third_party/googletest/googletest/src/gtest-all.cc
third_party/googletest/googlemock/src/gmock-all.cc
)
target_include_directories(promise_factory_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(promise_factory_test
${_gRPC_PROTOBUF_LIBRARIES}
${_gRPC_ALLTARGETS_LIBRARIES}
absl::bind_front
absl::optional
absl::variant
absl::utility
)
endif()
if(gRPC_BUILD_TESTS)

@ -6041,6 +6041,24 @@ targets:
- test/cpp/end2end/test_service_impl.cc
deps:
- grpc++_test_util
- name: promise_factory_test
gtest: true
build: test
language: c++
headers:
- src/core/lib/gprpp/capture.h
- src/core/lib/promise/detail/promise_factory.h
- src/core/lib/promise/detail/promise_like.h
- src/core/lib/promise/poll.h
- src/core/lib/promise/promise.h
src:
- test/core/promise/promise_factory_test.cc
deps:
- absl/functional:bind_front
- absl/types:optional
- absl/types:variant
- absl/utility:utility
uses_polling: false
- name: promise_test
gtest: true
build: test

@ -0,0 +1,201 @@
// 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_PROMISE_FACTORY_H
#define GRPC_CORE_LIB_PROMISE_DETAIL_PROMISE_FACTORY_H
#include <grpc/impl/codegen/port_platform.h>
#include "src/core/lib/promise/detail/promise_like.h"
#include "src/core/lib/promise/poll.h"
#include "absl/meta/type_traits.h"
// PromiseFactory is an adaptor class.
//
// Where a Promise is a thing that's polled periodically, a PromiseFactory
// creates a Promise. Within this Promise/Activity framework, PromiseFactory's
// then provide the edges for computation -- invoked at state transition
// boundaries to provide the new steady state.
//
// A PromiseFactory formally is f(A) -> Promise<T> for some types A & T.
// This get a bit awkward and inapproprate to write however, and so the type
// contained herein can adapt various kinds of callable into the correct form.
// Of course a callable of a single argument returning a Promise will see an
// identity translation. One taking no arguments and returning a Promise
// similarly.
//
// A Promise passed to a PromiseFactory will yield a PromiseFactory that
// returns just that Promise.
//
// Generalizing slightly, a callable taking a single argument A and returning a
// Poll<T> will yield a PromiseFactory that captures it's argument A and
// returns a Poll<T>.
//
// Since various consumers of PromiseFactory run either repeatedly through an
// overarching Promises lifetime, or just once, and we can optimize just once
// by moving the contents of the PromiseFactory, two factory methods are
// provided: Once, that can be called just once, and Repeated, that can (wait
// for it) be called Repeatedly.
namespace grpc_core {
namespace promise_detail {
// Helper trait: given a T, and T x, is calling x() legal?
template <typename T, typename Ignored = void>
struct IsVoidCallableT {
static constexpr bool value = false;
};
template <typename F>
struct IsVoidCallableT<F, absl::void_t<decltype(std::declval<F>()())>> {
static constexpr bool value = true;
};
// Wrap that trait in some nice syntax.
template <typename T>
constexpr bool IsVoidCallable() {
return IsVoidCallableT<T>::value;
}
// T -> T, const T& -> T
template <typename T>
using RemoveCVRef = absl::remove_cv_t<absl::remove_reference_t<T>>;
// Given F(A,B,C,...), what's the return type?
template <typename T, typename Ignored = void>
struct ResultOfTInner;
template <typename F, typename... Args>
struct ResultOfTInner<F(Args...), absl::void_t<decltype(std::declval<F>()(
std::declval<Args>()...))>> {
using T = decltype(std::declval<F>()(std::declval<Args>()...));
};
template <typename T>
struct ResultOfT;
template <typename F, typename... Args>
struct ResultOfT<F(Args...)> {
using FP = RemoveCVRef<F>;
using T = typename ResultOfTInner<FP(Args...)>::T;
};
template <typename T>
using ResultOf = typename ResultOfT<T>::T;
// Captures the promise functor and the argument passed.
// Provides the interface of a promise.
template <typename F, typename Arg>
class Curried {
public:
Curried(F&& f, Arg&& arg)
: f_(std::forward<F>(f)), arg_(std::forward<Arg>(arg)) {}
using Result = decltype(std::declval<F>()(std::declval<Arg>()));
Result operator()() { return f_(arg_); }
private:
GPR_NO_UNIQUE_ADDRESS F f_;
GPR_NO_UNIQUE_ADDRESS Arg arg_;
};
// Promote a callable(A) -> T | Poll<T> to a PromiseFactory(A) -> Promise<T> by
// capturing A.
template <typename A, typename F>
absl::enable_if_t<!IsVoidCallable<ResultOf<F(A)>>(), PromiseLike<Curried<A, F>>>
PromiseFactoryImpl(F&& f, A&& arg) {
return Curried<A, F>(std::forward<F>(f), std::forward<A>(arg));
}
// Promote a callable() -> T|Poll<T> to a PromiseFactory(A) -> Promise<T>
// by dropping the argument passed to the factory.
template <typename A, typename F>
absl::enable_if_t<!IsVoidCallable<ResultOf<F()>>(), PromiseLike<RemoveCVRef<F>>>
PromiseFactoryImpl(F f, A&&) {
return PromiseLike<F>(std::move(f));
}
// Promote a callable() -> T|Poll<T> to a PromiseFactory() -> Promise<T>
template <typename F>
absl::enable_if_t<!IsVoidCallable<ResultOf<F()>>(), PromiseLike<RemoveCVRef<F>>>
PromiseFactoryImpl(F f) {
return PromiseLike<F>(std::move(f));
}
// Given a callable(A) -> Promise<T>, name it a PromiseFactory and use it.
template <typename A, typename F>
absl::enable_if_t<IsVoidCallable<ResultOf<F(A)>>(),
PromiseLike<decltype(std::declval<F>()(std::declval<A>()))>>
PromiseFactoryImpl(F&& f, A&& arg) {
return f(std::forward<A>(arg));
}
// Given a callable() -> Promise<T>, promote it to a
// PromiseFactory(A) -> Promise<T> by dropping the first argument.
template <typename A, typename F>
absl::enable_if_t<IsVoidCallable<ResultOf<F()>>(),
PromiseLike<decltype(std::declval<F>()())>>
PromiseFactoryImpl(F&& f, A&&) {
return f();
}
// Given a callable() -> Promise<T>, name it a PromiseFactory and use it.
template <typename F>
absl::enable_if_t<IsVoidCallable<ResultOf<F()>>(),
PromiseLike<decltype(std::declval<F>()())>>
PromiseFactoryImpl(F&& f) {
return f();
};
template <typename A, typename F>
class PromiseFactory {
private:
GPR_NO_UNIQUE_ADDRESS F f_;
public:
using Arg = A;
explicit PromiseFactory(F f) : f_(std::move(f)) {}
auto Once(Arg&& a)
-> decltype(PromiseFactoryImpl(std::move(f_), std::forward<Arg>(a))) {
return PromiseFactoryImpl(std::move(f_), std::forward<Arg>(a));
}
auto Repeated(Arg&& a) const
-> decltype(PromiseFactoryImpl(f_, std::forward<Arg>(a))) {
return PromiseFactoryImpl(f_, std::forward<Arg>(a));
}
};
template <typename F>
class PromiseFactory<void, F> {
private:
GPR_NO_UNIQUE_ADDRESS F f_;
public:
using Arg = void;
explicit PromiseFactory(F f) : f_(std::move(f)) {}
auto Once() -> decltype(PromiseFactoryImpl(std::move(f_))) {
return PromiseFactoryImpl(std::move(f_));
}
auto Repeated() const -> decltype(PromiseFactoryImpl(f_)) {
return PromiseFactoryImpl(f_);
}
};
} // namespace promise_detail
} // namespace grpc_core
#endif // GRPC_CORE_LIB_PROMISE_DETAIL_PROMISE_FACTORY_H

@ -68,7 +68,8 @@ class PromiseLike {
GPR_NO_UNIQUE_ADDRESS F f_;
public:
explicit PromiseLike(F&& f) : f_(std::forward<F>(f)) {}
// NOLINTNEXTLINE - internal detail that drastically simplifies calling code.
PromiseLike(F&& f) : f_(std::forward<F>(f)) {}
auto operator()() -> decltype(WrapInPoll(f_())) { return WrapInPoll(f_()); }
using Result = typename PollTraits<decltype(WrapInPoll(f_()))>::Type;
};

@ -41,3 +41,20 @@ grpc_cc_test(
"//test/core/util:grpc_suppressions",
],
)
grpc_cc_test(
name = "promise_factory_test",
srcs = ["promise_factory_test.cc"],
external_deps = [
"gtest",
"absl/functional:bind_front",
],
language = "c++",
uses_polling = False,
deps = [
"//:capture",
"//:promise",
"//:promise_factory",
"//test/core/util:grpc_suppressions",
],
)

@ -0,0 +1,69 @@
// 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/detail/promise_factory.h"
#include <gtest/gtest.h>
#include "absl/functional/bind_front.h"
#include "src/core/lib/gprpp/capture.h"
#include "src/core/lib/promise/promise.h"
namespace grpc_core {
namespace promise_detail {
namespace testing {
template <typename Arg, typename F>
PromiseFactory<Arg, F> MakeFactory(F f) {
return PromiseFactory<Arg, F>(std::move(f));
}
TEST(AdaptorTest, FactoryFromPromise) {
EXPECT_EQ(
MakeFactory<void>([]() { return Poll<int>(Poll<int>(42)); }).Once()(),
Poll<int>(42));
EXPECT_EQ(
MakeFactory<void>([]() { return Poll<int>(Poll<int>(42)); }).Repeated()(),
Poll<int>(42));
EXPECT_EQ(MakeFactory<void>(Promise<int>([]() {
return Poll<int>(Poll<int>(42));
})).Once()(),
Poll<int>(42));
EXPECT_EQ(MakeFactory<void>(Promise<int>([]() {
return Poll<int>(Poll<int>(42));
})).Repeated()(),
Poll<int>(42));
}
TEST(AdaptorTest, FactoryFromBindFrontPromise) {
EXPECT_EQ(MakeFactory<void>(
absl::bind_front([](int i) { return Poll<int>(i); }, 42))
.Once()(),
Poll<int>(42));
}
TEST(AdaptorTest, FactoryFromCapturePromise) {
EXPECT_EQ(MakeFactory<void>(
grpc_core::Capture([](int* i) { return Poll<int>(*i); }, 42))
.Once()(),
Poll<int>(42));
}
} // namespace testing
} // namespace promise_detail
} // namespace grpc_core
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

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

Loading…
Cancel
Save