blob: 5d8d33eaf8a028e493443af536b990a51a607a8a [file] [log] [blame]
// Copyright 2021 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_FDF_CPP_DISPATCHER_H_
#define LIB_FDF_CPP_DISPATCHER_H_
#include <lib/async/dispatcher.h>
#include <lib/fdf/cpp/unowned.h>
#include <lib/fdf/dispatcher.h>
#include <lib/fit/function.h>
#include <lib/stdcompat/string_view.h>
#include <lib/zx/result.h>
#include <zircon/assert.h>
#include <string>
namespace fdf_env {
// Forward declaration to support friend declaration.
class DispatcherBuilder;
} // namespace fdf_env
namespace fdf_internal {
// Forward declaration to support friend declaration.
class TestDispatcherBuilder;
} // namespace fdf_internal
namespace fdf {
// C++ wrapper for a dispatcher, with RAII semantics. Automatically shuts down
// the dispatcher when it goes out of scope.
//
// # Thread safety
//
// This class is thread-unsafe.
//
// # Example
//
// void Driver::OnDispatcherShutdown(fdf_dispatcher_t* dispatcher) {
// // Handle dispatcher shutdown.
// // It is now safe to destroy |dispatcher|.
// }
//
// void Driver::Start() {
// // TODO(https://fxbug.dev/42166901): update this once scheduler_role is supported.
// const std::string_view scheduler_role = "";
// const std::string_view name = "MyDriver";
//
// auto shutdown_handler = [&]() {
// OnDispatcherShutdown();
// };
// auto dispatcher =
// fdf::SynchronizedDispatcher::Create(0, name, shutdown_handler, scheduler_role);
//
// fdf::ChannelRead channel_read;
// ...
// zx_status_t status = channel_read->Begin(dispatcher.get());
//
// // The dispatcher will call the channel_read handler when ready.
// }
class Dispatcher {
public:
using HandleType = fdf_dispatcher_t*;
// Called when the asynchronous shutdown for |dispatcher| has completed.
using ShutdownHandler = fit::callback<void(fdf_dispatcher_t* dispatcher)>;
// Returns the current thread's dispatcher.
// This will return NULL if not called from a dispatcher managed thread.
static Unowned<Dispatcher> GetCurrent() {
return Unowned<Dispatcher>(fdf_dispatcher_get_current_dispatcher());
}
// Returns an unowned dispatcher provided an async dispatcher. If |async_dispatcher| was not
// retrieved via |fdf_dispatcher_get_async_dispatcher|, the call will result in a crash.
static Unowned<Dispatcher> Downcast(async_dispatcher_t* async_dispatcher) {
return Unowned<Dispatcher>(fdf_dispatcher_downcast_async_dispatcher(async_dispatcher));
}
explicit Dispatcher(fdf_dispatcher_t* dispatcher = nullptr) : dispatcher_(dispatcher) {}
// Dispatcher cannot be copied.
Dispatcher(const Dispatcher& to_copy) = delete;
Dispatcher& operator=(const Dispatcher& other) = delete;
// Dispatcher can be moved. Once moved, invoking a method on an instance will
// yield undefined behavior.
Dispatcher(Dispatcher&& other) noexcept : Dispatcher(other.release()) {}
Dispatcher& operator=(Dispatcher&& other) noexcept {
reset(other.release());
return *this;
}
// Begins shutting down the dispatcher. Shutting down is an asynchronous operation.
//
// Once |Dispatcher::ShutdownAsync| is called, the dispatcher will no longer
// accept queueing new |async_dispatcher_t| operations or |ChannelRead| callbacks.
//
// The dispatcher will asynchronously wait for all pending |async_dispatcher_t|
// and |ChannelRead| callbacks to complete. Then it will serially cancel all
// remaining callbacks with |ZX_ERR_CANCELED| and call the shutdown handler set
// in |SynchronizedDispatcher::Create| or |UnsynchronizedDispatcher::Create|.
//
// If the dispatcher is already shutting down or has completed shutdown, this will do nothing.
void ShutdownAsync() {
if (dispatcher_) {
fdf_dispatcher_shutdown_async(dispatcher_);
}
}
// The dispatcher must be completely shutdown before the dispatcher can be closed.
// i.e. the shutdown handler set in |SynchronizedDispatcher::Create|
// or |UnsynchronizedDispatcher::Create| has been called.
// It is safe to call this from that shutdown handler.
~Dispatcher() { close(); }
fdf_dispatcher_t* get() const { return dispatcher_; }
void reset(fdf_dispatcher_t* dispatcher = nullptr) {
close();
dispatcher_ = dispatcher;
}
void close() {
if (dispatcher_) {
fdf_dispatcher_destroy(dispatcher_);
dispatcher_ = nullptr;
}
}
fdf_dispatcher_t* release() {
fdf_dispatcher_t* ret = dispatcher_;
dispatcher_ = nullptr;
return ret;
}
// Gets the dispatcher's asynchronous dispatch interface.
async_dispatcher_t* async_dispatcher() const {
return dispatcher_ ? fdf_dispatcher_get_async_dispatcher(dispatcher_) : nullptr;
}
// Returns the options set for this dispatcher.
std::optional<uint32_t> options() const {
return dispatcher_ ? std::optional(fdf_dispatcher_get_options(dispatcher_)) : std::nullopt;
}
Unowned<Dispatcher> borrow() const { return Unowned<Dispatcher>(dispatcher_); }
// Removes the allow_sync option that was set on the dispatcher during creation.
// Calling this will remove the ability of the dispatcher to make any more synchronous calls.
//
// # Thread requirements
//
// This must be called from the |dispatcher|'s own context.
//
// # Errors
//
// ZX_ERR_BAD_STATE: Not called from the same context as the given dispatcher or the dispatcher
// is not in a running state.
//
// ZX_ERR_ALREADY_EXISTS: The dispatcher did not contain the allow_sync option. This can happen
// if this is called more than once, or if it was not created with that option.
zx::result<> SealAllowSync() {
return zx::make_result(
fdf_dispatcher_seal(dispatcher_, FDF_DISPATCHER_OPTION_ALLOW_SYNC_CALLS));
}
protected:
// Friend declaration is needed because the |DispatcherShutdownContext| is private.
friend class fdf_env::DispatcherBuilder;
friend class fdf_internal::TestDispatcherBuilder;
class DispatcherShutdownContext final {
public:
explicit DispatcherShutdownContext(ShutdownHandler handler)
: observer_{CallHandler}, handler_(std::move(handler)) {}
fdf_dispatcher_shutdown_observer_t* observer() { return &observer_; }
private:
static void CallHandler(fdf_dispatcher_t* dispatcher,
fdf_dispatcher_shutdown_observer_t* observer) {
static_assert(offsetof(DispatcherShutdownContext, observer_) == 0);
auto self = reinterpret_cast<DispatcherShutdownContext*>(observer);
self->handler_(dispatcher);
// Delete the pointer allocated in |SynchronizedDispatcher::Create| or
// |UnsynchronizedDispatcher::Create|.
delete self;
}
fdf_dispatcher_shutdown_observer_t observer_;
ShutdownHandler handler_;
};
fdf_dispatcher_t* dispatcher_;
};
// Dispatcher that disallows parallel calls into callbacks.
class SynchronizedDispatcher final : public Dispatcher {
public:
// Options that may be passed to |fdf::SynchronizedDispatcher::Create|.
// When using the |SynchronizedDispatcher| class, the dispatcher options must set
// FDF_DISPATCHER_OPTION_SYNCHRONIZED.
struct Options {
// The options to set for the dispatcher. In additional to FDF_DISPATCHER_OPTION_SYNCHRONIZED,
// the following options are supported:
// * `FDF_DISPATCHER_OPTION_ALLOW_SYNC_CALLS` - blocking calls may be made on this dispatcher.
uint32_t value = FDF_DISPATCHER_OPTION_SYNCHRONIZED;
// Specifies a synchronized dispatcher that allows blocking calls to be made.
static const Options kAllowSyncCalls;
};
// Creates a dispatcher for performing asynchronous operations.
//
// |options| provides the dispatcher configuration. This will panic if options does not
// set FDF_DISPATCHER_OPTION_SYNCHRONIZED.
//
// |name| is reported via diagnostics. It is similar to setting the name of a thread. Names longer
// than `ZX_MAX_NAME_LEN` may be truncated.
//
// |scheduler_role| is a hint. It may or not impact the priority the work scheduler against the
// dispatcher is handled at. It may or may not impact the ability for other drivers to share
// zircon threads with the dispatcher.
//
// |shutdown_handler| will be called after |ShutdownAsync| has been called, and the dispatcher
// has completed its asynchronous shutdown. The client must keep any pointers that are
// referenced in |shutdown_handler| alive until the handler runs.
//
// # Thread requirements
//
// This must be called from a thread managed by the driver runtime.
//
// # Errors
//
// ZX_ERR_INVALID_ARGS: This was not called from a thread managed by the driver runtime.
//
// ZX_ERR_BAD_STATE: Dispatchers are currently not allowed to be created, such as when a driver
// is being shutdown by its driver host.
static zx::result<SynchronizedDispatcher> Create(Options options, cpp17::string_view name,
ShutdownHandler shutdown_handler,
cpp17::string_view scheduler_role = {}) {
ZX_ASSERT_MSG((options.value & FDF_DISPATCHER_OPTION_SYNCHRONIZATION_MASK) ==
FDF_DISPATCHER_OPTION_SYNCHRONIZED,
"options.value=%u, needs to have FDF_DISPATCHER_OPTION_SYNCHRONIZED",
options.value);
// We need to create an additional shutdown context in addition to the fdf::Dispatcher
// object, as the fdf::SynchronizedDispatcher may be destructed before the shutdown handler
// is called. This can happen if the raw pointer is released from the
// fdf::SynchronizedDispatcher.
auto dispatcher_shutdown_context =
std::make_unique<DispatcherShutdownContext>(std::move(shutdown_handler));
fdf_dispatcher_t* dispatcher;
zx_status_t status =
fdf_dispatcher_create(FDF_DISPATCHER_OPTION_SYNCHRONIZED | options.value, name.data(),
name.size(), scheduler_role.data(), scheduler_role.size(),
dispatcher_shutdown_context->observer(), &dispatcher);
if (status != ZX_OK) {
return zx::error(status);
}
dispatcher_shutdown_context.release();
return zx::ok(SynchronizedDispatcher(dispatcher));
}
explicit SynchronizedDispatcher(fdf_dispatcher_t* dispatcher = nullptr) : Dispatcher(dispatcher) {
if (dispatcher) {
ZX_ASSERT((fdf_dispatcher_get_options(dispatcher) &
FDF_DISPATCHER_OPTION_SYNCHRONIZATION_MASK) == FDF_DISPATCHER_OPTION_SYNCHRONIZED);
}
}
Unowned<SynchronizedDispatcher> borrow() const {
return Unowned<SynchronizedDispatcher>(dispatcher_);
}
};
inline constexpr SynchronizedDispatcher::Options SynchronizedDispatcher::Options::kAllowSyncCalls =
{FDF_DISPATCHER_OPTION_SYNCHRONIZED | FDF_DISPATCHER_OPTION_ALLOW_SYNC_CALLS};
// Dispatcher that allows parallel calls into callbacks.
class UnsynchronizedDispatcher final : public Dispatcher {
public:
// Options that may be passed to |fdf::UnsynchronizedDispatcher::Create|.
// When using the |UnsynchronizedDispatcher| class, the dispatcher options must set
// FDF_DISPATCHER_OPTION_UNSYNCHRONIZED.
struct Options {
// The options to set for the dispatcher. Currently no additional options are supported.
uint32_t value = FDF_DISPATCHER_OPTION_UNSYNCHRONIZED;
};
// Creates a dispatcher for performing asynchronous operations.
//
// |options| provides the dispatcher configuration. This will panic if options does not
// set FDF_DISPATCHER_OPTION_UNSYNCHRONIZED.
//
// |name| is reported via diagnostics. It is similar to setting the name of a thread. Names longer
// than `ZX_MAX_NAME_LEN` may be truncated.
//
// |scheduler_role| is a hint. It may or not impact the priority the work scheduler against the
// dispatcher is handled at. It may or may not impact the ability for other drivers to share
// zircon threads with the dispatcher.
//
// |shutdown_handler| will be called after |ShutdownAsync| has been called, and the dispatcher
// has completed its asynchronous shutdown. The client must keep any pointers that are
// referenced in |shutdown_handler| alive until the handler runs.
//
// # Thread requirements
//
// This must be called from a thread managed by the driver runtime.
//
// # Errors
//
// ZX_ERR_NOT_SUPPORTED: |options| is not a supported configuration, which is any of:
// * `FDF_DISPATCHER_OPTION_ALLOW_SYNC_CALLS`.
//
// ZX_ERR_INVALID_ARGS: This was not called from a thread managed by the driver runtime.
//
// ZX_ERR_BAD_STATE: Dispatchers are currently not allowed to be created, such as when a driver
// is being shutdown by its driver host.
static zx::result<UnsynchronizedDispatcher> Create(Options options, cpp17::string_view name,
ShutdownHandler shutdown_handler,
cpp17::string_view scheduler_role = {}) {
ZX_ASSERT_MSG((options.value & FDF_DISPATCHER_OPTION_SYNCHRONIZATION_MASK) ==
FDF_DISPATCHER_OPTION_UNSYNCHRONIZED,
"options.value=%u, needs to have FDF_DISPATCHER_OPTION_UNSYNCHRONIZED",
options.value);
// We need to create an additional shutdown context in addition to the fdf::Dispatcher
// object, as the fdf::UnsynchronizedDispatcher may be destructed before the shutdown handler
// is called. This can happen if the raw pointer is released from the
// fdf::UnsynchronizedDispatcher.
auto dispatcher_shutdown_context =
std::make_unique<DispatcherShutdownContext>(std::move(shutdown_handler));
fdf_dispatcher_t* dispatcher;
zx_status_t status =
fdf_dispatcher_create(FDF_DISPATCHER_OPTION_UNSYNCHRONIZED | options.value, name.data(),
name.size(), scheduler_role.data(), scheduler_role.size(),
dispatcher_shutdown_context->observer(), &dispatcher);
if (status != ZX_OK) {
return zx::error(status);
}
dispatcher_shutdown_context.release();
return zx::ok(UnsynchronizedDispatcher(dispatcher));
}
explicit UnsynchronizedDispatcher(fdf_dispatcher_t* dispatcher = nullptr)
: Dispatcher(dispatcher) {
if (dispatcher) {
ZX_ASSERT(
(fdf_dispatcher_get_options(dispatcher) & FDF_DISPATCHER_OPTION_SYNCHRONIZATION_MASK) ==
FDF_DISPATCHER_OPTION_UNSYNCHRONIZED);
}
}
Unowned<UnsynchronizedDispatcher> borrow() const {
return Unowned<UnsynchronizedDispatcher>(dispatcher_);
}
};
using UnownedDispatcher = Unowned<Dispatcher>;
using UnownedSynchronizedDispatcher = Unowned<SynchronizedDispatcher>;
using UnownedUnsynchronizedDispatcher = Unowned<UnsynchronizedDispatcher>;
} // namespace fdf
#endif // LIB_FDF_CPP_DISPATCHER_H_