[promises] Add a switch primitive (#35424)

Remove the old `switch` library - this used to be an implementation detail of `Seq`, `TrySeq` - but has become unused.

Add a new user facing primitive `Switch` that fills a similar role to `switch` in C++ - selecting a promise to execute based on a primitive discriminator - much like `If` allows selection based on a boolean discriminator now.

A future change will optimize this to actually lower the `Switch` into an actual `switch` statement, but for right now I want to get the functionality in.

Closes #35424

COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/35424 from ctiller:switchy 5308a914c6
PiperOrigin-RevId: 595140965
pull/35415/head
Craig Tiller 1 year ago committed by Copybara-Service
parent 1383c4629b
commit 75e5ebcb14
  1. 36
      CMakeLists.txt
  2. 18
      build_autogenerated.yaml
  3. 20
      src/core/BUILD
  4. 1455
      src/core/lib/promise/detail/switch.h
  5. 72
      src/core/lib/promise/switch.h
  6. 11
      test/core/promise/BUILD
  7. 69
      test/core/promise/switch_test.cc
  8. 78
      tools/codegen/core/gen_switch.py
  9. 24
      tools/run_tests/generated/tests.json

36
CMakeLists.txt generated

@ -1372,6 +1372,7 @@ if(gRPC_BUILD_TESTS)
add_dependencies(buildtests_cxx streams_not_seen_test)
add_dependencies(buildtests_cxx string_ref_test)
add_dependencies(buildtests_cxx string_test)
add_dependencies(buildtests_cxx switch_test)
add_dependencies(buildtests_cxx sync_test)
add_dependencies(buildtests_cxx system_roots_test)
add_dependencies(buildtests_cxx table_test)
@ -23771,6 +23772,41 @@ target_link_libraries(string_test
)
endif()
if(gRPC_BUILD_TESTS)
add_executable(switch_test
test/core/promise/switch_test.cc
)
target_compile_features(switch_test PUBLIC cxx_std_14)
target_include_directories(switch_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(switch_test
${_gRPC_ALLTARGETS_LIBRARIES}
gtest
absl::type_traits
absl::statusor
gpr
)
endif()
if(gRPC_BUILD_TESTS)

@ -16282,6 +16282,24 @@ targets:
- gtest
- grpc_test_util
uses_polling: false
- name: switch_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/if.h
- src/core/lib/promise/poll.h
- src/core/lib/promise/switch.h
src:
- test/core/promise/switch_test.cc
deps:
- gtest
- absl/meta:type_traits
- absl/status:statusor
- gpr
uses_polling: false
- name: sync_test
gtest: true
build: test

@ -619,6 +619,17 @@ grpc_cc_library(
],
)
grpc_cc_library(
name = "switch",
language = "c++",
public_hdrs = ["lib/promise/switch.h"],
deps = [
"if",
"promise_factory",
"//:gpr_platform",
],
)
grpc_cc_library(
name = "promise_status",
external_deps = [
@ -736,15 +747,6 @@ grpc_cc_library(
],
)
grpc_cc_library(
name = "switch",
language = "c++",
public_hdrs = [
"lib/promise/detail/switch.h",
],
deps = ["//:gpr_platform"],
)
grpc_cc_library(
name = "basic_seq",
language = "c++",

File diff suppressed because it is too large Load Diff

@ -0,0 +1,72 @@
// Copyright 2023 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_SWITCH_H
#define GRPC_SRC_CORE_LIB_PROMISE_SWITCH_H
#include <grpc/support/port_platform.h>
#include <memory>
#include <utility>
#include "src/core/lib/promise/detail/promise_factory.h"
#include "src/core/lib/promise/if.h"
namespace grpc_core {
namespace promise_detail {
template <typename D, typename F>
struct Case {
D discriminator;
F factory;
};
template <typename F>
struct Default {
F factory;
};
} // namespace promise_detail
template <typename D, typename PromiseFactory>
auto Case(D discriminator, PromiseFactory f) {
return promise_detail::Case<D, PromiseFactory>{discriminator, std::move(f)};
}
template <typename PromiseFactory>
auto Default(PromiseFactory f) {
return promise_detail::Default<PromiseFactory>{std::move(f)};
}
// Given a list of cases that result in promise factories, return a single
// promise chosen by the discriminator (the first argument of this function).
// e.g.:
// Switch(1, Case<1>([] { return 43; }), Case<2>([] { return 44; }))
// resolves to 43.
// TODO(ctiller): consider writing a code-generator like we do for seq/join
// so that this lowers into a C switch statement.
template <typename D, typename F>
auto Switch(D, promise_detail::Default<F> def) {
return promise_detail::OncePromiseFactory<void, F>(std::move(def.factory))
.Make();
}
template <typename D, typename F, typename... Others>
auto Switch(D discriminator, promise_detail::Case<D, F> c, Others... others) {
return If(discriminator == c.discriminator, std::move(c.factory),
Switch(discriminator, std::move(others)...));
}
} // namespace grpc_core
#endif // GRPC_SRC_CORE_LIB_PROMISE_SWITCH_H

@ -197,6 +197,17 @@ grpc_cc_test(
deps = ["//src/core:if"],
)
grpc_cc_test(
name = "switch_test",
srcs = ["switch_test.cc"],
external_deps = ["gtest"],
language = "c++",
tags = ["promise_test"],
uses_event_engine = False,
uses_polling = False,
deps = ["//src/core:switch"],
)
grpc_cc_test(
name = "loop_test",
srcs = ["loop_test.cc"],

@ -0,0 +1,69 @@
// Copyright 2023 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/switch.h"
#include "gtest/gtest.h"
namespace grpc_core {
TEST(SwitchTest, JustDefault) {
EXPECT_EQ(Switch(42, Default([] { return 1; }))(), Poll<int>(1));
}
TEST(SwitchTest, ThreeCases) {
auto test_switch = [](int d) {
return Switch(d, Case(1, [] { return 25; }), Case(2, [] { return 95; }),
Case(3, [] { return 68; }), Default([] { return 52; }));
};
EXPECT_EQ(test_switch(0)(), Poll<int>(52));
EXPECT_EQ(test_switch(1)(), Poll<int>(25));
EXPECT_EQ(test_switch(2)(), Poll<int>(95));
EXPECT_EQ(test_switch(3)(), Poll<int>(68));
EXPECT_EQ(test_switch(4)(), Poll<int>(52));
}
TEST(SwitchTest, Pending) {
auto test_switch = [](int d) {
return Switch(d, Case(42, []() -> Poll<int> { return Pending{}; }),
Case(1, [] { return 25; }), Case(2, [] { return 95; }),
Case(3, [] { return 68; }), Default([] { return 52; }));
};
EXPECT_EQ(test_switch(0)(), Poll<int>(52));
EXPECT_EQ(test_switch(1)(), Poll<int>(25));
EXPECT_EQ(test_switch(2)(), Poll<int>(95));
EXPECT_EQ(test_switch(3)(), Poll<int>(68));
EXPECT_EQ(test_switch(4)(), Poll<int>(52));
EXPECT_EQ(test_switch(42)(), Poll<int>(Pending{}));
}
TEST(SwitchTest, ThreeCasesFromEnum) {
enum class X : uint8_t { A, B, C };
auto test_switch = [](X d) {
return Switch(d, Case(X::A, [] { return 25; }),
Case(X::B, [] { return 95; }), Case(X::C, [] { return 68; }),
Default([] { return 52; }));
};
EXPECT_EQ(test_switch(X::A)(), Poll<int>(25));
EXPECT_EQ(test_switch(X::B)(), Poll<int>(95));
EXPECT_EQ(test_switch(X::C)(), Poll<int>(68));
}
} // namespace grpc_core
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

@ -1,78 +0,0 @@
#!/usr/bin/env python3
# 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.
import sys
# utility: print a big comment block into a set of files
def put_banner(files, banner):
for f in files:
print("/*", file=f)
for line in banner:
print(" * %s" % line, file=f)
print(" */", file=f)
print("", file=f)
with open("src/core/lib/promise/detail/switch.h", "w") as H:
# copy-paste copyright notice from this file
with open(sys.argv[0]) as my_source:
copyright = []
for line in my_source:
if line[0] != "#":
break
for line in my_source:
if line[0] == "#":
copyright.append(line)
break
for line in my_source:
if line[0] != "#":
break
copyright.append(line)
put_banner([H], [line[2:].rstrip() for line in copyright])
put_banner([H], ["Automatically generated by %s" % sys.argv[0]])
print("#ifndef GRPC_CORE_LIB_PROMISE_DETAIL_SWITCH_H", file=H)
print("#define GRPC_CORE_LIB_PROMISE_DETAIL_SWITCH_H", file=H)
print("", file=H)
print("#include <grpc/support/port_platform.h>", file=H)
print("", file=H)
print("#include <stdlib.h>", file=H)
print("", file=H)
print("namespace grpc_core {", file=H)
for n in range(1, 33):
print("", file=H)
print(
"template <typename R, %s> R Switch(char idx, %s) {"
% (
", ".join("typename F%d" % i for i in range(0, n)),
", ".join("F%d f%d" % (i, i) for i in range(0, n)),
),
file=H,
)
print(" switch (idx) {", file=H)
for i in range(0, n):
print(" case %d: return f%d();" % (i, i), file=H)
print(" }", file=H)
print(" abort();", file=H)
print("}", file=H)
print("", file=H)
print("}", file=H)
print("", file=H)
print("#endif // GRPC_CORE_LIB_PROMISE_DETAIL_SWITCH_H", file=H)

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

Loading…
Cancel
Save