blob: da8234063761c07a04089f7292b8b1803fafcad5 [file] [log] [blame]
// 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 <stdlib.h>
#include <type_traits>
#include <utility>
#include "absl/meta/type_traits.h"
#include "src/core/lib/promise/context.h"
#include "src/core/lib/promise/poll.h"
#include "src/core/lib/resource_quota/arena.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 poll_cast<T>(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(Callable&& callable) {
return GetContext<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(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(Callable&& callable) {
return ChooseImplForCallable<T, Callable>::Make(
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,
typename Ignored =
absl::enable_if_t<!std::is_same<Callable, ArenaPromise>::value>>
// NOLINTNEXTLINE(google-explicit-constructor)
ArenaPromise(Callable&& callable)
: impl_(arena_promise_detail::MakeImplForCallable<T>(
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_->Destroy();
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(); }
bool has_value() const {
return impl_ != arena_promise_detail::NullImpl<T>::Get();
}
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 */