2025-07-03 12:53:49 +04:00

542 lines
13 KiB
C++

#include "os/c-api/il2cpp-config-platforms.h"
#include "os/Thread.h"
#if IL2CPP_SUPPORT_THREADS
#include "os/Mutex.h"
#include "os/ThreadLocalValue.h"
#if IL2CPP_THREADS_STD
#include "os/Std/ThreadImpl.h"
#elif IL2CPP_TARGET_WINDOWS
#include "os/Win32/ThreadImpl.h"
#elif IL2CPP_THREADS_PTHREAD
#include "os/Posix/ThreadImpl.h"
#else
#include "os/ThreadImpl.h"
#endif
#include "utils/dynamic_array.h"
#include "Baselib.h"
#include "Cpp/ReentrantLock.h"
#include <limits>
namespace il2cpp
{
namespace os
{
/// TLS variable referring to current thread.
static ThreadLocalValue s_CurrentThread;
// TLS variable referring to whether this thread is currently executing Thread::Shutdown
// It is thread local for thread safety
static ThreadLocalValue s_IsCleaningUpThreads;
struct ThreadContext
{
baselib::ReentrantLock m_AliveThreadsMutex;
il2cpp::utils::dynamic_array<Thread*> m_AliveThreads;
};
ThreadContext* s_ThreadContext = nullptr;
int64_t Thread::s_DefaultAffinityMask = kThreadAffinityAll;
static bool GetIsCleaningUpThreads()
{
void* value = NULL;
s_IsCleaningUpThreads.GetValue(&value);
return reinterpret_cast<intptr_t>(value) != 0;
}
static void SetIsCleaningUpThreads(bool value)
{
s_IsCleaningUpThreads.SetValue(reinterpret_cast<void*>(static_cast<intptr_t>(value)));
}
Thread::Thread()
: m_Thread(new ThreadImpl())
, m_State(kThreadCreated)
, m_ThreadExitedEvent(true) // Manual reset event
, m_CleanupFunc(NULL)
, m_CleanupFuncArg(NULL)
{
FastAutoLock lock(&s_ThreadContext->m_AliveThreadsMutex);
s_ThreadContext->m_AliveThreads.push_back(this);
}
Thread::Thread(ThreadImpl* thread)
: m_Thread(thread)
, m_State(kThreadRunning)
, m_ThreadExitedEvent(true) // Manual reset event
, m_CleanupFunc(NULL)
, m_CleanupFuncArg(NULL)
{
FastAutoLock lock(&s_ThreadContext->m_AliveThreadsMutex);
s_ThreadContext->m_AliveThreads.push_back(this);
}
Thread::~Thread()
{
delete m_Thread;
if (!GetIsCleaningUpThreads())
{
FastAutoLock lock(&s_ThreadContext->m_AliveThreadsMutex);
size_t count = s_ThreadContext->m_AliveThreads.size();
for (size_t i = 0; i < count; i++)
{
if (s_ThreadContext->m_AliveThreads[i] == this)
{
s_ThreadContext->m_AliveThreads.erase_swap_back(&s_ThreadContext->m_AliveThreads[i]);
break;
}
}
}
}
void Thread::Init()
{
il2cpp::os::ThreadImpl::AllocateStaticData();
s_ThreadContext = new ThreadContext();
Thread* thread = GetOrCreateCurrentThread();
if (thread->GetApartment() == kApartmentStateUnknown)
thread->SetApartment(kApartmentStateInMTA);
}
void Thread::Shutdown()
{
Thread* currentThread = GetCurrentThread();
currentThread->SetApartment(kApartmentStateUnknown);
SetIsCleaningUpThreads(true);
{
FastAutoLock lock(&s_ThreadContext->m_AliveThreadsMutex);
size_t count = s_ThreadContext->m_AliveThreads.size();
for (size_t i = 0; i < count; i++)
{
// If this is not the current thread, wait a bit for it to exit. This will avoid an
// infinite wait on shutdown, but it should give the thread enough time to complete its
// use of the os::Thread object before we delete it. Note that we don't call Join here,
// as we want to explicitly do a non-interruptable wait because we are pretty late in
// the shutdown process. The VM thread code should have already caused any running
// threads to get a thread abort exception, meaning that any running OS threads will
// be exiting soon, with no need to check for APCs.
if (s_ThreadContext->m_AliveThreads[i] != currentThread)
{
s_ThreadContext->m_AliveThreads[i]->m_ThreadExitedEvent.Wait(10, false);
delete s_ThreadContext->m_AliveThreads[i];
}
}
// Wait to delete the current thread last, as waiting on an event may need to access the current thread
delete currentThread;
s_ThreadContext->m_AliveThreads.clear();
}
SetIsCleaningUpThreads(false);
#if IL2CPP_ENABLE_RELOAD
s_CurrentThread.SetValue(NULL);
#endif
delete s_ThreadContext;
s_ThreadContext = nullptr;
il2cpp::os::ThreadImpl::FreeStaticData();
}
Thread::ThreadId Thread::Id()
{
return m_Thread->Id();
}
void Thread::SetName(const char* name)
{
m_Thread->SetName(name);
}
void Thread::SetPriority(ThreadPriority priority)
{
m_Thread->SetPriority(priority);
}
ThreadPriority Thread::GetPriority()
{
return m_Thread->GetPriority();
}
void Thread::SetStackSize(size_t stackSize)
{
m_Thread->SetStackSize(stackSize);
}
int Thread::GetMaxStackSize()
{
return ThreadImpl::GetMaxStackSize();
}
struct StartData
{
Thread* thread;
Thread::StartFunc startFunction;
void* startFunctionArgument;
};
/// Wrapper for the user's thread start function. Sets s_CurrentThread.
void Thread::RunWrapper(void* arg)
{
StartData* data = reinterpret_cast<StartData*>(arg);
// Store thread reference.
Thread* thread = data->thread;
const ApartmentState apartment = thread->GetExplicitApartment();
if (apartment != kApartmentStateUnknown)
{
thread->SetExplicitApartment(kApartmentStateUnknown);
thread->SetApartment(apartment);
}
s_CurrentThread.SetValue(thread);
// Get rid of StartData.
StartFunc startFunction = data->startFunction;
void* startFunctionArgument = data->startFunctionArgument;
delete data;
// Make sure thread exit event is not signaled.
thread->m_ThreadExitedEvent.Reset();
// Run user thread start function.
thread->m_State = kThreadRunning;
startFunction(startFunctionArgument);
thread->m_State = kThreadExited;
thread->SetApartment(kApartmentStateUnknown);
CleanupFunc cleanupFunc = thread->m_CleanupFunc;
void* cleanupFuncArg = thread->m_CleanupFuncArg;
// Signal that we've finished execution.
thread->m_ThreadExitedEvent.Set();
if (cleanupFunc)
cleanupFunc(cleanupFuncArg);
}
ErrorCode Thread::Run(StartFunc func, void* arg)
{
IL2CPP_ASSERT(m_State == kThreadCreated || m_State == kThreadExited);
StartData* startData = new StartData;
startData->startFunction = func;
startData->startFunctionArgument = arg;
startData->thread = this;
return m_Thread->Run(RunWrapper, startData, s_DefaultAffinityMask);
}
WaitStatus Thread::Join()
{
IL2CPP_ASSERT(this != GetCurrentThread() && "Trying to join the current thread will deadlock");
return Join(std::numeric_limits<uint32_t>::max());
}
WaitStatus Thread::Join(uint32_t ms)
{
// Wait for thread exit event.
if (m_ThreadExitedEvent.Wait(ms, true) != kWaitStatusSuccess)
return kWaitStatusFailure;
return kWaitStatusSuccess;
}
void Thread::QueueUserAPC(APCFunc func, void* context)
{
m_Thread->QueueUserAPC(func, context);
}
ApartmentState Thread::GetApartment()
{
#if IL2CPP_THREAD_IMPL_HAS_COM_APARTMENTS
return m_Thread->GetApartment();
#else
return kApartmentStateUnknown;
#endif
}
ApartmentState Thread::GetExplicitApartment()
{
#if IL2CPP_THREAD_IMPL_HAS_COM_APARTMENTS
return m_Thread->GetExplicitApartment();
#else
return kApartmentStateUnknown;
#endif
}
ApartmentState Thread::SetApartment(ApartmentState state)
{
#if IL2CPP_THREAD_IMPL_HAS_COM_APARTMENTS
return m_Thread->SetApartment(state);
#else
NO_UNUSED_WARNING(state);
return GetApartment();
#endif
}
void Thread::SetExplicitApartment(ApartmentState state)
{
#if IL2CPP_THREAD_IMPL_HAS_COM_APARTMENTS
m_Thread->SetExplicitApartment(state);
#else
NO_UNUSED_WARNING(state);
#endif
}
void Thread::Sleep(uint32_t milliseconds, bool interruptible)
{
ThreadImpl::Sleep(milliseconds, interruptible);
}
size_t Thread::CurrentThreadId()
{
return ThreadImpl::CurrentThreadId();
}
Thread* Thread::GetCurrentThread()
{
void* value;
s_CurrentThread.GetValue(&value);
IL2CPP_ASSERT(value != NULL);
return reinterpret_cast<Thread*>(value);
}
bool Thread::HasCurrentThread()
{
void* value;
s_CurrentThread.GetValue(&value);
return value != NULL;
}
Thread* Thread::GetOrCreateCurrentThread()
{
Thread* thread = NULL;
s_CurrentThread.GetValue(reinterpret_cast<void**>(&thread));
if (thread)
return thread;
// The os::Thread object is deallocated in the InternalThread::Thread_free_internal icall, which
// is called from the managed thread finalizer.
thread = new Thread(ThreadImpl::CreateForCurrentThread());
s_CurrentThread.SetValue(thread);
return thread;
}
void Thread::DetachCurrentThread()
{
// PTHREAD cleanup isn't deterministic: it could be that our thread local variables get cleaned up before thread clean up routine runs
#if IL2CPP_DEBUG && !IL2CPP_THREADS_PTHREAD
void* value;
s_CurrentThread.GetValue(&value);
IL2CPP_ASSERT(value != NULL);
#endif
s_CurrentThread.SetValue(NULL);
}
bool Thread::YieldInternal()
{
return ThreadImpl::YieldInternal();
}
void Thread::SetDefaultAffinityMask(int64_t affinityMask)
{
s_DefaultAffinityMask = affinityMask;
}
#if IL2CPP_HAS_NATIVE_THREAD_CLEANUP
void Thread::SetNativeThreadCleanup(ThreadCleanupFunc cleanupFunction)
{
ThreadImpl::SetNativeThreadCleanup(cleanupFunction);
}
void Thread::RegisterCurrentThreadForCleanup(void* arg)
{
ThreadImpl::RegisterCurrentThreadForCleanup(arg);
}
void Thread::UnregisterCurrentThreadForCleanup()
{
ThreadImpl::UnregisterCurrentThreadForCleanup();
}
void Thread::SignalExited()
{
m_ThreadExitedEvent.Set();
}
#endif
}
}
#else
#include <limits.h>
namespace il2cpp
{
namespace os
{
int64_t Thread::s_DefaultAffinityMask = -1;
Thread::Thread()
{
}
Thread::~Thread()
{
}
void Thread::Init()
{
}
void Thread::Shutdown()
{
}
Thread::ThreadId Thread::Id()
{
return 0;
}
void Thread::SetName(const char* name)
{
}
void Thread::SetPriority(ThreadPriority priority)
{
}
ThreadPriority Thread::GetPriority()
{
return kThreadPriorityLowest;
}
void Thread::SetStackSize(size_t stackSize)
{
}
int Thread::GetMaxStackSize()
{
return INT_MAX;
}
void Thread::RunWrapper(void* arg)
{
}
ErrorCode Thread::Run(StartFunc func, void* arg)
{
IL2CPP_ASSERT(0 && "Threads are not enabled for this platform.");
return kErrorCodeSuccess;
}
WaitStatus Thread::Join()
{
IL2CPP_ASSERT(0 && "Threads are not enabled for this platform.");
return kWaitStatusSuccess;
}
WaitStatus Thread::Join(uint32_t ms)
{
IL2CPP_ASSERT(0 && "Threads are not enabled for this platform.");
return kWaitStatusSuccess;
}
void Thread::QueueUserAPC(APCFunc func, void* context)
{
}
ApartmentState Thread::GetApartment()
{
return kApartmentStateUnknown;
}
ApartmentState Thread::GetExplicitApartment()
{
return kApartmentStateUnknown;
}
ApartmentState Thread::SetApartment(ApartmentState state)
{
return kApartmentStateUnknown;
}
void Thread::SetExplicitApartment(ApartmentState state)
{
}
void Thread::Sleep(uint32_t milliseconds, bool interruptible)
{
}
size_t Thread::CurrentThreadId()
{
return 0;
}
Thread* Thread::GetCurrentThread()
{
return NULL;
}
Thread* Thread::GetOrCreateCurrentThread()
{
return NULL;
}
void Thread::DetachCurrentThread()
{
}
bool Thread::YieldInternal()
{
return false;
}
void Thread::SetDefaultAffinityMask(int64_t affinityMask)
{
s_DefaultAffinityMask = affinityMask;
}
#if IL2CPP_HAS_NATIVE_THREAD_CLEANUP
void Thread::SetNativeThreadCleanup(ThreadCleanupFunc cleanupFunction)
{
}
void Thread::RegisterCurrentThreadForCleanup(void* arg)
{
}
void Thread::UnregisterCurrentThreadForCleanup()
{
}
void Thread::SignalExited()
{
}
#endif
}
}
#endif