Serenity Operating System
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
12namespace Threading {
13
14Thread::Thread(Function<intptr_t()> action, StringView thread_name)
15 : Core::Object(nullptr)
16 , m_action(move(action))
17 , m_thread_name(thread_name.is_null() ? ""sv : thread_name)
18{
19 register_property("thread_name", [&] { return JsonValue { m_thread_name }; });
20#if defined(AK_OS_SERENITY) || defined(AK_OS_LINUX)
21 // FIXME: Print out a pretty TID for BSD and macOS platforms, too
22 register_property("tid", [&] { return JsonValue { m_tid }; });
23#endif
24}
25
26Thread::~Thread()
27{
28 if (needs_to_be_joined()) {
29 dbgln("Destroying {} while it is still running undetached!", *this);
30 [[maybe_unused]] auto res = join();
31 }
32 if (m_state == ThreadState::Detached)
33 dbgln("Bug! {} in state {} is being destroyed; AK/Function will crash shortly!", *this, m_state.load());
34}
35
36ErrorOr<void> Thread::set_priority(int priority)
37{
38 // MacOS has an extra __opaque field, so list initialization will not compile on MacOS Lagom.
39 sched_param scheduling_parameters {};
40 scheduling_parameters.sched_priority = priority;
41 int result = pthread_setschedparam(m_tid, 0, &scheduling_parameters);
42 if (result != 0)
43 return Error::from_errno(result);
44 return {};
45}
46
47ErrorOr<int> Thread::get_priority() const
48{
49 sched_param scheduling_parameters {};
50 int policy;
51 int result = pthread_getschedparam(m_tid, &policy, &scheduling_parameters);
52 if (result != 0)
53 return Error::from_errno(result);
54 return scheduling_parameters.sched_priority;
55}
56
57DeprecatedString Thread::thread_name() const { return m_thread_name; }
58
59pthread_t Thread::tid() const { return m_tid; }
60
61ThreadState Thread::state() const { return m_state; }
62
63bool Thread::is_started() const { return m_state != ThreadState::Startable; }
64
65bool Threading::Thread::needs_to_be_joined() const
66{
67 auto state = m_state.load();
68 return state == ThreadState::Running || state == ThreadState::Exited;
69}
70
71bool Threading::Thread::has_exited() const
72{
73 auto state = m_state.load();
74 return state == ThreadState::Joined || state == ThreadState::Exited || state == ThreadState::DetachedExited;
75}
76
77void Thread::start()
78{
79 VERIFY(!is_started());
80
81 // Set this first so that the other thread starts out seeing m_state == Running.
82 m_state = Threading::ThreadState::Running;
83
84 int rc = pthread_create(
85 &m_tid,
86 // FIXME: Use pthread_attr_t to start a thread detached if that was requested by the user before the call to start().
87 nullptr,
88 [](void* arg) -> void* {
89 Thread* self = static_cast<Thread*>(arg);
90 auto exit_code = self->m_action();
91
92 auto expected = Threading::ThreadState::Running;
93 // This code might race with a call to detach().
94 if (!self->m_state.compare_exchange_strong(expected, Threading::ThreadState::Exited)) {
95 // If the original state was Detached, we need to set to DetachedExited instead.
96 if (expected == Threading::ThreadState::Detached) {
97 if (!self->m_state.compare_exchange_strong(expected, Threading::ThreadState::DetachedExited)) {
98 dbgln("Thread logic bug: Found thread state {} while trying to set ExitedDetached state!", expected);
99 VERIFY_NOT_REACHED();
100 }
101 } else {
102 dbgln("Thread logic bug: Found thread state {} while trying to set Exited state!", expected);
103 VERIFY_NOT_REACHED();
104 }
105 }
106
107 return reinterpret_cast<void*>(exit_code);
108 },
109 static_cast<void*>(this));
110
111 VERIFY(rc == 0);
112#ifdef AK_OS_SERENITY
113 if (!m_thread_name.is_empty()) {
114 rc = pthread_setname_np(m_tid, m_thread_name.characters());
115 VERIFY(rc == 0);
116 }
117#endif
118 dbgln("Started {}", *this);
119}
120
121void Thread::detach()
122{
123 auto expected = Threading::ThreadState::Running;
124 // This code might race with the other thread exiting.
125 if (!m_state.compare_exchange_strong(expected, Threading::ThreadState::Detached)) {
126 // Always report a precise error before crashing. These kinds of bugs are hard to reproduce.
127 if (expected == Threading::ThreadState::Exited)
128 dbgln("Thread logic bug: {} is being detached after having exited!", this);
129 else
130 dbgln("Thread logic bug: trying to detach {} which is not in the Started state, but state {}!", this, expected);
131 VERIFY_NOT_REACHED();
132 }
133
134 int rc = pthread_detach(m_tid);
135 VERIFY(rc == 0);
136}
137
138}