#include "il2cpp-config.h" #if !IL2CPP_THREADS_STD && IL2CPP_THREADS_PTHREAD #include #include #include #include #include #include #if IL2CPP_TARGET_LINUX #include #include #endif #include "ThreadImpl.h" #include "PosixHelpers.h" namespace il2cpp { namespace os { #define ASSERT_CALLED_ON_CURRENT_THREAD \ IL2CPP_ASSERT(pthread_equal (pthread_self (), m_Handle) && "Must be called on current thread!"); static Event* s_ThreadSleepObject = nullptr; void ThreadImpl::AllocateStaticData() { s_ThreadSleepObject = new Event(); } void ThreadImpl::FreeStaticData() { delete s_ThreadSleepObject; s_ThreadSleepObject = nullptr; } ThreadImpl::ThreadImpl() : m_Handle(0) , m_StartFunc(NULL) , m_StartArg(NULL) , m_CurrentWaitObject(NULL) , m_StackSize(IL2CPP_DEFAULT_STACK_SIZE) , m_ConditionSemaphore(1) { } ThreadImpl::~ThreadImpl() { } ErrorCode ThreadImpl::Run(Thread::StartFunc func, void* arg, int64_t affinityMask) { // Store state for run wrapper. m_StartFunc = func; m_StartArg = arg; // Initialize thread attributes. pthread_attr_t attr; int s = pthread_attr_init(&attr); if (s) return kErrorCodeGenFailure; #if defined(IL2CPP_ENABLE_PLATFORM_THREAD_AFFINTY) #if IL2CPP_THREAD_HAS_CPU_SET if (affinityMask != Thread::kThreadAffinityAll) { cpu_set_t cpuset; CPU_ZERO(&cpuset); for (int i = 0; i < 64; ++i) { if (affinityMask & (1 << i)) CPU_SET(i, &cpuset); } pthread_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset); } else { // set create default core affinity pthread_attr_setaffinity_np(&attr, 0, NULL); } #else pthread_attr_setaffinity_np(&attr, 0, NULL); #endif // IL2CPP_THREAD_HAS_CPU_SET #endif // defined(IL2CPP_ENABLE_PLATFORM_THREAD_AFFINTY) #if defined(IL2CPP_ENABLE_PLATFORM_THREAD_STACKSIZE) pthread_attr_setstacksize(&attr, m_StackSize); #endif // Create thread. pthread_t threadId; s = pthread_create(&threadId, &attr, &ThreadStartWrapper, this); if (s) return kErrorCodeGenFailure; // Destroy thread attributes. s = pthread_attr_destroy(&attr); if (s) return kErrorCodeGenFailure; // We're up and running. m_Handle = threadId; return kErrorCodeSuccess; } void* ThreadImpl::ThreadStartWrapper(void* arg) { ThreadImpl* thread = reinterpret_cast(arg); // Also set handle here. No matter which thread proceeds first, // we need to make sure the handle is set. thread->m_Handle = pthread_self(); // Detach this thread since we will manage calling Join at the VM level // if necessary. Detaching it also prevents use from running out of thread // handles for background threads that are never joined. int returnValue = pthread_detach(thread->m_Handle); IL2CPP_ASSERT(returnValue == 0); (void)returnValue; // Run user code. thread->m_StartFunc(thread->m_StartArg); return 0; } uint64_t ThreadImpl::Id() { return posix::PosixThreadIdToThreadId(m_Handle); } void ThreadImpl::SetName(const char* name) { // Can only be set on current thread on OSX and Linux. if (pthread_self() != m_Handle) return; #if IL2CPP_TARGET_DARWIN pthread_setname_np(name); #elif IL2CPP_TARGET_LINUX || IL2CPP_TARGET_ANDROID || IL2CPP_ENABLE_PLATFORM_THREAD_RENAME if (pthread_setname_np(m_Handle, name) == ERANGE) { char buf[16]; // TASK_COMM_LEN=16 strncpy(buf, name, sizeof(buf)); buf[sizeof(buf) - 1] = '\0'; pthread_setname_np(m_Handle, buf); } #endif } void ThreadImpl::SetStackSize(size_t newsize) { // if newsize is zero we use the per-platform default value for size of stack if (newsize == 0) { newsize = IL2CPP_DEFAULT_STACK_SIZE; } m_StackSize = newsize; } int ThreadImpl::GetMaxStackSize() { #if IL2CPP_TARGET_DARWIN || IL2CPP_TARGET_LINUX struct rlimit lim; /* If getrlimit fails, we don't enforce any limits. */ if (getrlimit(RLIMIT_STACK, &lim)) return INT_MAX; /* rlim_t is an unsigned long long on 64bits OSX but we want an int response. */ if (lim.rlim_max > (rlim_t)INT_MAX) return INT_MAX; return (int)lim.rlim_max; #else return INT_MAX; #endif } void ThreadImpl::SetPriority(ThreadPriority priority) { ////TODO } ThreadPriority ThreadImpl::GetPriority() { /// TODO return kThreadPriorityNormal; } void ThreadImpl::QueueUserAPC(Thread::APCFunc function, void* context) { IL2CPP_ASSERT(function != NULL); // Put on queue. { m_PendingAPCsMutex.Acquire(); m_PendingAPCs.push_back(APCRequest(function, context)); m_PendingAPCsMutex.Release(); } // Interrupt an ongoing wait, only interrupt if we have an object waiting if (m_CurrentWaitObject.load()) { m_ConditionSemaphore.Release(1); } } void ThreadImpl::CheckForUserAPCAndHandle() { ASSERT_CALLED_ON_CURRENT_THREAD; m_PendingAPCsMutex.Acquire(); while (!m_PendingAPCs.empty()) { APCRequest apcRequest = m_PendingAPCs.front(); // Remove from list. Do before calling the function to make sure the list // is up to date in case the function throws. m_PendingAPCs.erase(m_PendingAPCs.begin()); // Release mutex while we call the function so that we don't deadlock // if the function starts waiting on a thread that tries queuing an APC // on us. m_PendingAPCsMutex.Release(); // Call function. apcRequest.callback(apcRequest.context); // Re-acquire mutex. m_PendingAPCsMutex.Acquire(); } m_PendingAPCsMutex.Release(); } void ThreadImpl::SetWaitObject(WaitObject* waitObject) { // Cannot set wait objects on threads other than the current thread. ASSERT_CALLED_ON_CURRENT_THREAD; // This is an unprotected write as write acccess is restricted to the // current thread. m_CurrentWaitObject = waitObject; } void ThreadImpl::Sleep(uint32_t milliseconds, bool interruptible) { /// An Event that we never signal. This is used for sleeping threads in an alertable state. They /// simply wait on this object with the sleep timer as the timeout. This way we don't need a separate /// codepath for implementing sleep logic. s_ThreadSleepObject->Wait(milliseconds, interruptible); } uint64_t ThreadImpl::CurrentThreadId() { return posix::PosixThreadIdToThreadId(pthread_self()); } ThreadImpl* ThreadImpl::GetCurrentThread() { return Thread::GetCurrentThread()->m_Thread; } ThreadImpl* ThreadImpl::CreateForCurrentThread() { ThreadImpl* thread = new ThreadImpl(); thread->m_Handle = pthread_self(); return thread; } bool ThreadImpl::YieldInternal() { return sched_yield() == 0; } #if IL2CPP_HAS_NATIVE_THREAD_CLEANUP static pthread_key_t s_CleanupKey; static Thread::ThreadCleanupFunc s_CleanupFunc; static void CleanupThreadIfCanceled(void* arg) { Thread::ThreadCleanupFunc cleanupFunc = s_CleanupFunc; if (cleanupFunc) cleanupFunc(arg); } void ThreadImpl::SetNativeThreadCleanup(Thread::ThreadCleanupFunc cleanupFunction) { if (cleanupFunction) { IL2CPP_ASSERT(!s_CleanupFunc); s_CleanupFunc = cleanupFunction; int result = pthread_key_create(&s_CleanupKey, &CleanupThreadIfCanceled); IL2CPP_ASSERT(!result); NO_UNUSED_WARNING(result); } else { IL2CPP_ASSERT(s_CleanupFunc); int result = pthread_key_delete(s_CleanupKey); IL2CPP_ASSERT(!result); NO_UNUSED_WARNING(result); s_CleanupFunc = NULL; } } void ThreadImpl::RegisterCurrentThreadForCleanup(void* arg) { IL2CPP_ASSERT(s_CleanupFunc); pthread_setspecific(s_CleanupKey, arg); } void ThreadImpl::UnregisterCurrentThreadForCleanup() { IL2CPP_ASSERT(s_CleanupFunc); void* data = pthread_getspecific(s_CleanupKey); if (data != NULL) pthread_setspecific(s_CleanupKey, NULL); } #endif } } #endif