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

959 lines
35 KiB
C++

#include "il2cpp-config.h"
#include "os/Mutex.h"
#include "os/Thread.h"
#include "os/ThreadLocalValue.h"
#include "os/Time.h"
#include "os/Semaphore.h"
#include "vm/Domain.h"
#include "vm/Exception.h"
#include "vm/Object.h"
#include "vm/Profiler.h"
#include "vm/Runtime.h"
#include "vm/StackTrace.h"
#include "vm/Thread.h"
#include "vm/String.h"
#include "gc/Allocator.h"
#include "gc/GarbageCollector.h"
#include "gc/GCHandle.h"
#include "gc/WriteBarrier.h"
#include "utils/Memory.h"
#include "utils/StringUtils.h"
#include "vm-utils/Debugger.h"
#include "il2cpp-class-internals.h"
#include "il2cpp-object-internals.h"
#include <algorithm>
#include "Baselib.h"
#include "Cpp/Atomic.h"
#include "Cpp/ReentrantLock.h"
namespace il2cpp
{
namespace vm
{
Il2CppThread* Thread::s_MainThread = NULL;
typedef std::vector<Il2CppThread*, il2cpp::gc::Allocator<Il2CppThread*> > GCTrackedThreadVector;
// we need to allocate this ourselves so the CRT does not initialize it and try to allocate GC memory on startup before the GC is initialized
static GCTrackedThreadVector* s_AttachedThreads;
static bool s_BlockNewThreads = false;
#define AUTO_LOCK_THREADS() il2cpp::os::FastAutoLock lock(&s_ThreadMutex)
static baselib::ReentrantLock s_ThreadMutex;
static std::vector<int32_t> s_ThreadStaticSizes;
static il2cpp::os::ThreadLocalValue s_CurrentThread;
static il2cpp::os::ThreadLocalValue s_StaticData; // Cache the static thread data in a local TLS slot for faster lookup
static baselib::atomic<int32_t> s_NextManagedThreadId = {0};
/*
Thread static data is stored in a two level lookup so we can grow the size at runtime
without requiring a lock on looking up static data
We pre-allocate a fixed number of slot pointers - kMaxThreadStaticSlots at startup.
Each of these slots can hold kMaxThreadStaticDataPointers data pointer. These slots
are allocated as needed.
*/
const int32_t kMaxThreadStaticSlots = 1024;
const int32_t kMaxThreadStaticDataPointers = 1024;
struct ThreadStaticOffset
{
uint32_t slot;
uint32_t index;
};
static ThreadStaticOffset IndexToStaticFieldOffset(int32_t index)
{
static_assert(kMaxThreadStaticSlots <= 0xFFFF, "Only 65535 base thread static slots are supported");
static_assert(kMaxThreadStaticDataPointers <= 0xFFFF, "Only 65535 thread static slots are supported");
uint32_t value = (uint32_t)index;
ThreadStaticOffset offset;
offset.slot = value >> 16;
offset.index = value & 0xFFFF;
return offset;
}
struct ThreadStaticDataSlot
{
void* data[kMaxThreadStaticSlots];
};
struct ThreadStaticData
{
ThreadStaticDataSlot* slots[kMaxThreadStaticSlots];
};
static void
set_wbarrier_for_attached_threads()
{
gc::GarbageCollector::SetWriteBarrier((void**)s_AttachedThreads->data(), sizeof(Il2CppThread*) * s_AttachedThreads->size());
}
static void
thread_cleanup_on_cancel(void* arg)
{
Thread::Detach((Il2CppThread*)arg, true);
#if IL2CPP_HAS_NATIVE_THREAD_CLEANUP
il2cpp::os::Thread* osThread = ((Il2CppThread*)arg)->GetInternalThread()->handle;
osThread->SignalExited();
#endif
}
void Thread::Initialize()
{
#if IL2CPP_HAS_NATIVE_THREAD_CLEANUP
os::Thread::SetNativeThreadCleanup(&thread_cleanup_on_cancel);
#endif
#if IL2CPP_ENABLE_RELOAD
s_BlockNewThreads = false;
#endif
s_AttachedThreads = new GCTrackedThreadVector();
}
void Thread::Uninitialize()
{
IL2CPP_ASSERT(Current() == Main());
#if IL2CPP_HAS_NATIVE_THREAD_CLEANUP
os::Thread::SetNativeThreadCleanup(NULL);
#endif
delete s_AttachedThreads;
s_AttachedThreads = NULL;
s_MainThread = NULL;
}
Il2CppThread* Thread::Attach(Il2CppDomain *domain)
{
Il2CppThread* managedThread = Current();
if (managedThread != NULL)
return managedThread;
gc::GarbageCollector::RegisterThread();
StackTrace::InitializeStackTracesForCurrentThread();
// Get/create OS thread representing the current thread. For pre-existing threads such as
// the main thread, this will create an OS thread instance on demand. For threads that have
// been started through our OS layer, there will already be an instance.
os::Thread* osThread = os::Thread::GetOrCreateCurrentThread();
// Create managed object representing the current thread.
managedThread = (Il2CppThread*)Object::New(il2cpp_defaults.thread_class);
SetupInternalManagedThread(managedThread, osThread);
managedThread->GetInternalThread()->state = kThreadStateRunning;
InitializeManagedThread(managedThread, domain);
return managedThread;
}
void Thread::SetupInternalManagedThread(Il2CppThread* thread, os::Thread* osThread)
{
Il2CppInternalThread* internalManagedThread = (Il2CppInternalThread*)Object::New(il2cpp_defaults.internal_thread_class);
internalManagedThread->handle = osThread;
internalManagedThread->tid = osThread->Id();
internalManagedThread->managed_id = GetNewManagedId();
// The synch_cs object is deallocated in the InternalThread::Thread_free_internal icall, which
// is called from the managed thread finalizer.
internalManagedThread->longlived = (Il2CppLongLivedThreadData*)IL2CPP_MALLOC(sizeof(Il2CppLongLivedThreadData));
internalManagedThread->longlived->synch_cs = new baselib::ReentrantLock;
internalManagedThread->apartment_state = il2cpp::os::kApartmentStateUnknown;
gc::WriteBarrier::GenericStore(&thread->internal_thread, internalManagedThread);
}
void Thread::InitializeManagedThread(Il2CppThread* thread, Il2CppDomain* domain)
{
#if IL2CPP_SUPPORT_THREADS
IL2CPP_ASSERT(thread->GetInternalThread()->handle != NULL);
IL2CPP_ASSERT(thread->GetInternalThread()->longlived->synch_cs != NULL);
#endif
#if IL2CPP_MONO_DEBUGGER
utils::Debugger::AllocateThreadLocalData();
#endif
s_CurrentThread.SetValue(thread);
Domain::ContextSet(domain->default_context);
Register(thread);
AllocateStaticDataForCurrentThread();
#if IL2CPP_MONO_DEBUGGER
utils::Debugger::ThreadStarted((uintptr_t)thread->GetInternalThread()->tid);
#endif
#if IL2CPP_ENABLE_PROFILER
vm::Profiler::ThreadStart(((unsigned long)thread->GetInternalThread()->tid));
#endif
// Sync thread name.
if (thread->GetInternalThread()->name.chars)
{
std::string utf8Name = il2cpp::utils::StringUtils::Utf16ToUtf8(thread->GetInternalThread()->name.chars);
thread->GetInternalThread()->handle->SetName(utf8Name.c_str());
}
// Sync thread apartment state.
thread->GetInternalThread()->apartment_state = thread->GetInternalThread()->handle->GetApartment();
#if IL2CPP_HAS_NATIVE_THREAD_CLEANUP
// register us for platform specific cleanup attempt in case thread is not exited cleanly
os::Thread::RegisterCurrentThreadForCleanup(thread);
#endif
// If an interrupt has been requested before the thread was started, re-request
// the interrupt now.
if (thread->GetInternalThread()->interruption_requested)
RequestInterrupt(thread);
}
void Thread::UninitializeManagedThread(Il2CppThread* thread)
{
Thread::UninitializeManagedThread(thread, false);
}
void Thread::UninitializeManagedThread(Il2CppThread *thread, bool inNativeThreadCleanup)
{
// This method is only valid to call from the current thread
// But we can't safely check the Current() in native thread shutdown
// because we can't rely on TLS values being valid
IL2CPP_ASSERT(inNativeThreadCleanup || thread == Current());
#if IL2CPP_HAS_NATIVE_THREAD_CLEANUP
// unregister from special cleanup since we are doing it now
os::Thread::UnregisterCurrentThreadForCleanup();
#endif
if (!gc::GarbageCollector::UnregisterThread())
IL2CPP_ASSERT(0 && "gc::GarbageCollector::UnregisterThread failed");
#if IL2CPP_ENABLE_PROFILER
vm::Profiler::ThreadEnd(((unsigned long)thread->GetInternalThread()->tid));
#endif
#if IL2CPP_MONO_DEBUGGER
// Only raise the event for the debugger if there is a current thread at the OS thread level.
// The debugger code will try to take a lock, which requires a current thread. If this
// thread is being detached by a call from thread_cleanup_on_cancel, then there might
// not be a current thread, as pthreads does not privide TLS entries in thread destructors.
if (os::Thread::HasCurrentThread())
utils::Debugger::ThreadStopped((uintptr_t)thread->GetInternalThread()->tid);
#endif
FreeCurrentThreadStaticData(thread, inNativeThreadCleanup);
// Call Unregister after all access to managed objects (Il2CppThread and Il2CppInternalThread)
// is complete. Unregister will remove the managed thread object from the GC tracked vector of
// attached threads, and allow it to be finalized and re-used. If runtime code accesses it
// after a call to Unregister, there will be a race condition between the GC and the runtime
// code for access to that object.
Unregister(thread);
#if IL2CPP_MONO_DEBUGGER
utils::Debugger::FreeThreadLocalData();
#endif
os::Thread::DetachCurrentThread();
s_CurrentThread.SetValue(NULL);
}
Il2CppThread* Thread::Current()
{
void* value = NULL;
s_CurrentThread.GetValue(&value);
return (Il2CppThread*)value;
}
static void STDCALL TerminateThread(void* context)
{
// We throw a dummy exception to make sure things clean up properly
// and we don't leave any locks behind (such as global locks in the allocator which
// would then deadlock other threads). This could work off ThreadAbortException
// but we don't want to deal with a managed exception here. So we use a C++ exception.
throw Thread::NativeThreadAbortException();
}
static bool IsDebuggerThread(os::Thread* thread)
{
#if IL2CPP_MONO_DEBUGGER
return utils::Debugger::IsDebuggerThread(thread);
#else
return false;
#endif
}
// This function requests that all threads exit
// If a thread is in a non-alertable wait it may not have exited when this method exits
void Thread::AbortAllThreads()
{
#if IL2CPP_SUPPORT_THREADS
Il2CppThread* gcFinalizerThread = NULL;
Il2CppThread* currentThread = Current();
IL2CPP_ASSERT(currentThread != NULL && "No current thread!");
s_ThreadMutex.Acquire();
s_BlockNewThreads = true;
GCTrackedThreadVector attachedThreadsCopy = *s_AttachedThreads;
// In theory, we don't need a write barrier here for Boehm, because we keep a
// reference to the object on the stack during it's lifetime. But for validation
// tests, we turn off GC, and thus we need it to pass.
gc::GarbageCollector::SetWriteBarrier((void**)attachedThreadsCopy.data(), sizeof(Il2CppThread*) * attachedThreadsCopy.size());
s_ThreadMutex.Release();
std::vector<os::Thread*> activeThreads;
// Kill all threads but the finalizer and current one. We temporarily flush out
// the entire list and then just put the two threads back.
while (attachedThreadsCopy.size())
{
Il2CppThread* thread = attachedThreadsCopy.back();
os::Thread* osThread = thread->GetInternalThread()->handle;
if (gc::GarbageCollector::IsFinalizerThread(thread))
{
IL2CPP_ASSERT(gcFinalizerThread == NULL && "There seems to be more than one finalizer thread!");
gcFinalizerThread = thread;
}
else if (thread != currentThread && !IsDebuggerThread(osThread))
{
////TODO: use Thread.Abort() instead
osThread->QueueUserAPC(TerminateThread, NULL);
activeThreads.push_back(osThread);
}
attachedThreadsCopy.pop_back();
}
// In theory, we don't need a write barrier here for Boehm, because we keep a
// reference to the object on the stack during it's lifetime. But for validation
// tests, we turn off GC, and thus we need it to pass.
gc::GarbageCollector::SetWriteBarrier((void**)attachedThreadsCopy.data(), sizeof(Il2CppThread*) * attachedThreadsCopy.size());
////FIXME: While we don't have stable thread abortion in place yet, work around problems in
//// the current implementation by repeatedly requesting threads to terminate. This works around
//// race condition to some extent.
while (activeThreads.size())
{
os::Thread* osThread = activeThreads.back();
// Wait for the thread.
if (osThread->Join(10) == kWaitStatusSuccess)
activeThreads.pop_back();
else
{
////TODO: use Thread.Abort() instead
osThread->QueueUserAPC(TerminateThread, NULL);
}
}
AUTO_LOCK_THREADS();
s_AttachedThreads->clear();
// Put finalizer and current thread back in list.
IL2CPP_ASSERT(gcFinalizerThread != NULL && "GC finalizer thread was not found in list of attached threads!");
if (gcFinalizerThread)
s_AttachedThreads->push_back(gcFinalizerThread);
if (currentThread)
s_AttachedThreads->push_back(currentThread);
set_wbarrier_for_attached_threads();
#endif
}
void Thread::Detach(Il2CppThread* thread)
{
Thread::Detach(thread, false);
}
void Thread::Detach(Il2CppThread *thread, bool inNativeThreadCleanup)
{
IL2CPP_ASSERT(thread != NULL && "Cannot detach a NULL thread");
UninitializeManagedThread(thread, inNativeThreadCleanup);
il2cpp::vm::StackTrace::CleanupStackTracesForCurrentThread();
}
Il2CppThread* Thread::Main()
{
return s_MainThread;
}
void Thread::SetMain(Il2CppThread* thread)
{
IL2CPP_ASSERT(s_MainThread == NULL);
s_MainThread = thread;
}
void Thread::SetState(Il2CppThread *thread, ThreadState value)
{
il2cpp::os::FastAutoLock lock(thread->GetInternalThread()->longlived->synch_cs);
thread->GetInternalThread()->state |= value;
}
void Thread::ClrState(Il2CppInternalThread* thread, ThreadState clr)
{
il2cpp::os::FastAutoLock lock(thread->longlived->synch_cs);
thread->state &= ~clr;
}
void Thread::SetState(Il2CppInternalThread *thread, ThreadState value)
{
il2cpp::os::FastAutoLock lock(thread->longlived->synch_cs);
thread->state |= value;
}
ThreadState Thread::GetState(Il2CppInternalThread *thread)
{
il2cpp::os::FastAutoLock lock(thread->longlived->synch_cs);
return (ThreadState)thread->state;
}
bool Thread::TestState(Il2CppInternalThread* thread, ThreadState value)
{
il2cpp::os::FastAutoLock lock(thread->longlived->synch_cs);
return (thread->state & value) != 0;
}
Il2CppInternalThread* Thread::CurrentInternal()
{
return Current()->GetInternalThread();
}
ThreadState Thread::GetState(Il2CppThread *thread)
{
il2cpp::os::FastAutoLock lock(thread->GetInternalThread()->longlived->synch_cs);
return (ThreadState)thread->GetInternalThread()->state;
}
void Thread::ClrState(Il2CppThread* thread, ThreadState state)
{
il2cpp::os::FastAutoLock lock(thread->GetInternalThread()->longlived->synch_cs);
thread->GetInternalThread()->state &= ~state;
}
static void AllocThreadDataSlot(ThreadStaticData* staticData, ThreadStaticOffset offset, int32_t size)
{
if (staticData->slots[offset.slot] == NULL)
staticData->slots[offset.slot] = (ThreadStaticDataSlot*)IL2CPP_CALLOC(1, sizeof(ThreadStaticDataSlot));
if (staticData->slots[offset.slot]->data[offset.index] == NULL)
staticData->slots[offset.slot]->data[offset.index] = gc::GarbageCollector::AllocateFixed(size, NULL);
}
void Thread::AllocateStaticDataForCurrentThread()
{
AUTO_LOCK_THREADS();
int32_t index = 0;
// Alloc the slotData along with the first slots at once
ThreadStaticData* staticData = (ThreadStaticData*)IL2CPP_CALLOC(1, sizeof(ThreadStaticData) + sizeof(ThreadStaticDataSlot));
staticData->slots[0] = (ThreadStaticDataSlot*)(staticData + 1);
Il2CppThread* thread = Current();
IL2CPP_ASSERT(!thread->GetInternalThread()->static_data);
thread->GetInternalThread()->static_data = staticData;
s_StaticData.SetValue(staticData);
for (std::vector<int32_t>::const_iterator iter = s_ThreadStaticSizes.begin(); iter != s_ThreadStaticSizes.end(); ++iter)
{
AllocThreadDataSlot(staticData, IndexToStaticFieldOffset(index), *iter);
index++;
}
}
int32_t Thread::AllocThreadStaticData(int32_t size)
{
AUTO_LOCK_THREADS();
int32_t index = (int32_t)s_ThreadStaticSizes.size();
IL2CPP_ASSERT(index < kMaxThreadStaticSlots * kMaxThreadStaticDataPointers);
if (index >= kMaxThreadStaticSlots * kMaxThreadStaticDataPointers)
il2cpp::vm::Exception::Raise(Exception::GetExecutionEngineException("Out of thread static storage slots"));
s_ThreadStaticSizes.push_back(size);
ThreadStaticOffset offset = IndexToStaticFieldOffset(index);
for (GCTrackedThreadVector::const_iterator iter = s_AttachedThreads->begin(); iter != s_AttachedThreads->end(); ++iter)
{
Il2CppThread* thread = *iter;
ThreadStaticData* staticData = reinterpret_cast<ThreadStaticData*>(thread->GetInternalThread()->static_data);
if (staticData == NULL)
{
// There is a race on staticData for a thread could be NULL here in two cases
// 1. The thread hasn't entered AllocateStaticDataForCurrentThread yet
// 2. The thread has exited FreeCurrentThreadStaticData but hasn't been remove from the s_AttachedThreads yet
// In both cases we can just continue and in 1. the data will be allocated in AllocateStaticDataForCurrentThread
// and in 2. we don't want to allocate anything
continue;
}
AllocThreadDataSlot(staticData, offset, size);
}
return index;
}
void Thread::FreeCurrentThreadStaticData(Il2CppThread *thread, bool inNativeThreadCleanup)
{
// This method is only valid to call from the current thread
// But we can't safely check the Current() in native thread shutdown
// because we can't rely on TLS values being valid
IL2CPP_ASSERT(inNativeThreadCleanup || thread == Current());
AUTO_LOCK_THREADS();
ThreadStaticData* staticData = reinterpret_cast<ThreadStaticData*>(thread->GetInternalThread()->static_data);
thread->GetInternalThread()->static_data = NULL;
s_StaticData.SetValue(NULL);
// This shouldn't happen unless we call this twice, but there's no reason to crash here
IL2CPP_ASSERT(staticData);
if (staticData == NULL)
return;
for (int slot = 0; slot < kMaxThreadStaticSlots; slot++)
{
if (!staticData->slots[slot])
break;
for (int i = 0; i < kMaxThreadStaticDataPointers; i++)
{
if (!staticData->slots[slot]->data[i])
break;
gc::GarbageCollector::FreeFixed(staticData->slots[slot]->data[i]);
}
// Don't free the first slot because we allocate the first slot along with the root slots
if (slot > 0)
IL2CPP_FREE(staticData->slots[slot]);
}
IL2CPP_FREE(staticData);
}
void* Thread::GetThreadStaticData(int32_t offset)
{
// No lock. We allocate static_data once with a fixed size so we can read it
// safely without a lock here.
IL2CPP_ASSERT(offset >= 0 && static_cast<uint32_t>(offset) < s_ThreadStaticSizes.size());
ThreadStaticOffset staticOffset = IndexToStaticFieldOffset(offset);
ThreadStaticData* staticData;
s_StaticData.GetValue((void**)&staticData);
IL2CPP_ASSERT(staticData != NULL);
return staticData->slots[staticOffset.slot]->data[staticOffset.index];
}
void* Thread::GetThreadStaticDataForThread(int32_t offset, Il2CppInternalThread* thread)
{
// No lock. We allocate static_data once with a fixed size so we can read it
// safely without a lock here.
IL2CPP_ASSERT(offset >= 0 && static_cast<uint32_t>(offset) < s_ThreadStaticSizes.size());
IL2CPP_ASSERT(thread->static_data != NULL);
ThreadStaticOffset staticOffset = IndexToStaticFieldOffset(offset);
return reinterpret_cast<ThreadStaticData*>(thread->static_data)->slots[staticOffset.slot]->data[staticOffset.index];
}
void Thread::Register(Il2CppThread *thread)
{
AUTO_LOCK_THREADS();
if (s_BlockNewThreads)
TerminateThread(NULL);
else
{
s_AttachedThreads->push_back(thread);
set_wbarrier_for_attached_threads();
}
}
void Thread::Unregister(Il2CppThread *thread)
{
AUTO_LOCK_THREADS();
GCTrackedThreadVector::iterator it = std::find(s_AttachedThreads->begin(), s_AttachedThreads->end(), thread);
#if IL2CPP_MONO_DEBUGGER
if (it == s_AttachedThreads->end() && thread->internal_thread && il2cpp::utils::Debugger::IsDebuggerThread(thread->internal_thread->handle))
return;
#endif
IL2CPP_ASSERT(it != s_AttachedThreads->end() && "Vm thread not found in list of attached threads.");
s_AttachedThreads->erase(it);
set_wbarrier_for_attached_threads();
}
bool Thread::IsVmThread(Il2CppThread *thread)
{
return !gc::GarbageCollector::IsFinalizerThread(thread);
}
std::string Thread::GetName(Il2CppInternalThread* thread)
{
if (thread->name.chars == NULL)
return std::string();
return utils::StringUtils::Utf16ToUtf8(thread->name.chars);
}
void Thread::SetName(Il2CppThread* thread, Il2CppString* name)
{
il2cpp::os::FastAutoLock lock(thread->GetInternalThread()->longlived->synch_cs);
// Throw if already set.
if (thread->GetInternalThread()->name.length != 0)
il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetInvalidOperationException("Thread name can only be set once."));
// Store name.
thread->GetInternalThread()->name.length = utils::StringUtils::GetLength(name);
thread->GetInternalThread()->name.chars = il2cpp::utils::StringUtils::StringDuplicate(utils::StringUtils::GetChars(name), thread->GetInternalThread()->name.length);
// Hand over to OS layer, if thread has been started already.
if (thread->GetInternalThread()->handle)
{
std::string utf8Name = il2cpp::utils::StringUtils::Utf16ToUtf8(thread->GetInternalThread()->name.chars);
thread->GetInternalThread()->handle->SetName(utf8Name.c_str());
}
}
void Thread::SetName(Il2CppInternalThread* thread, Il2CppString* name)
{
il2cpp::os::FastAutoLock lock(thread->longlived->synch_cs);
// Throw if already set.
if (thread->name.length != 0)
il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetInvalidOperationException("Thread name can only be set once."));
// Store name.
thread->name.length = utils::StringUtils::GetLength(name);
thread->name.chars = il2cpp::utils::StringUtils::StringDuplicate(utils::StringUtils::GetChars(name), thread->name.length);
// Hand over to OS layer, if thread has been started already.
if (thread->handle)
{
std::string utf8Name = il2cpp::utils::StringUtils::Utf16ToUtf8(thread->name.chars);
thread->handle->SetName(utf8Name.c_str());
}
}
static void STDCALL CheckCurrentThreadForInterruptCallback(void* context)
{
Thread::CheckCurrentThreadForInterruptAndThrowIfNecessary();
}
void Thread::RequestInterrupt(Il2CppThread* thread)
{
il2cpp::os::FastAutoLock lock(thread->GetInternalThread()->longlived->synch_cs);
thread->GetInternalThread()->interruption_requested = true;
// If thread has already been started, queue an interrupt now.
il2cpp::os::Thread* osThread = thread->GetInternalThread()->handle;
if (osThread)
osThread->QueueUserAPC(CheckCurrentThreadForInterruptCallback, NULL);
}
void Thread::CheckCurrentThreadForInterruptAndThrowIfNecessary()
{
Il2CppThread* currentThread = il2cpp::vm::Thread::Current();
if (!currentThread)
return;
il2cpp::os::FastAutoLock lock(currentThread->GetInternalThread()->longlived->synch_cs);
// Don't throw if thread is not currently in waiting state or if there's
// no pending interrupt.
if (!currentThread->GetInternalThread()->interruption_requested
|| !(il2cpp::vm::Thread::GetState(currentThread) & il2cpp::vm::kThreadStateWaitSleepJoin))
return;
// Mark the current thread as being unblocked.
currentThread->GetInternalThread()->interruption_requested = false;
il2cpp::vm::Thread::ClrState(currentThread, il2cpp::vm::kThreadStateWaitSleepJoin);
// Throw interrupt exception.
il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetThreadInterruptedException());
}
static void STDCALL CheckCurrentThreadForAbortCallback(void* context)
{
Thread::CheckCurrentThreadForAbortAndThrowIfNecessary();
}
bool Thread::RequestAbort(Il2CppThread* thread)
{
il2cpp::os::FastAutoLock lock(thread->GetInternalThread()->longlived->synch_cs);
ThreadState state = il2cpp::vm::Thread::GetState(thread);
if (state & kThreadStateAbortRequested || state & kThreadStateStopped || state & kThreadStateStopRequested)
return false;
il2cpp::os::Thread* osThread = thread->GetInternalThread()->handle;
if (osThread)
{
// If thread has already been started, queue an abort now.
Thread::SetState(thread, kThreadStateAbortRequested);
osThread->QueueUserAPC(CheckCurrentThreadForAbortCallback, NULL);
}
else
{
// If thread has not started, put it in the aborted state.
Thread::SetState(thread, kThreadStateAborted);
}
return true;
}
bool Thread::RequestAbort(Il2CppInternalThread* thread)
{
il2cpp::os::FastAutoLock lock(thread->longlived->synch_cs);
ThreadState state = il2cpp::vm::Thread::GetState(thread);
if (state & kThreadStateAbortRequested || state & kThreadStateStopped || state & kThreadStateStopRequested)
return false;
il2cpp::os::Thread* osThread = thread->handle;
if (osThread)
{
// If thread has already been started, queue an abort now.
Thread::SetState(thread, kThreadStateAbortRequested);
osThread->QueueUserAPC(CheckCurrentThreadForAbortCallback, NULL);
}
else
{
// If thread has not started, put it in the aborted state.
Thread::SetState(thread, kThreadStateAborted);
}
return true;
}
void Thread::SetPriority(Il2CppThread* thread, int32_t priority)
{
Il2CppInternalThread* internalThread = thread->GetInternalThread();
il2cpp::os::FastAutoLock lock(internalThread->longlived->synch_cs);
internalThread->handle->SetPriority((il2cpp::os::ThreadPriority)priority);
}
int32_t Thread::GetPriority(Il2CppThread* thread)
{
Il2CppInternalThread* internalThread = thread->GetInternalThread();
il2cpp::os::FastAutoLock lock(internalThread->longlived->synch_cs);
return internalThread->handle->GetPriority();
}
struct StartDataInternal
{
Il2CppThread* m_Thread;
Il2CppDomain* m_Domain;
void* m_Delegate;
void* m_StartArg;
il2cpp::os::Semaphore* m_Semaphore;
};
static void ThreadStart(void* arg)
{
StartDataInternal* startData = (StartDataInternal*)arg;
startData->m_Semaphore->Wait();
{
gc::GarbageCollector::RegisterThread();
il2cpp::vm::StackTrace::InitializeStackTracesForCurrentThread();
bool attachSuccessful = false;
try
{
il2cpp::vm::Thread::InitializeManagedThread(startData->m_Thread, startData->m_Domain);
il2cpp::vm::Thread::SetState(startData->m_Thread, kThreadStateRunning);
attachSuccessful = true;
try
{
((void(*)(void*))startData->m_Delegate)(startData->m_StartArg);
}
catch (Il2CppExceptionWrapper& ex)
{
// Only deal with the unhandled exception if the runtime is not
// shutting down. Otherwise, the code to process the unhandled
// exception might fail in unexpected ways, because it needs
// the full runtime available. We've seen this cause crashes
// that are difficult to reproduce locally.
if (!il2cpp::vm::Runtime::IsShuttingDown())
Runtime::UnhandledException(ex.ex);
}
}
catch (il2cpp::vm::Thread::NativeThreadAbortException)
{
// Nothing to do. We've successfully aborted the thread.
il2cpp::vm::Thread::SetState(startData->m_Thread, kThreadStateAborted);
}
il2cpp::vm::Thread::ClrState(startData->m_Thread, kThreadStateRunning);
il2cpp::vm::Thread::SetState(startData->m_Thread, kThreadStateStopped);
if (attachSuccessful)
il2cpp::vm::Thread::UninitializeManagedThread(startData->m_Thread, false);
il2cpp::vm::StackTrace::CleanupStackTracesForCurrentThread();
}
delete startData->m_Semaphore;
gc::GarbageCollector::FreeFixed(startData);
}
Il2CppInternalThread* Thread::CreateInternal(void(*func)(void*), void* arg, bool threadpool_thread, uint32_t stack_size)
{
// The os::Thread object is deallocated in the InternalThread::Thread_free_internal icall, which
// is called from the managed thread finalizer.
os::Thread* osThread = new os::Thread();
Il2CppThread* managedThread = (Il2CppThread*)Object::New(il2cpp_defaults.thread_class);
SetupInternalManagedThread(managedThread, osThread);
Il2CppInternalThread* internalManagedThread = managedThread->GetInternalThread();
internalManagedThread->state = kThreadStateUnstarted;
internalManagedThread->threadpool_thread = threadpool_thread;
// use fixed GC memory since we are storing managed object pointers
StartDataInternal* startData = (StartDataInternal*)gc::GarbageCollector::AllocateFixed(sizeof(StartDataInternal), NULL);
gc::WriteBarrier::GenericStore(&startData->m_Thread, managedThread);
gc::WriteBarrier::GenericStore(&startData->m_Domain, Domain::GetCurrent());
startData->m_Delegate = (void*)func;
startData->m_StartArg = arg;
startData->m_Semaphore = new il2cpp::os::Semaphore(0);
osThread->SetStackSize(stack_size);
osThread->SetExplicitApartment(static_cast<il2cpp::os::ApartmentState>(managedThread->GetInternalThread()->apartment_state));
il2cpp::os::ErrorCode status = osThread->Run(&ThreadStart, startData);
if (status != il2cpp::os::kErrorCodeSuccess)
{
delete osThread;
return NULL;
}
internalManagedThread->state &= ~kThreadStateUnstarted;
startData->m_Semaphore->Post(1, NULL);
return internalManagedThread;
}
void Thread::Stop(Il2CppInternalThread* thread)
{
IL2CPP_ASSERT(thread != CurrentInternal());
if (!RequestAbort(thread))
return;
os::Thread* osThread = thread->handle;
////FIXME: While we don't have stable thread abortion in place yet, work around problems in
//// the current implementation by repeatedly requesting threads to terminate. This works around
//// race condition to some extent.
while (true)
{
// If it's a background thread, request it to kill itself.
if (GetState(thread) & kThreadStateBackground)
{
////TODO: use Thread.Abort() instead
osThread->QueueUserAPC(TerminateThread, NULL);
}
// Wait for the thread.
if (osThread->Join(10) == kWaitStatusSuccess)
break;
}
}
void Thread::Sleep(uint32_t ms)
{
CurrentInternal()->handle->Sleep(ms);
}
bool Thread::YieldInternal()
{
return os::Thread::YieldInternal();
}
void Thread::SetDefaultAffinityMask(int64_t affinityMask)
{
#if defined(IL2CPP_ENABLE_PLATFORM_THREAD_AFFINTY)
os::Thread::SetDefaultAffinityMask(affinityMask);
#endif
}
void Thread::CheckCurrentThreadForAbortAndThrowIfNecessary()
{
Il2CppThread* currentThread = il2cpp::vm::Thread::Current();
if (!currentThread)
return;
il2cpp::os::FastAutoLock lock(currentThread->GetInternalThread()->longlived->synch_cs);
ThreadState state = il2cpp::vm::Thread::GetState(currentThread);
if (!(state & kThreadStateAbortRequested))
return;
// Throw interrupt exception.
Il2CppException* abortException = il2cpp::vm::Exception::GetThreadAbortException();
IL2CPP_OBJECT_SETREF(currentThread->GetInternalThread(), abort_exc, abortException);
il2cpp::vm::Exception::Raise(abortException);
}
void Thread::ResetAbort(Il2CppThread* thread)
{
il2cpp::vm::Thread::ClrState(thread, kThreadStateAbortRequested);
if (thread->GetInternalThread()->abort_exc == NULL)
il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetThreadStateException("Unable to reset abort because no abort was requested."));
}
void Thread::ResetAbort(Il2CppInternalThread* thread)
{
il2cpp::vm::Thread::ClrState(thread, kThreadStateAbortRequested);
if (thread->abort_exc == NULL)
il2cpp::vm::Exception::Raise(il2cpp::vm::Exception::GetThreadStateException("Unable to reset abort because no abort was requested."));
}
void Thread::FullMemoryBarrier()
{
os::Atomic::FullMemoryBarrier();
}
int32_t Thread::GetNewManagedId()
{
return ++s_NextManagedThreadId;
}
uint64_t Thread::GetId(Il2CppThread* thread)
{
return thread->GetInternalThread()->tid;
}
uint64_t Thread::GetId(Il2CppInternalThread* thread)
{
return thread->tid;
}
} /* namespace vm */
} /* namespace il2cpp */