446 lines
17 KiB
C++
446 lines
17 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2020 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#ifndef XENIA_BASE_THREADING_H_
|
|
#define XENIA_BASE_THREADING_H_
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <climits>
|
|
#include <condition_variable>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "xenia/base/assert.h"
|
|
#include "xenia/base/literals.h"
|
|
#include "xenia/base/platform.h"
|
|
|
|
namespace xe {
|
|
namespace threading {
|
|
|
|
using namespace xe::literals;
|
|
|
|
#if XE_PLATFORM_ANDROID
|
|
void AndroidInitialize();
|
|
void AndroidShutdown();
|
|
#endif
|
|
|
|
// This is more like an Event with self-reset when returning from Wait()
|
|
class Fence {
|
|
public:
|
|
Fence() : signal_state_(0) {}
|
|
|
|
void Signal() {
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
signal_state_ |= SIGMASK_;
|
|
cond_.notify_all();
|
|
}
|
|
|
|
// Wait for the Fence to be signaled. Clears the signal on return.
|
|
void Wait() {
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
assert_true((signal_state_ & ~SIGMASK_) < (SIGMASK_ - 1) &&
|
|
"Too many threads?");
|
|
|
|
// keep local copy to minimize loads
|
|
auto signal_state = ++signal_state_;
|
|
for (; !(signal_state & SIGMASK_); signal_state = signal_state_) {
|
|
cond_.wait(lock);
|
|
}
|
|
|
|
// We can't just clear the signal as other threads may not have read it yet
|
|
assert_true((signal_state & ~SIGMASK_) > 0); // wait_count > 0
|
|
if (signal_state == (1 | SIGMASK_)) { // wait_count == 1
|
|
// Last one out turn off the lights
|
|
signal_state_ = 0;
|
|
} else {
|
|
// Oops, another thread is still waiting, set the new count and keep the
|
|
// signal.
|
|
signal_state_ = --signal_state;
|
|
}
|
|
}
|
|
|
|
private:
|
|
using state_t_ = uint_fast32_t;
|
|
static constexpr state_t_ SIGMASK_ = state_t_(1)
|
|
<< (sizeof(state_t_) * 8 - 1);
|
|
|
|
std::mutex mutex_;
|
|
std::condition_variable cond_;
|
|
// Use the highest bit (sign bit) as the signal flag and the rest to count
|
|
// waiting threads.
|
|
volatile state_t_ signal_state_;
|
|
};
|
|
|
|
// Returns the total number of logical processors in the host system.
|
|
uint32_t logical_processor_count();
|
|
|
|
// Enables the current process to set thread affinity.
|
|
// Must be called at startup before attempting to set thread affinity.
|
|
void EnableAffinityConfiguration();
|
|
|
|
// Gets a stable thread-specific ID, but may not be. Use for informative
|
|
// purposes only.
|
|
uint32_t current_thread_system_id();
|
|
|
|
// Gets a stable thread-specific ID that defaults to the same value as
|
|
// current_thread_system_id but may be overridden.
|
|
// Guest threads often change this to the guest thread handle.
|
|
uint32_t current_thread_id();
|
|
void set_current_thread_id(uint32_t id);
|
|
|
|
// Sets the current thread name.
|
|
void set_name(const std::string_view name);
|
|
|
|
// Yields the current thread to the scheduler. Maybe.
|
|
void MaybeYield();
|
|
|
|
// Memory barrier (request - may be ignored).
|
|
void SyncMemory();
|
|
|
|
// Sleeps the current thread for at least as long as the given duration.
|
|
void Sleep(std::chrono::microseconds duration);
|
|
template <typename Rep, typename Period>
|
|
void Sleep(std::chrono::duration<Rep, Period> duration) {
|
|
Sleep(std::chrono::duration_cast<std::chrono::microseconds>(duration));
|
|
}
|
|
|
|
enum class SleepResult {
|
|
kSuccess,
|
|
kAlerted,
|
|
};
|
|
// Sleeps the current thread for at least as long as the given duration.
|
|
// The thread is put in an alertable state and may wake to dispatch user
|
|
// callbacks. If this happens the sleep returns early with
|
|
// SleepResult::kAlerted.
|
|
SleepResult AlertableSleep(std::chrono::microseconds duration);
|
|
template <typename Rep, typename Period>
|
|
SleepResult AlertableSleep(std::chrono::duration<Rep, Period> duration) {
|
|
return AlertableSleep(
|
|
std::chrono::duration_cast<std::chrono::microseconds>(duration));
|
|
}
|
|
|
|
typedef uint32_t TlsHandle;
|
|
constexpr TlsHandle kInvalidTlsHandle = UINT_MAX;
|
|
|
|
TlsHandle AllocateTlsHandle();
|
|
bool FreeTlsHandle(TlsHandle handle);
|
|
uintptr_t GetTlsValue(TlsHandle handle);
|
|
bool SetTlsValue(TlsHandle handle, uintptr_t value);
|
|
|
|
// A high-resolution timer capable of firing at millisecond-precision.
|
|
// All timers created in this way are executed in the same thread so
|
|
// callbacks must be kept short or else all timers will be impacted.
|
|
class HighResolutionTimer {
|
|
public:
|
|
virtual ~HighResolutionTimer() = default;
|
|
|
|
// Creates a new repeating timer with the given period.
|
|
// The given function will be called back as close to the given period as
|
|
// possible.
|
|
static std::unique_ptr<HighResolutionTimer> CreateRepeating(
|
|
std::chrono::milliseconds period, std::function<void()> callback);
|
|
};
|
|
|
|
// Results for a WaitHandle operation.
|
|
enum class WaitResult {
|
|
// The state of the specified object is signaled.
|
|
// In a WaitAny the tuple will contain the index of the wait handle that
|
|
// caused the wait to be satisfied.
|
|
kSuccess,
|
|
// The wait was ended by one or more user-mode callbacks queued to the thread.
|
|
// This will occur when is_alertable is set true.
|
|
kUserCallback,
|
|
// The time-out interval elapsed, and the object's state is nonsignaled.
|
|
kTimeout,
|
|
// The specified object is a mutex object that was not released by the thread
|
|
// that owned the mutex object before the owning thread terminated. Ownership
|
|
// of the mutex object is granted to the calling thread and the mutex is set
|
|
// to nonsignaled.
|
|
// In a WaitAny the tuple will contain the index of the wait handle that
|
|
// caused the wait to be abandoned.
|
|
kAbandoned,
|
|
// The function has failed.
|
|
kFailed,
|
|
};
|
|
|
|
class WaitHandle {
|
|
public:
|
|
virtual ~WaitHandle() = default;
|
|
|
|
// Returns the native handle of the object on the host system.
|
|
// This value is platform specific.
|
|
virtual void* native_handle() const = 0;
|
|
|
|
protected:
|
|
WaitHandle() = default;
|
|
};
|
|
|
|
// Waits until the wait handle is in the signaled state, an alert triggers and
|
|
// a user callback is queued to the thread, or the timeout interval elapses.
|
|
// If timeout is zero the call will return immediately instead of waiting and
|
|
// if the timeout is max() the wait will not time out.
|
|
WaitResult Wait(
|
|
WaitHandle* wait_handle, bool is_alertable,
|
|
std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
|
|
|
|
// Signals one object and waits on another object as a single operation.
|
|
// Waits until the wait handle is in the signaled state, an alert triggers and
|
|
// a user callback is queued to the thread, or the timeout interval elapses.
|
|
// If timeout is zero the call will return immediately instead of waiting and
|
|
// if the timeout is max() the wait will not time out.
|
|
WaitResult SignalAndWait(
|
|
WaitHandle* wait_handle_to_signal, WaitHandle* wait_handle_to_wait_on,
|
|
bool is_alertable,
|
|
std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
|
|
|
|
std::pair<WaitResult, size_t> WaitMultiple(
|
|
WaitHandle* wait_handles[], size_t wait_handle_count, bool wait_all,
|
|
bool is_alertable,
|
|
std::chrono::milliseconds timeout = std::chrono::milliseconds::max());
|
|
|
|
// Waits until all of the specified objects are in the signaled state, a
|
|
// user callback is queued to the thread, or the time-out interval elapses.
|
|
// If timeout is zero the call will return immediately instead of waiting and
|
|
// if the timeout is max() the wait will not time out.
|
|
inline WaitResult WaitAll(
|
|
WaitHandle* wait_handles[], size_t wait_handle_count, bool is_alertable,
|
|
std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) {
|
|
return WaitMultiple(wait_handles, wait_handle_count, true, is_alertable,
|
|
timeout)
|
|
.first;
|
|
}
|
|
inline WaitResult WaitAll(
|
|
std::vector<WaitHandle*> wait_handles, bool is_alertable,
|
|
std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) {
|
|
return WaitAll(wait_handles.data(), wait_handles.size(), is_alertable,
|
|
timeout);
|
|
}
|
|
|
|
// Waits until any of the specified objects are in the signaled state, a
|
|
// user callback is queued to the thread, or the time-out interval elapses.
|
|
// If timeout is zero the call will return immediately instead of waiting and
|
|
// if the timeout is max() the wait will not time out.
|
|
// The second argument of the return tuple indicates which wait handle caused
|
|
// the wait to be satisfied or abandoned.
|
|
inline std::pair<WaitResult, size_t> WaitAny(
|
|
WaitHandle* wait_handles[], size_t wait_handle_count, bool is_alertable,
|
|
std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) {
|
|
return WaitMultiple(wait_handles, wait_handle_count, false, is_alertable,
|
|
timeout);
|
|
}
|
|
inline std::pair<WaitResult, size_t> WaitAny(
|
|
std::vector<WaitHandle*> wait_handles, bool is_alertable,
|
|
std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) {
|
|
return WaitAny(wait_handles.data(), wait_handles.size(), is_alertable,
|
|
timeout);
|
|
}
|
|
|
|
// Models a Win32-like event object.
|
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspx
|
|
class Event : public WaitHandle {
|
|
public:
|
|
// Creates a manual-reset event object, which requires the use of the
|
|
// Reset() function to set the event state to nonsignaled.
|
|
// If initial_state is true the event will start in the signaled state.
|
|
static std::unique_ptr<Event> CreateManualResetEvent(bool initial_state);
|
|
|
|
// Creates an auto-reset event object, and system automatically resets the
|
|
// event state to nonsignaled after a single waiting thread has been released.
|
|
// If initial_state is true the event will start in the signaled state.
|
|
static std::unique_ptr<Event> CreateAutoResetEvent(bool initial_state);
|
|
|
|
// Sets the specified event object to the signaled state.
|
|
// If this is a manual reset event the event stays signaled until Reset() is
|
|
// called. If this is an auto reset event until exactly one wait is satisfied.
|
|
virtual void Set() = 0;
|
|
|
|
// Sets the specified event object to the nonsignaled state.
|
|
// Resetting an event that is already reset has no effect.
|
|
virtual void Reset() = 0;
|
|
|
|
// Sets the specified event object to the signaled state and then resets it to
|
|
// the nonsignaled state after releasing the appropriate number of waiting
|
|
// threads.
|
|
virtual void Pulse() = 0;
|
|
};
|
|
|
|
// Models a Win32-like semaphore object.
|
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682438(v=vs.85).aspx
|
|
class Semaphore : public WaitHandle {
|
|
public:
|
|
// Creates a new semaphore object.
|
|
// The initial_count must be greater than or equal to zero and less than or
|
|
// equal to maximum_count. The state of a semaphore is signaled when its count
|
|
// is greater than zero and nonsignaled when it is zero. The count is
|
|
// decreased by one whenever a wait function releases a thread that was
|
|
// waiting for the semaphore. The count is increased by a specified amount by
|
|
// calling the Release() function.
|
|
static std::unique_ptr<Semaphore> Create(int initial_count,
|
|
int maximum_count);
|
|
|
|
// Increases the count of the specified semaphore object by a specified
|
|
// amount.
|
|
// release_count must be greater than zero.
|
|
// Returns false if adding release_count would set the semaphore over the
|
|
// initially specified maximum_count.
|
|
virtual bool Release(int release_count, int* out_previous_count) = 0;
|
|
};
|
|
|
|
// Models a Win32-like mutant (mutex) object.
|
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682411(v=vs.85).aspx
|
|
class Mutant : public WaitHandle {
|
|
public:
|
|
// Creates a new mutant object, initially owned by the calling thread if
|
|
// specified.
|
|
static std::unique_ptr<Mutant> Create(bool initial_owner);
|
|
|
|
// Releases ownership of the specified mutex object.
|
|
// Returns false if the calling thread does not own the mutant object.
|
|
virtual bool Release() = 0;
|
|
};
|
|
|
|
// Models a Win32-like timer object.
|
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms687012(v=vs.85).aspx
|
|
class Timer : public WaitHandle {
|
|
public:
|
|
// Creates a timer whose state remains signaled until SetOnce() or
|
|
// SetRepeating() is called to establish a new due time.
|
|
static std::unique_ptr<Timer> CreateManualResetTimer();
|
|
|
|
// Creates a timer whose state remains signaled until a thread completes a
|
|
// wait operation on the timer object.
|
|
static std::unique_ptr<Timer> CreateSynchronizationTimer();
|
|
|
|
// Activates the specified waitable timer. When the due time arrives, the
|
|
// timer is signaled and the thread that set the timer calls the optional
|
|
// completion routine.
|
|
// Returns true on success.
|
|
virtual bool SetOnce(std::chrono::nanoseconds due_time,
|
|
std::function<void()> opt_callback = nullptr) = 0;
|
|
|
|
// Activates the specified waitable timer. When the due time arrives, the
|
|
// timer is signaled and the thread that set the timer calls the optional
|
|
// completion routine. A periodic timer automatically reactivates each time
|
|
// the period elapses, until the timer is canceled or reset.
|
|
// Returns true on success.
|
|
virtual bool SetRepeating(std::chrono::nanoseconds due_time,
|
|
std::chrono::milliseconds period,
|
|
std::function<void()> opt_callback = nullptr) = 0;
|
|
template <typename Rep, typename Period>
|
|
bool SetRepeating(std::chrono::nanoseconds due_time,
|
|
std::chrono::duration<Rep, Period> period,
|
|
std::function<void()> opt_callback = nullptr) {
|
|
return SetRepeating(
|
|
due_time, std::chrono::duration_cast<std::chrono::milliseconds>(period),
|
|
std::move(opt_callback));
|
|
}
|
|
|
|
// Stops the timer before it can be set to the signaled state and cancels
|
|
// outstanding callbacks. Threads performing a wait operation on the timer
|
|
// remain waiting until they time out or the timer is reactivated and its
|
|
// state is set to signaled. If the timer is already in the signaled state, it
|
|
// remains in that state.
|
|
// Returns true on success.
|
|
virtual bool Cancel() = 0;
|
|
};
|
|
|
|
struct ThreadPriority {
|
|
static const int32_t kLowest = -2;
|
|
static const int32_t kBelowNormal = -1;
|
|
static const int32_t kNormal = 0;
|
|
static const int32_t kAboveNormal = 1;
|
|
static const int32_t kHighest = 2;
|
|
};
|
|
|
|
// Models a Win32-like thread object.
|
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms682453(v=vs.85).aspx
|
|
class Thread : public WaitHandle {
|
|
public:
|
|
struct CreationParameters {
|
|
size_t stack_size = 4_MiB;
|
|
bool create_suspended = false;
|
|
int32_t initial_priority = 0;
|
|
};
|
|
|
|
// Creates a thread with the given parameters and calls the start routine from
|
|
// within that thread.
|
|
static std::unique_ptr<Thread> Create(CreationParameters params,
|
|
std::function<void()> start_routine);
|
|
static Thread* GetCurrentThread();
|
|
|
|
// Ends the calling thread.
|
|
// No destructors are called, and this function does not return.
|
|
// The state of the thread object becomes signaled, releasing any other
|
|
// threads that had been waiting for the thread to terminate.
|
|
static void Exit(int exit_code);
|
|
|
|
// Returns the ID of the thread.
|
|
virtual uint32_t system_id() const = 0;
|
|
|
|
// Returns the current name of the thread, if previously specified.
|
|
const std::string& name() const { return name_; }
|
|
|
|
// Sets the name of the thread, used in debugging and logging.
|
|
virtual void set_name(std::string name) { name_ = std::move(name); }
|
|
|
|
// Returns the current priority value for the thread.
|
|
virtual int32_t priority() = 0;
|
|
|
|
// Sets the priority value for the thread. This value, together with the
|
|
// priority class of the thread's process, determines the thread's base
|
|
// priority level. ThreadPriority contains useful constants.
|
|
virtual void set_priority(int32_t new_priority) = 0;
|
|
|
|
// Returns the current processor affinity mask for the thread.
|
|
virtual uint64_t affinity_mask() = 0;
|
|
|
|
// Sets a processor affinity mask for the thread.
|
|
// A thread affinity mask is a bit vector in which each bit represents a
|
|
// logical processor that a thread is allowed to run on. A thread affinity
|
|
// mask must be a subset of the process affinity mask for the containing
|
|
// process of a thread.
|
|
virtual void set_affinity_mask(uint64_t new_affinity_mask) = 0;
|
|
|
|
// Adds a user-mode asynchronous procedure call request to the thread queue.
|
|
// When a user-mode APC is queued, the thread is not directed to call the APC
|
|
// function unless it is in an alertable state. After the thread is in an
|
|
// alertable state, the thread handles all pending APCs in first in, first out
|
|
// (FIFO) order, and the wait operation returns WaitResult::kUserCallback.
|
|
virtual void QueueUserCallback(std::function<void()> callback) = 0;
|
|
|
|
// Decrements a thread's suspend count. When the suspend count is decremented
|
|
// to zero, the execution of the thread is resumed.
|
|
virtual bool Resume(uint32_t* out_previous_suspend_count = nullptr) = 0;
|
|
|
|
// Suspends the specified thread.
|
|
virtual bool Suspend(uint32_t* out_previous_suspend_count = nullptr) = 0;
|
|
|
|
// Terminates the thread.
|
|
// No destructors are called, and this function does not return.
|
|
// The state of the thread object becomes signaled, releasing any other
|
|
// threads that had been waiting for the thread to terminate.
|
|
virtual void Terminate(int exit_code) = 0;
|
|
|
|
protected:
|
|
std::string name_;
|
|
};
|
|
|
|
} // namespace threading
|
|
} // namespace xe
|
|
|
|
#endif // XENIA_BASE_THREADING_H_
|