Reland arena based promises (#28174)
* Revert "Revert "Arena allocated promise (#28023)" (#28171)"
This reverts commit 9b07a81b1a
.
* fix
* Automated change: Fix sanity tests
Co-authored-by: ctiller <ctiller@users.noreply.github.com>
pull/28193/head
parent
cc968b2158
commit
888a10d8a6
7 changed files with 502 additions and 0 deletions
@ -0,0 +1,184 @@ |
||||
// 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_ARENA_PROMISE_H |
||||
#define GRPC_CORE_LIB_PROMISE_ARENA_PROMISE_H |
||||
|
||||
#include <grpc/support/port_platform.h> |
||||
|
||||
#include <grpc/support/log.h> |
||||
|
||||
#include "src/core/lib/gprpp/arena.h" |
||||
#include "src/core/lib/promise/poll.h" |
||||
|
||||
namespace grpc_core { |
||||
|
||||
namespace arena_promise_detail { |
||||
|
||||
// Type erased promise stored in the arena.
|
||||
template <typename T> |
||||
class ImplInterface { |
||||
public: |
||||
// Poll the promise, once.
|
||||
virtual Poll<T> PollOnce() = 0; |
||||
// Destroy the underlying callable object if there is one.
|
||||
// Since we don't delete (the arena owns the memory) but we may need to call a
|
||||
// destructor, we expose this for when the ArenaPromise object is destroyed.
|
||||
virtual void Destroy() = 0; |
||||
|
||||
protected: |
||||
~ImplInterface() = default; |
||||
}; |
||||
|
||||
// Implementation of ImplInterface for an empty object.
|
||||
// Used when an empty ArenaPromise is created, or when the ArenaPromise is moved
|
||||
// from. Since in either case these objects should not be polled, we simply
|
||||
// crash if it is.
|
||||
template <typename T> |
||||
class NullImpl final : public ImplInterface<T> { |
||||
public: |
||||
Poll<T> PollOnce() override { |
||||
abort(); |
||||
GPR_UNREACHABLE_CODE(return Pending{}); |
||||
} |
||||
void Destroy() override {} |
||||
|
||||
static ImplInterface<T>* Get() { |
||||
static NullImpl<T> instance; |
||||
return &instance; |
||||
} |
||||
|
||||
private: |
||||
~NullImpl() = default; |
||||
}; |
||||
|
||||
// Implementation of ImplInterface for a callable object.
|
||||
template <typename T, typename Callable> |
||||
class CallableImpl final : public ImplInterface<T> { |
||||
public: |
||||
explicit CallableImpl(Callable&& callable) : callable_(std::move(callable)) {} |
||||
// Forward polls to the callable object.
|
||||
Poll<T> PollOnce() override { return callable_(); } |
||||
// Destroy destructs the callable object.
|
||||
void Destroy() override { this->~CallableImpl(); } |
||||
|
||||
private: |
||||
// Should only be called by Destroy().
|
||||
~CallableImpl() = default; |
||||
|
||||
Callable callable_; |
||||
}; |
||||
|
||||
// If a callable object is empty we can substitute any instance of that callable
|
||||
// for the one we call (for how could we tell the difference)?
|
||||
// Since this corresponds to a lambda with no fields, and we expect these to be
|
||||
// reasonably common, we can elide the arena allocation entirely and simply poll
|
||||
// a global shared instance.
|
||||
// (this comes up often when the promise only accesses context data from the
|
||||
// containing activity).
|
||||
template <typename T, typename Callable> |
||||
class SharedImpl final : public ImplInterface<T>, private Callable { |
||||
public: |
||||
// Call the callable, or at least an exact duplicate of it - if you have no
|
||||
// members, all your instances look the same.
|
||||
Poll<T> PollOnce() override { return Callable::operator()(); } |
||||
// Nothing to destroy.
|
||||
void Destroy() override {} |
||||
// Return a pointer to the shared instance - these are singletons, and are
|
||||
// needed just to get the vtable in place.
|
||||
static SharedImpl* Get(Callable&& callable) { |
||||
static_assert(sizeof(SharedImpl) == sizeof(void*), |
||||
"SharedImpl should be pointer sized"); |
||||
static SharedImpl impl(std::forward<Callable>(callable)); |
||||
return &impl; |
||||
} |
||||
|
||||
private: |
||||
explicit SharedImpl(Callable&& callable) |
||||
: Callable(std::forward<Callable>(callable)) {} |
||||
~SharedImpl() = default; |
||||
}; |
||||
|
||||
// Redirector type: given a callable type, expose a Make() function that creates
|
||||
// the appropriate underlying implementation.
|
||||
template <typename T, typename Callable, typename Ignored = void> |
||||
struct ChooseImplForCallable; |
||||
|
||||
template <typename T, typename Callable> |
||||
struct ChooseImplForCallable< |
||||
T, Callable, absl::enable_if_t<!std::is_empty<Callable>::value>> { |
||||
static ImplInterface<T>* Make(Arena* arena, Callable&& callable) { |
||||
return arena->template New<CallableImpl<T, Callable>>( |
||||
std::forward<Callable>(callable)); |
||||
} |
||||
}; |
||||
|
||||
template <typename T, typename Callable> |
||||
struct ChooseImplForCallable< |
||||
T, Callable, absl::enable_if_t<std::is_empty<Callable>::value>> { |
||||
static ImplInterface<T>* Make(Arena*, Callable&& callable) { |
||||
return SharedImpl<T, Callable>::Get(std::forward<Callable>(callable)); |
||||
} |
||||
}; |
||||
|
||||
// Wrap ChooseImplForCallable with a friend approachable syntax.
|
||||
template <typename T, typename Callable> |
||||
ImplInterface<T>* MakeImplForCallable(Arena* arena, Callable&& callable) { |
||||
return ChooseImplForCallable<T, Callable>::Make( |
||||
arena, std::forward<Callable>(callable)); |
||||
} |
||||
|
||||
} // namespace arena_promise_detail
|
||||
|
||||
// A promise for which the state memory is allocated from an arena.
|
||||
template <typename T> |
||||
class ArenaPromise { |
||||
public: |
||||
// Construct an empty, uncallable, invalid ArenaPromise.
|
||||
ArenaPromise() = default; |
||||
|
||||
// Construct an ArenaPromise that will call the given callable when polled.
|
||||
template <typename Callable> |
||||
ArenaPromise(Arena* arena, Callable&& callable) |
||||
: impl_(arena_promise_detail::MakeImplForCallable<T>( |
||||
arena, std::forward<Callable>(callable))) {} |
||||
|
||||
// ArenaPromise is not copyable.
|
||||
ArenaPromise(const ArenaPromise&) = delete; |
||||
ArenaPromise& operator=(const ArenaPromise&) = delete; |
||||
// ArenaPromise is movable.
|
||||
ArenaPromise(ArenaPromise&& other) noexcept : impl_(other.impl_) { |
||||
other.impl_ = arena_promise_detail::NullImpl<T>::Get(); |
||||
} |
||||
ArenaPromise& operator=(ArenaPromise&& other) noexcept { |
||||
impl_ = other.impl_; |
||||
other.impl_ = arena_promise_detail::NullImpl<T>::Get(); |
||||
return *this; |
||||
} |
||||
|
||||
// Destruction => call Destroy on the underlying impl object.
|
||||
~ArenaPromise() { impl_->Destroy(); } |
||||
|
||||
// Expose the promise interface: a call operator that returns Poll<T>.
|
||||
Poll<T> operator()() { return impl_->PollOnce(); } |
||||
|
||||
private: |
||||
// Underlying impl object.
|
||||
arena_promise_detail::ImplInterface<T>* impl_ = |
||||
arena_promise_detail::NullImpl<T>::Get(); |
||||
}; |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
#endif /* GRPC_CORE_LIB_PROMISE_ARENA_PROMISE_H */ |
@ -0,0 +1,45 @@ |
||||
// 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/arena_promise.h" |
||||
|
||||
#include <memory> |
||||
|
||||
#include <gtest/gtest.h> |
||||
|
||||
namespace grpc_core { |
||||
|
||||
TEST(ArenaPromiseTest, AllocatedWorks) { |
||||
auto arena = MakeScopedArena(1024); |
||||
int x = 42; |
||||
ArenaPromise<int> p(arena.get(), [x] { return Poll<int>(x); }); |
||||
EXPECT_EQ(p(), Poll<int>(42)); |
||||
p = ArenaPromise<int>(arena.get(), [] { return Poll<int>(43); }); |
||||
EXPECT_EQ(p(), Poll<int>(43)); |
||||
} |
||||
|
||||
TEST(ArenaPromiseTest, DestructionWorks) { |
||||
auto arena = MakeScopedArena(1024); |
||||
auto x = std::make_shared<int>(42); |
||||
auto p = ArenaPromise<int>(arena.get(), [x] { return Poll<int>(*x); }); |
||||
ArenaPromise<int> q(std::move(p)); |
||||
EXPECT_EQ(q(), Poll<int>(42)); |
||||
} |
||||
|
||||
} // namespace grpc_core
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue