[promise] Introduce map_pipe, cleanup factories (#31430)
* [promise] Introduce map_pipe, cleanup factories * Automated change: Fix sanity tests Co-authored-by: ctiller <ctiller@users.noreply.github.com>pull/31462/head
parent
ab3d62ae8f
commit
58c628a7ad
29 changed files with 664 additions and 56 deletions
@ -0,0 +1,87 @@ |
|||||||
|
// 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_CORE_LIB_PROMISE_MAP_PIPE_H |
||||||
|
#define GRPC_CORE_LIB_PROMISE_MAP_PIPE_H |
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include "absl/status/status.h" |
||||||
|
|
||||||
|
#include "src/core/lib/promise/detail/promise_factory.h" |
||||||
|
#include "src/core/lib/promise/for_each.h" |
||||||
|
#include "src/core/lib/promise/map.h" |
||||||
|
#include "src/core/lib/promise/pipe.h" |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
|
||||||
|
// Apply a (possibly async) mapping function to src, and output into dst.
|
||||||
|
//
|
||||||
|
// In psuedo-code:
|
||||||
|
// for each element in wait_for src.Next:
|
||||||
|
// x = wait_for filter_factory(element)
|
||||||
|
// wait_for dst.Push(x)
|
||||||
|
template <typename T, typename Filter> |
||||||
|
auto MapPipe(PipeReceiver<T> src, PipeSender<T> dst, Filter filter_factory) { |
||||||
|
return ForEach( |
||||||
|
std::move(src), |
||||||
|
[filter_factory = promise_detail::RepeatedPromiseFactory<T, Filter>( |
||||||
|
std::move(filter_factory)), |
||||||
|
dst = std::move(dst)](T t) mutable { |
||||||
|
return TrySeq(filter_factory.Make(std::move(t)), [&dst](T t) { |
||||||
|
return Map(dst.Push(std::move(t)), [](bool successful_push) { |
||||||
|
if (successful_push) { |
||||||
|
return absl::OkStatus(); |
||||||
|
} |
||||||
|
return absl::CancelledError(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// Helper to intecept a pipe and apply a mapping function.
|
||||||
|
// Each of the `Intercept` constructors will take a PipeSender or PipeReceiver,
|
||||||
|
// construct a new pipe, and then replace the passed in pipe with its new end.
|
||||||
|
// In this way it can interject logic per-element.
|
||||||
|
// Next, the TakeAndRun function will return a promise that can be run to apply
|
||||||
|
// a mapping promise to each element of the pipe.
|
||||||
|
template <typename T> |
||||||
|
class PipeMapper { |
||||||
|
public: |
||||||
|
static PipeMapper Intercept(PipeSender<T>& intercept_sender) { |
||||||
|
PipeMapper<T> r; |
||||||
|
r.interceptor_.sender.Swap(&intercept_sender); |
||||||
|
return r; |
||||||
|
} |
||||||
|
|
||||||
|
static PipeMapper Intercept(PipeReceiver<T>& intercept_receiver) { |
||||||
|
PipeMapper<T> r; |
||||||
|
r.interceptor_.receiver.Swap(&intercept_receiver); |
||||||
|
return r; |
||||||
|
} |
||||||
|
|
||||||
|
template <typename Filter> |
||||||
|
auto TakeAndRun(Filter filter) { |
||||||
|
return MapPipe(std::move(interceptor_.receiver), |
||||||
|
std::move(interceptor_.sender), std::move(filter)); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
PipeMapper() = default; |
||||||
|
Pipe<T> interceptor_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace grpc_core
|
||||||
|
|
||||||
|
#endif // GRPC_CORE_LIB_PROMISE_MAP_PIPE_H
|
@ -0,0 +1,19 @@ |
|||||||
|
// 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 <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include "src/core/lib/promise/pipe.h" |
||||||
|
|
||||||
|
grpc_core::DebugOnlyTraceFlag grpc_trace_promise_pipe(false, "promise_pipe"); |
@ -0,0 +1,153 @@ |
|||||||
|
// 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/map_pipe.h" |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
#include <memory> |
||||||
|
#include <utility> |
||||||
|
|
||||||
|
#include "gmock/gmock.h" |
||||||
|
#include "gtest/gtest.h" |
||||||
|
|
||||||
|
#include <grpc/event_engine/memory_allocator.h> |
||||||
|
|
||||||
|
#include "src/core/lib/gprpp/ref_counted_ptr.h" |
||||||
|
#include "src/core/lib/promise/activity.h" |
||||||
|
#include "src/core/lib/promise/detail/basic_seq.h" |
||||||
|
#include "src/core/lib/promise/for_each.h" |
||||||
|
#include "src/core/lib/promise/join.h" |
||||||
|
#include "src/core/lib/promise/map.h" |
||||||
|
#include "src/core/lib/promise/pipe.h" |
||||||
|
#include "src/core/lib/promise/poll.h" |
||||||
|
#include "src/core/lib/promise/seq.h" |
||||||
|
#include "src/core/lib/promise/try_seq.h" |
||||||
|
#include "src/core/lib/resource_quota/arena.h" |
||||||
|
#include "src/core/lib/resource_quota/memory_quota.h" |
||||||
|
#include "src/core/lib/resource_quota/resource_quota.h" |
||||||
|
#include "test/core/promise/test_wakeup_schedulers.h" |
||||||
|
|
||||||
|
using testing::Mock; |
||||||
|
using testing::MockFunction; |
||||||
|
using testing::StrictMock; |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
|
||||||
|
static auto* g_memory_allocator = new MemoryAllocator( |
||||||
|
ResourceQuota::Default()->memory_quota()->CreateMemoryAllocator("test")); |
||||||
|
|
||||||
|
template <typename T> |
||||||
|
class Delayed { |
||||||
|
public: |
||||||
|
explicit Delayed(T x) : x_(x) {} |
||||||
|
|
||||||
|
Poll<T> operator()() { |
||||||
|
Activity::current()->ForceImmediateRepoll(); |
||||||
|
++polls_; |
||||||
|
if (polls_ == 10) return std::move(x_); |
||||||
|
return Pending(); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
int polls_ = 0; |
||||||
|
T x_; |
||||||
|
}; |
||||||
|
|
||||||
|
TEST(MapPipeTest, SendThriceWithPipeInterceptingReceive) { |
||||||
|
int num_received = 0; |
||||||
|
StrictMock<MockFunction<void(absl::Status)>> on_done; |
||||||
|
EXPECT_CALL(on_done, Call(absl::OkStatus())); |
||||||
|
MakeActivity( |
||||||
|
[&num_received] { |
||||||
|
Pipe<int> pipe; |
||||||
|
auto filter = |
||||||
|
PipeMapper<int>::Intercept(pipe.receiver).TakeAndRun([](int x) { |
||||||
|
return Delayed<int>(x + 1); |
||||||
|
}); |
||||||
|
auto sender = std::make_shared<std::unique_ptr<PipeSender<int>>>( |
||||||
|
std::make_unique<PipeSender<int>>(std::move(pipe.sender))); |
||||||
|
return Map( |
||||||
|
Join( |
||||||
|
std::move(filter), |
||||||
|
// Push 3 things into a pipe -- 0, 1, then 2 -- then close.
|
||||||
|
Seq((*sender)->Push(0), [sender] { return (*sender)->Push(1); }, |
||||||
|
[sender] { return (*sender)->Push(2); }, |
||||||
|
[sender] { |
||||||
|
sender->reset(); |
||||||
|
return absl::OkStatus(); |
||||||
|
}), |
||||||
|
// Use a ForEach loop to read them out and verify all values are
|
||||||
|
// seen (but with 1 added).
|
||||||
|
ForEach(std::move(pipe.receiver), |
||||||
|
[&num_received](int i) { |
||||||
|
num_received++; |
||||||
|
EXPECT_EQ(num_received, i); |
||||||
|
return absl::OkStatus(); |
||||||
|
})), |
||||||
|
JustElem<2>()); |
||||||
|
}, |
||||||
|
NoWakeupScheduler(), |
||||||
|
[&on_done](absl::Status status) { on_done.Call(std::move(status)); }, |
||||||
|
MakeScopedArena(1024, g_memory_allocator)); |
||||||
|
Mock::VerifyAndClearExpectations(&on_done); |
||||||
|
EXPECT_EQ(num_received, 3); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(MapPipeTest, SendThriceWithPipeInterceptingSend) { |
||||||
|
int num_received = 0; |
||||||
|
StrictMock<MockFunction<void(absl::Status)>> on_done; |
||||||
|
EXPECT_CALL(on_done, Call(absl::OkStatus())); |
||||||
|
MakeActivity( |
||||||
|
[&num_received] { |
||||||
|
Pipe<int> pipe; |
||||||
|
auto filter = |
||||||
|
PipeMapper<int>::Intercept(pipe.sender).TakeAndRun([](int x) { |
||||||
|
return Delayed<int>(x + 1); |
||||||
|
}); |
||||||
|
auto sender = std::make_shared<std::unique_ptr<PipeSender<int>>>( |
||||||
|
std::make_unique<PipeSender<int>>(std::move(pipe.sender))); |
||||||
|
return Map( |
||||||
|
Join( |
||||||
|
std::move(filter), |
||||||
|
// Push 3 things into a pipe -- 0, 1, then 2 -- then close.
|
||||||
|
Seq((*sender)->Push(0), [sender] { return (*sender)->Push(1); }, |
||||||
|
[sender] { return (*sender)->Push(2); }, |
||||||
|
[sender] { |
||||||
|
sender->reset(); |
||||||
|
return absl::OkStatus(); |
||||||
|
}), |
||||||
|
// Use a ForEach loop to read them out and verify all values are
|
||||||
|
// seen (but with 1 added).
|
||||||
|
ForEach(std::move(pipe.receiver), |
||||||
|
[&num_received](int i) { |
||||||
|
num_received++; |
||||||
|
EXPECT_EQ(num_received, i); |
||||||
|
return absl::OkStatus(); |
||||||
|
})), |
||||||
|
JustElem<2>()); |
||||||
|
}, |
||||||
|
NoWakeupScheduler(), |
||||||
|
[&on_done](absl::Status status) { on_done.Call(std::move(status)); }, |
||||||
|
MakeScopedArena(1024, g_memory_allocator)); |
||||||
|
Mock::VerifyAndClearExpectations(&on_done); |
||||||
|
EXPECT_EQ(num_received, 3); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace grpc_core
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
::testing::InitGoogleTest(&argc, argv); |
||||||
|
return RUN_ALL_TESTS(); |
||||||
|
} |
Loading…
Reference in new issue