Serenity Operating System
at master 119 lines 4.4 kB view raw
1/* 2 * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org> 3 * Copyright (c) 2021, Andreas Kling <kling@serenityos.org> 4 * Copyright (c) 2022-2023, the SerenityOS developers. 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#pragma once 10 11#include <AK/Function.h> 12#include <AK/NonnullRefPtr.h> 13#include <AK/Optional.h> 14#include <AK/Queue.h> 15#include <LibCore/Event.h> 16#include <LibCore/EventLoop.h> 17#include <LibCore/Object.h> 18#include <LibCore/Promise.h> 19#include <LibThreading/Thread.h> 20 21namespace Threading { 22 23template<typename Result> 24class BackgroundAction; 25 26class BackgroundActionBase { 27 template<typename Result> 28 friend class BackgroundAction; 29 30private: 31 BackgroundActionBase() = default; 32 33 static void enqueue_work(Function<void()>); 34 static Thread& background_thread(); 35}; 36 37template<typename Result> 38class BackgroundAction final : public Core::Object 39 , private BackgroundActionBase { 40 C_OBJECT(BackgroundAction); 41 42public: 43 // Promise is an implementation detail of BackgroundAction in order to communicate with EventLoop. 44 // All of the promise's callbacks and state are either managed by us or by EventLoop. 45 using Promise = Core::Promise<NonnullRefPtr<Core::Object>>; 46 47 virtual ~BackgroundAction() = default; 48 49 Optional<Result> const& result() const { return m_result; } 50 Optional<Result>& result() { return m_result; } 51 52 void cancel() { m_canceled = true; } 53 // If your action is long-running, you should periodically check the cancel state and possibly return early. 54 bool is_canceled() const { return m_canceled; } 55 56private: 57 BackgroundAction(Function<ErrorOr<Result>(BackgroundAction&)> action, Function<ErrorOr<void>(Result)> on_complete, Optional<Function<void(Error)>> on_error = {}) 58 : Core::Object(&background_thread()) 59 , m_promise(Promise::try_create().release_value_but_fixme_should_propagate_errors()) 60 , m_action(move(action)) 61 , m_on_complete(move(on_complete)) 62 { 63 if (m_on_complete) { 64 m_promise->on_resolved = [](NonnullRefPtr<Core::Object>& object) -> ErrorOr<void> { 65 auto self = static_ptr_cast<BackgroundAction<Result>>(object); 66 VERIFY(self->m_result.has_value()); 67 if (auto maybe_error = self->m_on_complete(self->m_result.value()); maybe_error.is_error()) 68 self->m_on_error(maybe_error.release_error()); 69 70 return {}; 71 }; 72 Core::EventLoop::current().add_job(m_promise); 73 } 74 75 if (on_error.has_value()) 76 m_on_error = on_error.release_value(); 77 78 enqueue_work([this, origin_event_loop = &Core::EventLoop::current()] { 79 auto result = m_action(*this); 80 // The event loop cancels the promise when it exits. 81 m_canceled |= m_promise->is_canceled(); 82 // All of our work was successful and we weren't cancelled; resolve the event loop's promise. 83 if (!m_canceled && !result.is_error()) { 84 m_result = result.release_value(); 85 // If there is no completion callback, we don't rely on the user keeping around the event loop. 86 if (m_on_complete) { 87 origin_event_loop->deferred_invoke([this] { 88 // Our promise's resolution function will never error. 89 (void)m_promise->resolve(*this); 90 remove_from_parent(); 91 }); 92 origin_event_loop->wake(); 93 } 94 } else { 95 // We were either unsuccessful or cancelled (in which case there is no error). 96 auto error = Error::from_errno(ECANCELED); 97 if (result.is_error()) 98 error = result.release_error(); 99 100 m_promise->cancel(Error::from_errno(ECANCELED)); 101 if (m_on_error) 102 m_on_error(move(error)); 103 104 remove_from_parent(); 105 } 106 }); 107 } 108 109 NonnullRefPtr<Promise> m_promise; 110 Function<ErrorOr<Result>(BackgroundAction&)> m_action; 111 Function<ErrorOr<void>(Result)> m_on_complete; 112 Function<void(Error)> m_on_error = [](Error error) { 113 dbgln("Error occurred while running a BackgroundAction: {}", error); 114 }; 115 Optional<Result> m_result; 116 bool m_canceled { false }; 117}; 118 119}