blob: d0f30256a94587b3f93250bec49cc7a5ab14daf3 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_PROMISE_PROMISE_EXECUTOR_H_
#define BASE_TASK_PROMISE_PROMISE_EXECUTOR_H_
#include "base/base_export.h"
#include "base/logging.h"
#include "base/task/promise/promise_value.h"
namespace base {
namespace internal {
class AbstractPromise;
// Unresolved promises have an executor which invokes one of the callbacks
// associated with the promise. Once the callback has been invoked the
// Executor is destroyed.
//
// Ideally Executor would be a pure virtual class, but we want to store these
// inline to reduce the number of memory allocations (small object
// optimization). The problem is even though placement new returns the same
// address it was allocated at, you have to use the returned pointer. Casting
// the buffer to the derived class is undefined behavior. STL implementations
// usually store an extra pointer, but there we have opted for implementing
// our own VTable to save a little bit of memory.
class BASE_EXPORT PromiseExecutor {
private:
static constexpr size_t MaxSize = sizeof(void*) * 2;
struct VTable;
public:
// We could just construct Executor in place, but that means templates need
// to inline the AbstractPromise constructor which we'd like to avoid due to
// binary size concerns. Despite containing refcounted objects, Data is
// intended to be memcopied into the Executor and it deliberately does not
// have a destructor. The type erasure provided by Executor allows us to
// move the AbstractPromise construction out of line.
class Data {
public:
// Constructs |Derived| in place.
template <typename Derived, typename... Args>
explicit Data(in_place_type_t<Derived>, Args&&... args) {
static_assert(sizeof(Derived) <= MaxSize, "Derived is too big");
static_assert(
sizeof(PromiseExecutor) <= sizeof(PromiseValueInternal::InlineAlloc),
"Executor is too big");
vtable_ = &VTableHelper<Derived>::vtable_;
new (storage_.array) Derived(std::forward<Args>(args)...);
}
Data(Data&& other) noexcept
: vtable_(other.vtable_), storage_(other.storage_) {
#if DCHECK_IS_ON()
other.vtable_ = nullptr;
#endif
}
Data(const Data& other) = delete;
~Data() { DCHECK_EQ(vtable_, nullptr); }
private:
friend class PromiseExecutor;
const VTable* vtable_;
struct {
char array[MaxSize];
} storage_;
};
// Caution it's an error to use |data| after this.
explicit PromiseExecutor(Data&& data) : data_(std::move(data)) {}
PromiseExecutor(PromiseExecutor&& other) noexcept
: data_(std::move(other.data_)) {
other.data_.vtable_ = nullptr;
}
PromiseExecutor(const PromiseExecutor& other) = delete;
~PromiseExecutor();
PromiseExecutor& operator=(const PromiseExecutor& other) = delete;
// Controls whether or not a promise should wait for its prerequisites
// before becoming eligible for execution.
enum class PrerequisitePolicy : uint8_t {
// Wait for all prerequisites to resolve (or any to reject) before
// becoming eligible for execution. If any prerequisites are canceled it
// will be canceled too.
kAll,
// Wait for any prerequisite to resolve or reject before becoming eligible
// for execution. If all prerequisites are canceled it will be canceled
// too.
kAny,
// Never become eligible for execution. Cancellation is ignored.
kNever,
};
// Returns the associated PrerequisitePolicy.
PrerequisitePolicy GetPrerequisitePolicy() const {
return data_.vtable_->prerequsite_policy;
}
// NB if there is both a resolve and a reject executor we require them to
// be both canceled at the same time.
bool IsCancelled() const {
return data_.vtable_->is_cancelled(data_.storage_.array);
}
// Describes an executor callback.
enum class ArgumentPassingType : uint8_t {
// No callback. E.g. the RejectArgumentPassingType in a promise with a
// resolve callback but no reject callback.
kNoCallback,
// Executor callback argument passed by value or by reference.
kNormal,
// Executor callback argument passed by r-value reference.
kMove,
};
#if DCHECK_IS_ON()
// Returns details of the resolve and reject executor callbacks if any. This
// data is used to diagnose double moves and missing catches.
ArgumentPassingType ResolveArgumentPassingType() const;
ArgumentPassingType RejectArgumentPassingType() const;
bool CanResolve() const;
bool CanReject() const;
#endif
// Invokes the associate callback for |promise|. If the callback was
// cancelled it should call |promise->OnCanceled()|. If the callback
// resolved it should store the resolve result via |promise->emplace()|. If
// the callback was rejected it should store the reject result in
// |promise->state()|. Caution the Executor will be destructed when
// |promise->state()| is written to.
void Execute(AbstractPromise* promise) {
return data_.vtable_->execute(data_.storage_.array, promise);
}
private:
struct VTable {
void (*destructor)(void* self);
PrerequisitePolicy prerequsite_policy;
bool (*is_cancelled)(const void* self);
#if DCHECK_IS_ON()
ArgumentPassingType (*resolve_argument_passing_type)(const void* self);
ArgumentPassingType (*reject_argument_passing_type)(const void* self);
bool (*can_resolve)(const void* self);
bool (*can_reject)(const void* self);
#endif
void (*execute)(void* self, AbstractPromise* promise);
private:
DISALLOW_COPY_AND_ASSIGN(VTable);
};
template <typename DerivedType>
struct VTableHelper {
VTableHelper(const VTableHelper& other) = delete;
VTableHelper& operator=(const VTableHelper& other) = delete;
static void Destructor(void* self) {
static_cast<DerivedType*>(self)->~DerivedType();
}
static constexpr PromiseExecutor::PrerequisitePolicy kPrerequisitePolicy =
DerivedType::kPrerequisitePolicy;
static PrerequisitePolicy GetPrerequisitePolicy(const void* self) {
return static_cast<const DerivedType*>(self)->GetPrerequisitePolicy();
}
static bool IsCancelled(const void* self) {
return static_cast<const DerivedType*>(self)->IsCancelled();
}
#if DCHECK_IS_ON()
static ArgumentPassingType ResolveArgumentPassingType(const void* self) {
return static_cast<const DerivedType*>(self)
->ResolveArgumentPassingType();
}
static ArgumentPassingType RejectArgumentPassingType(const void* self) {
return static_cast<const DerivedType*>(self)->RejectArgumentPassingType();
}
static bool CanResolve(const void* self) {
return static_cast<const DerivedType*>(self)->CanResolve();
}
static bool CanReject(const void* self) {
return static_cast<const DerivedType*>(self)->CanReject();
}
#endif
static void Execute(void* self, AbstractPromise* promise) {
return static_cast<DerivedType*>(self)->Execute(promise);
}
static constexpr VTable vtable_ = {
&VTableHelper::Destructor,
VTableHelper::kPrerequisitePolicy,
&VTableHelper::IsCancelled,
#if DCHECK_IS_ON()
&VTableHelper::ResolveArgumentPassingType,
&VTableHelper::RejectArgumentPassingType,
&VTableHelper::CanResolve,
&VTableHelper::CanReject,
#endif
&VTableHelper::Execute,
};
};
Data data_;
};
// static
template <typename T>
const PromiseExecutor::VTable PromiseExecutor::VTableHelper<T>::vtable_;
} // namespace internal
} // namespace base
#endif // BASE_TASK_PROMISE_PROMISE_EXECUTOR_H_