mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 09:12:45 +00:00 
			
		
		
		
	 2fcb713037
			
		
	
	
		2fcb713037
		
	
	
	
	
		
			
			This replaces all state-related variables with a single ThreadState. These are simplified over what the Kernel has, but capture all userspace-available thread state. Locking the state behind an atomic and using proper atomic operations also gets rid of quite some deadlocks and race conditions that have existed around m_tid and others beforehand. In terms of behavior, this introduces the following changes: - All thread state mishandling (e.g. joining a detached thread) crashes the program. Mishandling thread state is a severe kind of concurrency bug that might also be indeterministic, so letting it silently disappear with the return value of pthread_ APIs is a bad idea. The thread state can always be checked beforehand to ensure that no crash happens. - Destructing a still-running thread will crash in AK/Function, so the Thread destructor issues its own warning for debugging purposes. - Thread issues warnings before crashes in many places to aid concurrency debugging (the most difficult kind of debugging). - Joining dead but not detached threads is legal, as per POSIX APIs. - The thread ID is never reset to 0 after the thread has been started and subsequently been assigned a valid thread ID. The thread's exit state is still obtainable. - Detaching threads that are about to exit is considered a programming bug and will often (not always, as we can't catch all execution sequences involved in such a situation) crash the program on purpose. If you want to detach a thread that will definitely exit on its own, you have to prevent it from exiting before detach() was called (e.g. with an "exit requested" flag).
		
			
				
	
	
		
			138 lines
		
	
	
	
		
			4.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			138 lines
		
	
	
	
		
			4.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
 | |
|  * Copyright (c) 2021, Spencer Dixon <spencercdixon@gmail.com>
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-2-Clause
 | |
|  */
 | |
| 
 | |
| #pragma once
 | |
| 
 | |
| #include <AK/Assertions.h>
 | |
| #include <AK/DeprecatedString.h>
 | |
| #include <AK/DistinctNumeric.h>
 | |
| #include <AK/Function.h>
 | |
| #include <AK/Result.h>
 | |
| #include <LibCore/Object.h>
 | |
| #include <pthread.h>
 | |
| 
 | |
| namespace Threading {
 | |
| 
 | |
| AK_TYPEDEF_DISTINCT_ORDERED_ID(intptr_t, ThreadError);
 | |
| 
 | |
| // States of userspace threads are simplified over actual kernel states (and possibly POSIX states).
 | |
| // 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.
 | |
| enum class ThreadState : u8 {
 | |
|     // Thread has been constructed but not started.
 | |
|     // Transitions to Running via start().
 | |
|     Startable,
 | |
|     // Thread has been started, might be running, and can be joined.
 | |
|     // 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.
 | |
|     // Transitions to Detached via detach(), transitions to Exited when the thread finishes its action function.
 | |
|     Running,
 | |
|     // Thread has not been detached and exited, and has to still be joined.
 | |
|     // Transitions to Joined via join().
 | |
|     Exited,
 | |
|     // Thread has been started but also detached, meaning it cannot be joined.
 | |
|     // Transitions to DetachedExited when the thread finishes its action function.
 | |
|     Detached,
 | |
|     // Thread has exited but was detached, meaning it cannot be joined.
 | |
|     DetachedExited,
 | |
|     // Thread has exited and been joined.
 | |
|     Joined,
 | |
| };
 | |
| 
 | |
| class Thread final : public Core::Object {
 | |
|     C_OBJECT(Thread);
 | |
| 
 | |
| public:
 | |
|     virtual ~Thread();
 | |
| 
 | |
|     ErrorOr<void> set_priority(int priority);
 | |
|     ErrorOr<int> get_priority() const;
 | |
| 
 | |
|     // Only callable in the Startable state.
 | |
|     void start();
 | |
|     // Only callable in the Running state.
 | |
|     void detach();
 | |
| 
 | |
|     // Only callable in the Running or Exited states.
 | |
|     template<typename T = void>
 | |
|     Result<T, ThreadError> join();
 | |
| 
 | |
|     DeprecatedString thread_name() const;
 | |
|     pthread_t tid() const;
 | |
|     ThreadState state() const;
 | |
|     bool is_started() const;
 | |
|     bool needs_to_be_joined() const;
 | |
|     bool has_exited() const;
 | |
| 
 | |
| private:
 | |
|     explicit Thread(Function<intptr_t()> action, StringView thread_name = {});
 | |
|     Function<intptr_t()> m_action;
 | |
|     pthread_t m_tid { 0 };
 | |
|     DeprecatedString m_thread_name;
 | |
|     Atomic<ThreadState> m_state { ThreadState::Startable };
 | |
| };
 | |
| 
 | |
| template<typename T>
 | |
| Result<T, ThreadError> Thread::join()
 | |
| {
 | |
|     VERIFY(needs_to_be_joined());
 | |
| 
 | |
|     void* thread_return = nullptr;
 | |
|     int rc = pthread_join(m_tid, &thread_return);
 | |
|     if (rc != 0) {
 | |
|         return ThreadError { rc };
 | |
|     }
 | |
| 
 | |
|     // The other thread has now stopped running, so a TOCTOU bug is not possible.
 | |
|     // (If you call join from two different threads, you're doing something *very* wrong anyways.)
 | |
|     VERIFY(m_state == ThreadState::Exited);
 | |
|     m_state = ThreadState::Joined;
 | |
| 
 | |
|     if constexpr (IsVoid<T>)
 | |
|         return {};
 | |
|     else
 | |
|         return { static_cast<T>(thread_return) };
 | |
| }
 | |
| 
 | |
| }
 | |
| 
 | |
| template<>
 | |
| struct AK::Formatter<Threading::Thread> : AK::Formatter<FormatString> {
 | |
|     ErrorOr<void> format(FormatBuilder& builder, Threading::Thread const& thread)
 | |
|     {
 | |
|         return Formatter<FormatString>::format(builder, "Thread \"{}\"({})"sv, thread.thread_name(), thread.tid());
 | |
|     }
 | |
| };
 | |
| 
 | |
| template<>
 | |
| struct AK::Formatter<Threading::ThreadState> : AK::Formatter<FormatString> {
 | |
|     ErrorOr<void> format(FormatBuilder& builder, Threading::ThreadState state)
 | |
|     {
 | |
|         DeprecatedString name = "";
 | |
|         switch (state) {
 | |
|         case Threading::ThreadState::Detached:
 | |
|             name = "Detached";
 | |
|             break;
 | |
|         case Threading::ThreadState::DetachedExited:
 | |
|             name = "DetachedExited";
 | |
|             break;
 | |
|         case Threading::ThreadState::Exited:
 | |
|             name = "Exited";
 | |
|             break;
 | |
|         case Threading::ThreadState::Joined:
 | |
|             name = "Joined";
 | |
|             break;
 | |
|         case Threading::ThreadState::Running:
 | |
|             name = "Running";
 | |
|             break;
 | |
|         case Threading::ThreadState::Startable:
 | |
|             name = "Startable";
 | |
|             break;
 | |
|         default:
 | |
|             VERIFY_NOT_REACHED();
 | |
|         }
 | |
|         return Formatter<FormatString>::format(builder, "{}"sv, name);
 | |
|     }
 | |
| };
 |