123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964 |
- #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;
- }
- Il2CppThread** Thread::GetAllAttachedThreads(size_t &size)
- {
- size = s_AttachedThreads->size();
- return &(*s_AttachedThreads)[0];
- }
- 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 */
|