/src/serenity/Userland/Libraries/LibThreading/Thread.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibThreading/Thread.h> |
8 | | #include <pthread.h> |
9 | | #include <string.h> |
10 | | #include <unistd.h> |
11 | | |
12 | | namespace Threading { |
13 | | |
14 | | Thread::Thread(Function<intptr_t()> action, StringView thread_name) |
15 | 32 | : m_action(move(action)) |
16 | 32 | , m_thread_name(thread_name.is_null() ? ""sv : thread_name) |
17 | 32 | { |
18 | 32 | } |
19 | | |
20 | | Thread::~Thread() |
21 | 32 | { |
22 | 32 | if (needs_to_be_joined()) { |
23 | 0 | dbgln("Destroying {} while it is still running undetached!", *this); |
24 | 0 | [[maybe_unused]] auto res = join(); |
25 | 0 | } |
26 | 32 | } |
27 | | |
28 | | ErrorOr<void> Thread::set_priority(int priority) |
29 | 0 | { |
30 | | // MacOS has an extra __opaque field, so list initialization will not compile on MacOS Lagom. |
31 | 0 | sched_param scheduling_parameters {}; |
32 | 0 | scheduling_parameters.sched_priority = priority; |
33 | 0 | int result = pthread_setschedparam(m_tid, 0, &scheduling_parameters); |
34 | 0 | if (result != 0) |
35 | 0 | return Error::from_errno(result); |
36 | 0 | return {}; |
37 | 0 | } |
38 | | |
39 | | ErrorOr<int> Thread::get_priority() const |
40 | 0 | { |
41 | 0 | sched_param scheduling_parameters {}; |
42 | 0 | int policy; |
43 | 0 | int result = pthread_getschedparam(m_tid, &policy, &scheduling_parameters); |
44 | 0 | if (result != 0) |
45 | 0 | return Error::from_errno(result); |
46 | 0 | return scheduling_parameters.sched_priority; |
47 | 0 | } |
48 | | |
49 | 0 | ByteString Thread::thread_name() const { return m_thread_name; } |
50 | | |
51 | 0 | pthread_t Thread::tid() const { return m_tid; } |
52 | | |
53 | 0 | ThreadState Thread::state() const { return m_state; } |
54 | | |
55 | 32 | bool Thread::is_started() const { return m_state != ThreadState::Startable; } |
56 | | |
57 | | bool Threading::Thread::needs_to_be_joined() const |
58 | 64 | { |
59 | 64 | auto state = m_state.load(); |
60 | 64 | return state == ThreadState::Running || state == ThreadState::Exited; |
61 | 64 | } |
62 | | |
63 | | bool Threading::Thread::has_exited() const |
64 | 0 | { |
65 | 0 | auto state = m_state.load(); |
66 | 0 | return state == ThreadState::Joined || state == ThreadState::Exited || state == ThreadState::DetachedExited; |
67 | 0 | } |
68 | | |
69 | | void Thread::start() |
70 | 32 | { |
71 | 32 | VERIFY(!is_started()); |
72 | | |
73 | | // Set this first so that the other thread starts out seeing m_state == Running. |
74 | 32 | m_state = Threading::ThreadState::Running; |
75 | | |
76 | 32 | int rc = pthread_create( |
77 | 32 | &m_tid, |
78 | | // FIXME: Use pthread_attr_t to start a thread detached if that was requested by the user before the call to start(). |
79 | 32 | nullptr, |
80 | 32 | [](void* arg) -> void* { |
81 | 32 | auto self = adopt_ref(*static_cast<Thread*>(arg)); |
82 | | |
83 | 32 | auto exit_code = self->m_action(); |
84 | | |
85 | 32 | auto expected = Threading::ThreadState::Running; |
86 | | // This code might race with a call to detach(). |
87 | 32 | if (!self->m_state.compare_exchange_strong(expected, Threading::ThreadState::Exited)) { |
88 | | // If the original state was Detached, we need to set to DetachedExited instead. |
89 | 0 | if (expected == Threading::ThreadState::Detached) { |
90 | 0 | if (!self->m_state.compare_exchange_strong(expected, Threading::ThreadState::DetachedExited)) { |
91 | 0 | dbgln("Thread logic bug: Found thread state {} while trying to set ExitedDetached state!", expected); |
92 | 0 | VERIFY_NOT_REACHED(); |
93 | 0 | } |
94 | 0 | } else { |
95 | 0 | dbgln("Thread logic bug: Found thread state {} while trying to set Exited state!", expected); |
96 | 0 | VERIFY_NOT_REACHED(); |
97 | 0 | } |
98 | 0 | } |
99 | | |
100 | 32 | return reinterpret_cast<void*>(exit_code); |
101 | 32 | }, |
102 | 32 | &NonnullRefPtr(*this).leak_ref()); |
103 | | |
104 | 32 | VERIFY(rc == 0); |
105 | | #ifdef AK_OS_SERENITY |
106 | | if (!m_thread_name.is_empty()) { |
107 | | rc = pthread_setname_np(m_tid, m_thread_name.characters()); |
108 | | VERIFY(rc == 0); |
109 | | } |
110 | | #endif |
111 | 32 | } |
112 | | |
113 | | void Thread::detach() |
114 | 0 | { |
115 | 0 | auto expected = Threading::ThreadState::Running; |
116 | | // This code might race with the other thread exiting. |
117 | 0 | if (!m_state.compare_exchange_strong(expected, Threading::ThreadState::Detached)) { |
118 | 0 | if (expected == Threading::ThreadState::Exited) |
119 | 0 | return; |
120 | | |
121 | | // Always report a precise error before crashing. These kinds of bugs are hard to reproduce. |
122 | 0 | dbgln("Thread logic bug: trying to detach {} in state {}, which is neither Started nor Exited", this, expected); |
123 | 0 | VERIFY_NOT_REACHED(); |
124 | 0 | } |
125 | | |
126 | 0 | int rc = pthread_detach(m_tid); |
127 | 0 | VERIFY(rc == 0); |
128 | 0 | } |
129 | | |
130 | | } |