[promises] Remove TryConcurrently - it was the wrong idea (#32298)
parent
860947605b
commit
c9b47f8584
7 changed files with 0 additions and 795 deletions
@ -1,370 +0,0 @@ |
|||||||
// 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_SRC_CORE_LIB_PROMISE_TRY_CONCURRENTLY_H |
|
||||||
#define GRPC_SRC_CORE_LIB_PROMISE_TRY_CONCURRENTLY_H |
|
||||||
|
|
||||||
#include <grpc/support/port_platform.h> |
|
||||||
|
|
||||||
#include <stddef.h> |
|
||||||
|
|
||||||
#include <cstdint> |
|
||||||
#include <string> |
|
||||||
#include <type_traits> |
|
||||||
#include <utility> |
|
||||||
|
|
||||||
#include "absl/strings/str_cat.h" |
|
||||||
#include "absl/types/variant.h" |
|
||||||
|
|
||||||
#include <grpc/support/log.h> |
|
||||||
|
|
||||||
#include "src/core/lib/debug/trace.h" |
|
||||||
#include "src/core/lib/gprpp/construct_destruct.h" |
|
||||||
#include "src/core/lib/promise/activity.h" |
|
||||||
#include "src/core/lib/promise/detail/promise_like.h" |
|
||||||
#include "src/core/lib/promise/detail/status.h" |
|
||||||
#include "src/core/lib/promise/poll.h" |
|
||||||
#include "src/core/lib/promise/trace.h" |
|
||||||
|
|
||||||
namespace grpc_core { |
|
||||||
|
|
||||||
namespace promise_detail { |
|
||||||
|
|
||||||
template <typename Promise> |
|
||||||
struct Necessary { |
|
||||||
PromiseLike<Promise> promise; |
|
||||||
static constexpr bool must_complete() { return true; } |
|
||||||
}; |
|
||||||
|
|
||||||
template <typename Promise> |
|
||||||
struct Helper { |
|
||||||
PromiseLike<Promise> promise; |
|
||||||
static constexpr bool must_complete() { return false; } |
|
||||||
}; |
|
||||||
|
|
||||||
// A set of promises that can be polled concurrently.
|
|
||||||
// Fuses them when completed (that is, destroys the promise and records it
|
|
||||||
// completed).
|
|
||||||
// Relies on an external bit field to handle the recording: this saves a bunch
|
|
||||||
// of space, but means the implementation of this type is weird: it's really
|
|
||||||
// super tied to TryConcurrently and no attempt should be made to use this
|
|
||||||
// independently.
|
|
||||||
template <typename... Ts> |
|
||||||
class FusedSet; |
|
||||||
|
|
||||||
template <typename T, typename... Ts> |
|
||||||
class FusedSet<T, Ts...> : public FusedSet<Ts...> { |
|
||||||
public: |
|
||||||
explicit FusedSet(T&& x, Ts&&... xs) |
|
||||||
: FusedSet<Ts...>(std::forward<T>(xs)...) { |
|
||||||
Construct(&wrapper_, std::forward<T>(x)); |
|
||||||
} |
|
||||||
explicit FusedSet(T&& x, FusedSet<Ts...>&& xs) |
|
||||||
: FusedSet<Ts...>(std::forward<FusedSet<Ts...>>(xs)) { |
|
||||||
Construct(&wrapper_, std::forward<T>(x)); |
|
||||||
} |
|
||||||
// Empty destructor: consumers must call Destroy() to ensure cleanup occurs
|
|
||||||
~FusedSet() {} |
|
||||||
|
|
||||||
FusedSet(const FusedSet&) = delete; |
|
||||||
FusedSet& operator=(const FusedSet&) = delete; |
|
||||||
|
|
||||||
// Assumes all 'done_bits' for other are 0 and will be set to 1
|
|
||||||
FusedSet(FusedSet&& other) noexcept : FusedSet<Ts...>(std::move(other)) { |
|
||||||
Construct(&wrapper_, std::move(other.wrapper_)); |
|
||||||
Destruct(&other.wrapper_); |
|
||||||
} |
|
||||||
|
|
||||||
static constexpr size_t Size() { return 1 + sizeof...(Ts); } |
|
||||||
|
|
||||||
static constexpr uint8_t NecessaryBits() { |
|
||||||
return (T::must_complete() ? 1 : 0) | |
|
||||||
(FusedSet<Ts...>::NecessaryBits() << 1); |
|
||||||
} |
|
||||||
|
|
||||||
template <int kDoneBit> |
|
||||||
void Destroy(uint8_t done_bits) { |
|
||||||
if ((done_bits & (1 << kDoneBit)) == 0) { |
|
||||||
Destruct(&wrapper_); |
|
||||||
} |
|
||||||
FusedSet<Ts...>::template Destroy<kDoneBit + 1>(done_bits); |
|
||||||
} |
|
||||||
|
|
||||||
template <typename Result, int kDoneBit> |
|
||||||
Poll<Result> Run(uint8_t& done_bits) { |
|
||||||
if ((done_bits & (1 << kDoneBit)) == 0) { |
|
||||||
auto p = wrapper_.promise(); |
|
||||||
if (auto* status = absl::get_if<kPollReadyIdx>(&p)) { |
|
||||||
done_bits |= (1 << kDoneBit); |
|
||||||
Destruct(&wrapper_); |
|
||||||
if (!IsStatusOk(*status)) { |
|
||||||
return StatusCast<Result>(std::move(*status)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return FusedSet<Ts...>::template Run<Result, kDoneBit + 1>(done_bits); |
|
||||||
} |
|
||||||
|
|
||||||
template <typename P> |
|
||||||
FusedSet<P, T, Ts...> With(P x) { |
|
||||||
return FusedSet<P, T, Ts...>(std::move(x), std::move(*this)); |
|
||||||
} |
|
||||||
|
|
||||||
private: |
|
||||||
union { |
|
||||||
T wrapper_; |
|
||||||
}; |
|
||||||
}; |
|
||||||
|
|
||||||
template <> |
|
||||||
class FusedSet<> { |
|
||||||
public: |
|
||||||
static constexpr size_t Size() { return 0; } |
|
||||||
static constexpr uint8_t NecessaryBits() { return 0; } |
|
||||||
|
|
||||||
template <typename Result, int kDoneBit> |
|
||||||
Poll<Result> Run(uint8_t) { |
|
||||||
return Pending{}; |
|
||||||
} |
|
||||||
template <int kDoneBit> |
|
||||||
void Destroy(uint8_t) {} |
|
||||||
|
|
||||||
template <typename P> |
|
||||||
FusedSet<P> With(P x) { |
|
||||||
return FusedSet<P>(std::move(x)); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
template <typename Main, typename PreMain, typename PostMain> |
|
||||||
class TryConcurrently { |
|
||||||
public: |
|
||||||
TryConcurrently(Main main, PreMain pre_main, PostMain post_main) |
|
||||||
: done_bits_(0), |
|
||||||
pre_main_(std::move(pre_main)), |
|
||||||
post_main_(std::move(post_main)) { |
|
||||||
Construct(&main_, std::move(main)); |
|
||||||
} |
|
||||||
|
|
||||||
TryConcurrently(const TryConcurrently&) = delete; |
|
||||||
TryConcurrently& operator=(const TryConcurrently&) = delete; |
|
||||||
TryConcurrently(TryConcurrently&& other) noexcept |
|
||||||
: done_bits_(0), |
|
||||||
pre_main_(std::move(other.pre_main_)), |
|
||||||
post_main_(std::move(other.post_main_)) { |
|
||||||
GPR_DEBUG_ASSERT(other.done_bits_ == 0); |
|
||||||
other.done_bits_ = HelperBits(); |
|
||||||
Construct(&main_, std::move(other.main_)); |
|
||||||
} |
|
||||||
TryConcurrently& operator=(TryConcurrently&& other) noexcept { |
|
||||||
GPR_DEBUG_ASSERT(other.done_bits_ == 0); |
|
||||||
done_bits_ = 0; |
|
||||||
other.done_bits_ = HelperBits(); |
|
||||||
pre_main_ = std::move(other.pre_main_); |
|
||||||
post_main_ = std::move(other.post_main_); |
|
||||||
Construct(&main_, std::move(other.main_)); |
|
||||||
return *this; |
|
||||||
} |
|
||||||
|
|
||||||
~TryConcurrently() { |
|
||||||
if (done_bits_ & 1) { |
|
||||||
Destruct(&result_); |
|
||||||
} else { |
|
||||||
Destruct(&main_); |
|
||||||
} |
|
||||||
pre_main_.template Destroy<1>(done_bits_); |
|
||||||
post_main_.template Destroy<1 + PreMain::Size()>(done_bits_); |
|
||||||
} |
|
||||||
|
|
||||||
using Result = |
|
||||||
typename PollTraits<decltype(std::declval<PromiseLike<Main>>()())>::Type; |
|
||||||
|
|
||||||
Poll<Result> operator()() { |
|
||||||
if (grpc_trace_promise_primitives.enabled()) { |
|
||||||
gpr_log(GPR_DEBUG, "%sBEGIN POLL: done_bits=%x necessary_bits=%x", |
|
||||||
DebugTag().c_str(), done_bits_, NecessaryBits()); |
|
||||||
} |
|
||||||
auto r = pre_main_.template Run<Result, 1>(done_bits_); |
|
||||||
if (auto* status = absl::get_if<Result>(&r)) { |
|
||||||
GPR_DEBUG_ASSERT(!IsStatusOk(*status)); |
|
||||||
if (grpc_trace_promise_primitives.enabled()) { |
|
||||||
gpr_log(GPR_DEBUG, |
|
||||||
"%sFAIL POLL PRE-MAIN: done_bits=%x necessary_bits=%x", |
|
||||||
DebugTag().c_str(), done_bits_, NecessaryBits()); |
|
||||||
} |
|
||||||
return std::move(*status); |
|
||||||
} |
|
||||||
if ((done_bits_ & 1) == 0) { |
|
||||||
auto p = main_(); |
|
||||||
if (auto* status = absl::get_if<kPollReadyIdx>(&p)) { |
|
||||||
done_bits_ |= 1; |
|
||||||
Destruct(&main_); |
|
||||||
Construct(&result_, std::move(*status)); |
|
||||||
} |
|
||||||
} |
|
||||||
r = post_main_.template Run<Result, 1 + PreMain::Size()>(done_bits_); |
|
||||||
if (auto* status = absl::get_if<Result>(&r)) { |
|
||||||
GPR_DEBUG_ASSERT(!IsStatusOk(*status)); |
|
||||||
if (grpc_trace_promise_primitives.enabled()) { |
|
||||||
gpr_log(GPR_DEBUG, |
|
||||||
"%sFAIL POLL POST-MAIN: done_bits=%x necessary_bits=%x", |
|
||||||
DebugTag().c_str(), done_bits_, NecessaryBits()); |
|
||||||
} |
|
||||||
return std::move(*status); |
|
||||||
} |
|
||||||
if (grpc_trace_promise_primitives.enabled()) { |
|
||||||
gpr_log(GPR_DEBUG, "%sEND POLL: done_bits=%x necessary_bits=%x", |
|
||||||
DebugTag().c_str(), done_bits_, NecessaryBits()); |
|
||||||
} |
|
||||||
if ((done_bits_ & NecessaryBits()) == NecessaryBits()) { |
|
||||||
return std::move(result_); |
|
||||||
} |
|
||||||
return Pending{}; |
|
||||||
} |
|
||||||
|
|
||||||
template <typename P> |
|
||||||
auto NecessaryPush(P p); |
|
||||||
template <typename P> |
|
||||||
auto NecessaryPull(P p); |
|
||||||
template <typename P> |
|
||||||
auto Push(P p); |
|
||||||
template <typename P> |
|
||||||
auto Pull(P p); |
|
||||||
|
|
||||||
private: |
|
||||||
// Bitmask for done_bits_ specifying which promises must be completed prior to
|
|
||||||
// returning ok.
|
|
||||||
constexpr uint8_t NecessaryBits() { |
|
||||||
return 1 | (PreMain::NecessaryBits() << 1) | |
|
||||||
(PostMain::NecessaryBits() << (1 + PreMain::Size())); |
|
||||||
} |
|
||||||
// Bitmask for done_bits_ specifying what all of the promises being complete
|
|
||||||
// would look like.
|
|
||||||
constexpr uint8_t AllBits() { |
|
||||||
return (1 << (1 + PreMain::Size() + PostMain::Size())) - 1; |
|
||||||
} |
|
||||||
// Bitmask of done_bits_ specifying which bits correspond to helper promises -
|
|
||||||
// that is all promises that are not the main one.
|
|
||||||
constexpr uint8_t HelperBits() { return AllBits() ^ 1; } |
|
||||||
|
|
||||||
std::string DebugTag() { |
|
||||||
return absl::StrCat(Activity::current()->DebugTag(), " TRY_CONCURRENTLY[0x", |
|
||||||
reinterpret_cast<uintptr_t>(this), "]: "); |
|
||||||
} |
|
||||||
|
|
||||||
// done_bits signifies which operations have completed.
|
|
||||||
// Bit 0 is set if main_ has completed.
|
|
||||||
// The next higher bits correspond one per pre-main promise.
|
|
||||||
// The next higher bits correspond one per post-main promise.
|
|
||||||
// So, going from most significant bit to least significant:
|
|
||||||
// +--------------+-------------+--------+
|
|
||||||
// |post_main bits|pre_main bits|main bit|
|
|
||||||
// +--------------+-------------+--------+
|
|
||||||
uint8_t done_bits_; |
|
||||||
PreMain pre_main_; |
|
||||||
union { |
|
||||||
PromiseLike<Main> main_; |
|
||||||
Result result_; |
|
||||||
}; |
|
||||||
PostMain post_main_; |
|
||||||
}; |
|
||||||
|
|
||||||
template <typename Main, typename PreMain, typename PostMain> |
|
||||||
auto MakeTryConcurrently(Main&& main, PreMain&& pre_main, |
|
||||||
PostMain&& post_main) { |
|
||||||
return TryConcurrently<Main, PreMain, PostMain>( |
|
||||||
std::forward<Main>(main), std::forward<PreMain>(pre_main), |
|
||||||
std::forward<PostMain>(post_main)); |
|
||||||
} |
|
||||||
|
|
||||||
template <typename Main, typename PreMain, typename PostMain> |
|
||||||
template <typename P> |
|
||||||
auto TryConcurrently<Main, PreMain, PostMain>::NecessaryPush(P p) { |
|
||||||
GPR_DEBUG_ASSERT(done_bits_ == 0); |
|
||||||
done_bits_ = HelperBits(); |
|
||||||
return MakeTryConcurrently(std::move(main_), |
|
||||||
pre_main_.With(Necessary<P>{std::move(p)}), |
|
||||||
std::move(post_main_)); |
|
||||||
} |
|
||||||
|
|
||||||
template <typename Main, typename PreMain, typename PostMain> |
|
||||||
template <typename P> |
|
||||||
auto TryConcurrently<Main, PreMain, PostMain>::NecessaryPull(P p) { |
|
||||||
GPR_DEBUG_ASSERT(done_bits_ == 0); |
|
||||||
done_bits_ = HelperBits(); |
|
||||||
return MakeTryConcurrently(std::move(main_), std::move(pre_main_), |
|
||||||
post_main_.With(Necessary<P>{std::move(p)})); |
|
||||||
} |
|
||||||
|
|
||||||
template <typename Main, typename PreMain, typename PostMain> |
|
||||||
template <typename P> |
|
||||||
auto TryConcurrently<Main, PreMain, PostMain>::Push(P p) { |
|
||||||
GPR_DEBUG_ASSERT(done_bits_ == 0); |
|
||||||
done_bits_ = HelperBits(); |
|
||||||
return MakeTryConcurrently(std::move(main_), |
|
||||||
pre_main_.With(Helper<P>{std::move(p)}), |
|
||||||
std::move(post_main_)); |
|
||||||
} |
|
||||||
|
|
||||||
template <typename Main, typename PreMain, typename PostMain> |
|
||||||
template <typename P> |
|
||||||
auto TryConcurrently<Main, PreMain, PostMain>::Pull(P p) { |
|
||||||
GPR_DEBUG_ASSERT(done_bits_ == 0); |
|
||||||
done_bits_ = HelperBits(); |
|
||||||
return MakeTryConcurrently(std::move(main_), std::move(pre_main_), |
|
||||||
post_main_.With(Helper<P>{std::move(p)})); |
|
||||||
} |
|
||||||
|
|
||||||
} // namespace promise_detail
|
|
||||||
|
|
||||||
// TryConcurrently runs a set of promises concurrently.
|
|
||||||
// There is a structure to the promises:
|
|
||||||
// - A 'main' promise dominates the others - it must complete before the
|
|
||||||
// overall promise successfully completes. Its result is chosen in the event
|
|
||||||
// of successful completion.
|
|
||||||
// - A set of (optional) push and pull promises to aid main. Push promises are
|
|
||||||
// polled before main, pull promises are polled after. In this way we can
|
|
||||||
// avoid overall wakeup churn - sending a message will tend to push things
|
|
||||||
// down the promise tree as its polled, so that send should be in a push
|
|
||||||
// promise - then as the main promise is polled and it calls into things
|
|
||||||
// lower in the stack they'll already see things there (this reasoning holds
|
|
||||||
// for receiving things and the pull promises too!).
|
|
||||||
// - Each push and pull promise is either necessary or optional.
|
|
||||||
// Necessary promises must complete successfully before the overall promise
|
|
||||||
// completes. Optional promises will just be cancelled once the main promise
|
|
||||||
// completes and any necessary helpers.
|
|
||||||
// - If any of the promises fail, the overall promise fails immediately.
|
|
||||||
// API:
|
|
||||||
// This function, TryConcurrently, is used to create a TryConcurrently promise.
|
|
||||||
// It takes a single argument, being the main promise. That promise also has
|
|
||||||
// a set of methods for attaching push and pull promises. The act of attachment
|
|
||||||
// returns a new TryConcurrently promise with previous contained promises moved
|
|
||||||
// out.
|
|
||||||
// The methods exposed:
|
|
||||||
// - Push, NecessaryPush: attach a push promise (with the first variant being
|
|
||||||
// optional, the second necessary).
|
|
||||||
// - Pull, NecessaryPull: attach a pull promise, with variants as above.
|
|
||||||
// Example:
|
|
||||||
// TryConcurrently(call_next_filter(std::move(call_args)))
|
|
||||||
// .Push(send_messages_promise)
|
|
||||||
// .Pull(recv_messages_promise)
|
|
||||||
template <typename Main> |
|
||||||
auto TryConcurrently(Main main) { |
|
||||||
return promise_detail::MakeTryConcurrently(std::move(main), |
|
||||||
promise_detail::FusedSet<>(), |
|
||||||
promise_detail::FusedSet<>()); |
|
||||||
} |
|
||||||
|
|
||||||
} // namespace grpc_core
|
|
||||||
|
|
||||||
#endif // GRPC_SRC_CORE_LIB_PROMISE_TRY_CONCURRENTLY_H
|
|
@ -1,194 +0,0 @@ |
|||||||
// 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/promise/try_concurrently.h" |
|
||||||
|
|
||||||
#include <algorithm> |
|
||||||
#include <iosfwd> |
|
||||||
#include <memory> |
|
||||||
#include <string> |
|
||||||
#include <vector> |
|
||||||
|
|
||||||
#include "absl/status/status.h" |
|
||||||
#include "absl/strings/string_view.h" |
|
||||||
#include "gtest/gtest.h" |
|
||||||
|
|
||||||
namespace grpc_core { |
|
||||||
|
|
||||||
class PromiseFactory { |
|
||||||
public: |
|
||||||
// Create a promise that resolves to Ok but has a memory allocation (to verify
|
|
||||||
// destruction)
|
|
||||||
// tag should have static lifetime (i.e. pass a string literal here).
|
|
||||||
auto OkPromise(absl::string_view tag) { |
|
||||||
return [this, tag, |
|
||||||
p = std::make_unique<absl::Status>(absl::OkStatus())]() mutable { |
|
||||||
order_.push_back(tag); |
|
||||||
return std::move(*p); |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
// Create a promise that never resolves and carries a memory allocation
|
|
||||||
// tag should have static lifetime (i.e. pass a string literal here).
|
|
||||||
auto NeverPromise(absl::string_view tag) { |
|
||||||
return |
|
||||||
[this, tag, p = std::make_unique<Pending>()]() -> Poll<absl::Status> { |
|
||||||
order_.push_back(tag); |
|
||||||
return *p; |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
// Create a promise that fails and carries a memory allocation
|
|
||||||
// tag should have static lifetime (i.e. pass a string literal here).
|
|
||||||
auto FailPromise(absl::string_view tag) { |
|
||||||
return [this, p = std::make_unique<absl::Status>(absl::UnknownError(tag)), |
|
||||||
tag]() mutable { |
|
||||||
order_.push_back(tag); |
|
||||||
return std::move(*p); |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
// Finish one round and return a vector of strings representing which promises
|
|
||||||
// were polled and in which order.
|
|
||||||
std::vector<absl::string_view> Finish() { return std::exchange(order_, {}); } |
|
||||||
|
|
||||||
private: |
|
||||||
std::vector<absl::string_view> order_; |
|
||||||
}; |
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& out, const Poll<absl::Status>& p) { |
|
||||||
return out << PollToString( |
|
||||||
p, [](const absl::Status& s) { return s.ToString(); }); |
|
||||||
} |
|
||||||
|
|
||||||
TEST(TryConcurrentlyTest, Immediate) { |
|
||||||
PromiseFactory pf; |
|
||||||
auto a = TryConcurrently(pf.OkPromise("1")); |
|
||||||
EXPECT_EQ(a(), Poll<absl::Status>(absl::OkStatus())); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>({"1"})); |
|
||||||
auto b = TryConcurrently(pf.OkPromise("1")).NecessaryPush(pf.OkPromise("2")); |
|
||||||
EXPECT_EQ(b(), Poll<absl::Status>(absl::OkStatus())); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>({"2", "1"})); |
|
||||||
auto c = TryConcurrently(pf.OkPromise("1")).NecessaryPull(pf.OkPromise("2")); |
|
||||||
EXPECT_EQ(c(), Poll<absl::Status>(absl::OkStatus())); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>({"1", "2"})); |
|
||||||
auto d = TryConcurrently(pf.OkPromise("1")) |
|
||||||
.NecessaryPull(pf.OkPromise("2")) |
|
||||||
.NecessaryPush(pf.OkPromise("3")); |
|
||||||
EXPECT_EQ(d(), Poll<absl::Status>(absl::OkStatus())); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>({"3", "1", "2"})); |
|
||||||
auto e = TryConcurrently(pf.OkPromise("1")).Push(pf.NeverPromise("2")); |
|
||||||
EXPECT_EQ(e(), Poll<absl::Status>(absl::OkStatus())); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>({"2", "1"})); |
|
||||||
auto f = TryConcurrently(pf.OkPromise("1")).Pull(pf.NeverPromise("2")); |
|
||||||
EXPECT_EQ(f(), Poll<absl::Status>(absl::OkStatus())); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>({"1", "2"})); |
|
||||||
} |
|
||||||
|
|
||||||
TEST(TryConcurrentlyTest, Paused) { |
|
||||||
PromiseFactory pf; |
|
||||||
auto a = TryConcurrently(pf.NeverPromise("1")); |
|
||||||
EXPECT_EQ(a(), Poll<absl::Status>(Pending{})); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>({"1"})); |
|
||||||
auto b = |
|
||||||
TryConcurrently(pf.OkPromise("1")).NecessaryPush(pf.NeverPromise("2")); |
|
||||||
EXPECT_EQ(b(), Poll<absl::Status>(Pending{})); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>({"2", "1"})); |
|
||||||
auto c = |
|
||||||
TryConcurrently(pf.OkPromise("1")).NecessaryPull(pf.NeverPromise("2")); |
|
||||||
EXPECT_EQ(c(), Poll<absl::Status>(Pending{})); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>({"1", "2"})); |
|
||||||
} |
|
||||||
|
|
||||||
TEST(TryConcurrentlyTest, OneFailed) { |
|
||||||
PromiseFactory pf; |
|
||||||
auto a = TryConcurrently(pf.FailPromise("bah")); |
|
||||||
EXPECT_EQ(a(), Poll<absl::Status>(absl::UnknownError("bah"))); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>({"bah"})); |
|
||||||
auto b = TryConcurrently(pf.NeverPromise("1")) |
|
||||||
.NecessaryPush(pf.FailPromise("humbug")); |
|
||||||
EXPECT_EQ(b(), Poll<absl::Status>(absl::UnknownError("humbug"))); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>({"humbug"})); |
|
||||||
auto c = TryConcurrently(pf.NeverPromise("1")) |
|
||||||
.NecessaryPull(pf.FailPromise("wha")); |
|
||||||
EXPECT_EQ(c(), Poll<absl::Status>(absl::UnknownError("wha"))); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>({"1", "wha"})); |
|
||||||
} |
|
||||||
|
|
||||||
TEST(TryConcurrently, MuchPush) { |
|
||||||
PromiseFactory pf; |
|
||||||
auto p = TryConcurrently(pf.OkPromise("1")) |
|
||||||
.NecessaryPush(pf.OkPromise("2")) |
|
||||||
.NecessaryPush(pf.OkPromise("3")) |
|
||||||
.NecessaryPush(pf.OkPromise("4")) |
|
||||||
.NecessaryPush(pf.OkPromise("5")) |
|
||||||
.NecessaryPush(pf.OkPromise("6")) |
|
||||||
.NecessaryPush(pf.OkPromise("7")) |
|
||||||
.NecessaryPush(pf.OkPromise("8")); |
|
||||||
EXPECT_EQ(p(), Poll<absl::Status>(absl::OkStatus())); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>( |
|
||||||
{"8", "7", "6", "5", "4", "3", "2", "1"})); |
|
||||||
} |
|
||||||
|
|
||||||
TEST(TryConcurrently, SoPull) { |
|
||||||
PromiseFactory pf; |
|
||||||
auto p = TryConcurrently(pf.OkPromise("1")) |
|
||||||
.NecessaryPull(pf.OkPromise("2")) |
|
||||||
.NecessaryPull(pf.OkPromise("3")) |
|
||||||
.NecessaryPull(pf.OkPromise("4")) |
|
||||||
.NecessaryPull(pf.OkPromise("5")) |
|
||||||
.NecessaryPull(pf.OkPromise("6")) |
|
||||||
.NecessaryPull(pf.OkPromise("7")) |
|
||||||
.NecessaryPull(pf.OkPromise("8")); |
|
||||||
EXPECT_EQ(p(), Poll<absl::Status>(absl::OkStatus())); |
|
||||||
EXPECT_EQ(pf.Finish(), std::vector<absl::string_view>( |
|
||||||
{"1", "8", "7", "6", "5", "4", "3", "2"})); |
|
||||||
} |
|
||||||
|
|
||||||
// A pointer to an int designed to cause a double free if it's double destructed
|
|
||||||
// (to flush out bugs)
|
|
||||||
class ProblematicPointer { |
|
||||||
public: |
|
||||||
ProblematicPointer() : p_(new int(0)) {} |
|
||||||
~ProblematicPointer() { delete p_; } |
|
||||||
ProblematicPointer(const ProblematicPointer&) = delete; |
|
||||||
ProblematicPointer& operator=(const ProblematicPointer&) = delete; |
|
||||||
// NOLINTNEXTLINE: we want to allocate during move
|
|
||||||
ProblematicPointer(ProblematicPointer&& other) : p_(new int(*other.p_ + 1)) {} |
|
||||||
ProblematicPointer& operator=(ProblematicPointer&& other) = delete; |
|
||||||
|
|
||||||
private: |
|
||||||
int* p_; |
|
||||||
}; |
|
||||||
|
|
||||||
TEST(TryConcurrentlyTest, MoveItMoveIt) { |
|
||||||
auto a = |
|
||||||
TryConcurrently([x = ProblematicPointer()]() { return absl::OkStatus(); }) |
|
||||||
.NecessaryPull( |
|
||||||
[x = ProblematicPointer()]() { return absl::OkStatus(); }) |
|
||||||
.NecessaryPush( |
|
||||||
[x = ProblematicPointer()]() { return absl::OkStatus(); }) |
|
||||||
.Push([x = ProblematicPointer()]() { return absl::OkStatus(); }) |
|
||||||
.Pull([x = ProblematicPointer()]() { return absl::OkStatus(); }); |
|
||||||
auto b = std::move(a); |
|
||||||
auto c = std::move(b); |
|
||||||
c(); |
|
||||||
} |
|
||||||
|
|
||||||
} // namespace grpc_core
|
|
||||||
|
|
||||||
int main(int argc, char** argv) { |
|
||||||
::testing::InitGoogleTest(&argc, argv); |
|
||||||
return RUN_ALL_TESTS(); |
|
||||||
} |
|
Loading…
Reference in new issue