Serenity Operating System
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}