/src/serenity/Userland/Libraries/LibThreading/Thread.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org> |
3 | | * Copyright (c) 2021, Spencer Dixon <spencercdixon@gmail.com> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #pragma once |
9 | | |
10 | | #include <AK/Assertions.h> |
11 | | #include <AK/AtomicRefCounted.h> |
12 | | #include <AK/ByteString.h> |
13 | | #include <AK/DistinctNumeric.h> |
14 | | #include <AK/Function.h> |
15 | | #include <AK/Result.h> |
16 | | #include <LibCore/EventReceiver.h> |
17 | | #include <pthread.h> |
18 | | |
19 | | namespace Threading { |
20 | | |
21 | | AK_TYPEDEF_DISTINCT_ORDERED_ID(intptr_t, ThreadError); |
22 | | |
23 | | // States of userspace threads are simplified over actual kernel states (and possibly POSIX states). |
24 | | // There are only a couple of well-defined transitions between these states, and any attempt to call a function in a state where this is not allowed will crash the program. |
25 | | enum class ThreadState : u8 { |
26 | | // Thread has been constructed but not started. |
27 | | // Transitions to Running via start(). |
28 | | Startable, |
29 | | // Thread has been started, might be running, and can be joined. |
30 | | // Note that join() (valid to call in this state) only changes the thread state after the thread has exited, so it only ever transitions from Exited to Joined. |
31 | | // Transitions to Detached via detach(), transitions to Exited when the thread finishes its action function. |
32 | | Running, |
33 | | // Thread has not been detached and exited, and has to still be joined. |
34 | | // Transitions to Joined via join(). |
35 | | Exited, |
36 | | // Thread has been started but also detached, meaning it cannot be joined. |
37 | | // Transitions to DetachedExited when the thread finishes its action function. |
38 | | Detached, |
39 | | // Thread has exited but was detached, meaning it cannot be joined. |
40 | | DetachedExited, |
41 | | // Thread has exited and been joined. |
42 | | Joined, |
43 | | }; |
44 | | |
45 | | class Thread final |
46 | | : public AtomicRefCounted<Thread> |
47 | | , public Weakable<Thread> { |
48 | | public: |
49 | | static NonnullRefPtr<Thread> construct(ESCAPING Function<intptr_t()> action, StringView thread_name = {}) |
50 | 0 | { |
51 | 0 | return adopt_ref(*new Thread(move(action), thread_name)); |
52 | 0 | } |
53 | | static ErrorOr<NonnullRefPtr<Thread>> try_create(ESCAPING Function<intptr_t()> action, StringView thread_name = {}) |
54 | 88 | { |
55 | 88 | return adopt_nonnull_ref_or_enomem(new (nothrow) Thread(move(action), thread_name)); |
56 | 88 | } |
57 | | |
58 | | ~Thread(); |
59 | | |
60 | | ErrorOr<void> set_priority(int priority); |
61 | | ErrorOr<int> get_priority() const; |
62 | | |
63 | | // Only callable in the Startable state. |
64 | | void start(); |
65 | | // Only callable in the Running state. |
66 | | void detach(); |
67 | | |
68 | | // Only callable in the Running or Exited states. |
69 | | template<typename T = void> |
70 | | Result<T, ThreadError> join(); |
71 | | |
72 | | ByteString thread_name() const; |
73 | | pthread_t tid() const; |
74 | | ThreadState state() const; |
75 | | bool is_started() const; |
76 | | bool needs_to_be_joined() const; |
77 | | bool has_exited() const; |
78 | | |
79 | | private: |
80 | | explicit Thread(ESCAPING Function<intptr_t()> action, StringView thread_name = {}); |
81 | | Function<intptr_t()> m_action; |
82 | | pthread_t m_tid { 0 }; |
83 | | ByteString m_thread_name; |
84 | | Atomic<ThreadState> m_state { ThreadState::Startable }; |
85 | | }; |
86 | | |
87 | | template<typename T> |
88 | | Result<T, ThreadError> Thread::join() |
89 | 88 | { |
90 | 88 | VERIFY(needs_to_be_joined()); |
91 | | |
92 | 88 | void* thread_return = nullptr; |
93 | 88 | int rc = pthread_join(m_tid, &thread_return); |
94 | 88 | if (rc != 0) { |
95 | 0 | return ThreadError { rc }; |
96 | 0 | } |
97 | | |
98 | | // The other thread has now stopped running, so a TOCTOU bug is not possible. |
99 | | // (If you call join from two different threads, you're doing something *very* wrong anyways.) |
100 | 88 | VERIFY(m_state == ThreadState::Exited); |
101 | 88 | m_state = ThreadState::Joined; |
102 | | |
103 | | if constexpr (IsVoid<T>) |
104 | 88 | return {}; |
105 | | else |
106 | | return { static_cast<T>(thread_return) }; |
107 | 88 | } |
108 | | |
109 | | } |
110 | | |
111 | | template<> |
112 | | struct AK::Formatter<Threading::Thread> : AK::Formatter<FormatString> { |
113 | | ErrorOr<void> format(FormatBuilder& builder, Threading::Thread const& thread) |
114 | 0 | { |
115 | 0 | return Formatter<FormatString>::format(builder, "Thread \"{}\"({})"sv, thread.thread_name(), thread.tid()); |
116 | 0 | } |
117 | | }; |
118 | | |
119 | | template<> |
120 | | struct AK::Formatter<Threading::ThreadState> : AK::Formatter<FormatString> { |
121 | | ErrorOr<void> format(FormatBuilder& builder, Threading::ThreadState state) |
122 | 0 | { |
123 | 0 | ByteString name = ""; |
124 | 0 | switch (state) { |
125 | 0 | case Threading::ThreadState::Detached: |
126 | 0 | name = "Detached"; |
127 | 0 | break; |
128 | 0 | case Threading::ThreadState::DetachedExited: |
129 | 0 | name = "DetachedExited"; |
130 | 0 | break; |
131 | 0 | case Threading::ThreadState::Exited: |
132 | 0 | name = "Exited"; |
133 | 0 | break; |
134 | 0 | case Threading::ThreadState::Joined: |
135 | 0 | name = "Joined"; |
136 | 0 | break; |
137 | 0 | case Threading::ThreadState::Running: |
138 | 0 | name = "Running"; |
139 | 0 | break; |
140 | 0 | case Threading::ThreadState::Startable: |
141 | 0 | name = "Startable"; |
142 | 0 | break; |
143 | 0 | default: |
144 | 0 | VERIFY_NOT_REACHED(); |
145 | 0 | } |
146 | 0 | return Formatter<FormatString>::format(builder, "{}"sv, name); |
147 | 0 | } |
148 | | }; |