blob: d4095b3b6ae14303ff5cb60f7dbf664fc08d5acf [file] [log] [blame]
// Copyright 2023 The Fuchsia 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 LIB_ASYNC_PATTERNS_CPP_INTERNAL_DISPATCHER_BOUND_STORAGE_H_
#define LIB_ASYNC_PATTERNS_CPP_INTERNAL_DISPATCHER_BOUND_STORAGE_H_
#include <lib/async/cpp/sequence_checker.h>
#include <lib/async/dispatcher.h>
#include <lib/async_patterns/cpp/callback.h>
#include <lib/async_patterns/cpp/sendable.h>
#include <lib/fit/function.h>
#include <lib/stdcompat/functional.h>
#include <lib/stdcompat/type_traits.h>
#include <zircon/assert.h>
#include <cstdlib>
namespace async_patterns::internal {
struct PassDispatcherT {
// Pretend this is a dispatcher pointer to support |std::is_invocable| tests.
// It is never called in practice.
// NOLINTNEXTLINE(google-explicit-constructor)
operator async_dispatcher_t*() const {
__builtin_abort();
return nullptr;
}
};
class SynchronizedBase {
public:
explicit SynchronizedBase(async_dispatcher_t* dispatcher);
/// Returns a callable that will check for synchronization before calling some
/// member function of |Base|.
///
/// More precisely:
///
/// - Let |fn| be the result of `bind_front(member_fn, base)`.
///
/// - Returns a callable that takes some arguments. When called, that callable
/// will check for synchronization, then forward those arguments to |fn|.
template <typename Base, typename Callable>
auto Bind(Base* base, Callable member_fn) {
return [member_fn = std::move(member_fn), this, base](auto&&... args) mutable {
std::lock_guard lock(checker_);
return cpp20::bind_front(std::forward<Callable>(member_fn), base)(std::move(args)...);
};
}
private:
async::synchronization_checker checker_;
};
// |Synchronized<T>| adds a synchronization checker before |T|.
template <typename T>
class Synchronized : public SynchronizedBase {
public:
template <typename... Args>
explicit Synchronized(async_dispatcher_t* dispatcher, Args&&... args)
: SynchronizedBase(dispatcher), inner_(std::forward<Args>(args)...) {}
T* inner() { return &inner_; }
private:
T inner_;
};
// |DispatcherBoundStorage| encapsulates the subtle work of managing memory
// across concurrency domains so |DispatcherBound| could be a minimal wrapper.
class DispatcherBoundStorage final {
public:
DispatcherBoundStorage() = default;
~DispatcherBoundStorage();
bool has_value() const { return static_cast<bool>(op_fn_); }
// Asynchronously constructs |T| on the |dispatcher|.
//
// |T| will be constructed and later destructed inside tasks posted to the
// |dispatcher|.
template <typename Base, typename T, typename... Args>
void Construct(async_dispatcher_t* dispatcher, Args&&... args) {
ZX_ASSERT(!op_fn_);
// We first allocate the underlying memory before posting any tasks,
// so that the tasks have an agreed-upon memory location to construct
// and destruct |T|, even if |DispatcherBoundStorage| is destroyed.
Synchronized<T>* ptr = std::allocator<Synchronized<T>>{}.allocate(1);
Base* base = ptr->inner();
// |op_fn_| let us compactly store both the destructor and the pointer to
// the managed object in one inlined type erasing object (a function)
// without heap allocation.
op_fn_ = [ptr, base](Operation op) -> void* {
switch (op) {
case Operation::kDestruct:
ptr->~Synchronized<T>();
std::allocator<Synchronized<T>>{}.deallocate(ptr, 1);
return nullptr;
case Operation::kGetBasePointer:
// During |Call|, |base| will be cast back to |Base|. This is guaranteed to
// yield the same object pointer by the language. See
// https://timsong-cpp.github.io/cppwp/n4861/expr.static.cast#13
return static_cast<void*>(base);
case Operation::kGetPointer:
// During |Call|, |ptr| will be cast to |SynchronizedBase|. Similar reasoning follows.
return static_cast<void*>(static_cast<SynchronizedBase*>(ptr));
}
};
ConstructInternal(dispatcher,
BindForSending(
[dispatcher, ptr](auto&&... args) {
new (ptr) Synchronized<T>(dispatcher, std::move(args)...);
},
ForwardOrPassDispatcher(dispatcher, std::forward<Args>(args))...));
}
template <template <typename, typename, typename> typename Builder, typename Result, typename T,
typename Callable, typename... Args>
auto AsyncCall(async_dispatcher_t* dispatcher, Callable&& callable, Args&&... args) {
void* raw_base_ptr = op_fn_(Operation::kGetBasePointer);
T* base = static_cast<T*>(raw_base_ptr);
void* raw_ptr = op_fn_(Operation::kGetPointer);
SynchronizedBase* ptr = static_cast<SynchronizedBase*>(raw_ptr);
auto make_task = [&] {
return BindForSending(ptr->Bind(base, std::forward<Callable>(callable)),
ForwardOrPassDispatcher(dispatcher, std::forward<Args>(args))...);
};
return Builder<Result, decltype(make_task()), SubmitWithDispatcherBoundStorage>(
make_task(), SubmitWithDispatcherBoundStorage{this, dispatcher}, Tag<Result>());
}
template <typename Arg>
auto ForwardOrPassDispatcher(async_dispatcher_t* dispatcher, Arg&& arg) {
if constexpr (std::is_same_v<cpp20::remove_cvref_t<Arg>, PassDispatcherT>) {
return Smuggle(dispatcher);
} else {
return std::forward<Arg>(arg);
}
}
// Asynchronously destructs the object that was constructed earlier in
// |Construct|.
void Destruct(async_dispatcher_t* dispatcher);
enum class Operation : uint8_t {
kDestruct,
kGetBasePointer,
kGetPointer,
};
static void ConstructInternal(async_dispatcher_t* dispatcher, fit::callback<void()> task);
void CallInternal(async_dispatcher_t* dispatcher, fit::callback<void()> member);
private:
// A type that submits tasks using |DispatcherBoundStorage| that is meant to work
// together with |PendingCall|.
class SubmitWithDispatcherBoundStorage {
public:
SubmitWithDispatcherBoundStorage(DispatcherBoundStorage* storage,
async_dispatcher_t* dispatcher)
: storage_(storage), dispatcher_(dispatcher) {}
template <typename Task>
void operator()(Task&& task) {
ZX_DEBUG_ASSERT(storage_);
storage_->CallInternal(dispatcher_, std::forward<Task>(task));
reset();
}
bool has_value() const { return storage_; }
void reset() { storage_ = nullptr; }
private:
DispatcherBoundStorage* storage_;
async_dispatcher_t* dispatcher_;
};
// |op_fn_| type-erases the managed object so |DispatcherBoundStorage| avoids
// template bloat. See |DispatcherBoundStorage::Construct|.
//
// If |op_fn_| is valid, then the storage holds an object.
fit::inline_function<void*(Operation)> op_fn_;
};
} // namespace async_patterns::internal
#endif // LIB_ASYNC_PATTERNS_CPP_INTERNAL_DISPATCHER_BOUND_STORAGE_H_