/* * Copyright (c) 2019-2020, Sergey Bugaev * Copyright (c) 2021, Andreas Kling * Copyright (c) 2022-2023, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include #include #include namespace Threading { template class BackgroundAction; class BackgroundActionBase { template friend class BackgroundAction; private: BackgroundActionBase() = default; static void enqueue_work(ESCAPING Function); static Thread& background_thread(); }; template class BackgroundAction final : public Core::EventReceiver , private BackgroundActionBase { C_OBJECT(BackgroundAction); public: // Promise is an implementation detail of BackgroundAction in order to communicate with EventLoop. // All of the promise's callbacks and state are either managed by us or by EventLoop. using Promise = Core::Promise>; virtual ~BackgroundAction() = default; Optional const& result() const { return m_result; } Optional& result() { return m_result; } // Cancellation is a best-effort cross-thread signal. No other state is protected by this flag. // It is not used to synchronize access to any other state (m_result), so relaxed atomics are fine. void cancel() { m_canceled.store(true, AK::MemoryOrder::memory_order_relaxed); } // If your action is long-running, you should periodically check the cancel state and possibly return early. bool is_canceled() const { return m_canceled.load(AK::MemoryOrder::memory_order_relaxed); } private: BackgroundAction(ESCAPING Function(BackgroundAction&)> action, ESCAPING Function(Result)> on_complete, ESCAPING Optional> on_error = {}) : m_action(move(action)) , m_on_complete(move(on_complete)) { auto promise = Promise::construct(); if (m_on_complete) { auto self = NonnullRefPtr(*this); promise->on_resolution = [](NonnullRefPtr& object) -> ErrorOr { auto self = static_ptr_cast>(object); VERIFY(self->m_result.has_value()); if (auto maybe_error = self->m_on_complete(self->m_result.release_value()); maybe_error.is_error()) { // If on_complete returns an error, we pass it along to your on_error handler. if (self->m_on_error) self->m_on_error(maybe_error.release_error()); } return {}; }; promise->on_rejection = [self](Error& error) { if (error.is_errno() && error.code() == ECANCELED) self->m_canceled.store(true, AK::MemoryOrder::memory_order_relaxed); }; Core::EventLoop::current().add_job(promise); } if (on_error.has_value()) m_on_error = on_error.release_value(); enqueue_work([self = NonnullRefPtr(*this), promise = move(promise), origin_event_loop = Core::EventLoop::current_weak()]() mutable { auto* self_ptr = self.ptr(); auto post_to_origin = [&](StringView message_type, Function callback) { if (auto origin = origin_event_loop->take()) { origin->deferred_invoke(move(callback)); } else { dbgln("BackgroundAction {:p}: dropped {} (origin loop gone)", self_ptr, message_type); } }; auto result = self->m_action(*self); auto const has_job = static_cast(self->m_on_complete); auto const canceled = self->m_canceled.load(AK::MemoryOrder::memory_order_relaxed); if (canceled) { if (has_job) { post_to_origin("promise rejection"sv, [promise = move(promise)]() mutable { promise->reject(Error::from_errno(ECANCELED)); }); } return; } if (!result.is_error()) { self->m_result = result.release_value(); if (has_job) { post_to_origin("on_complete"sv, [self = move(self), promise = move(promise)]() mutable { // Our promise's resolution function will never error. (void)promise->resolve(*self); }); } return; } auto error = result.release_error(); if (has_job) { post_to_origin("promise rejection"sv, [promise = move(promise), error = Error::copy(error)]() mutable { promise->reject(Error::copy(error)); }); } if (self->m_on_error) { post_to_origin("on_error"sv, [self = move(self), error = Error::copy(error)]() mutable { self->m_on_error(Error::copy(error)); }); } }); } Function(BackgroundAction&)> m_action; Function(Result)> m_on_complete; Function m_on_error = [](Error error) { dbgln("Error occurred while running a BackgroundAction: {}", error); }; Optional m_result; Atomic m_canceled { false }; }; void quit_background_thread(); }