/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "primpl.h" #include /* for _beginthreadex() */ /* --- globals ------------------------------------------------ */ PRLock *_pr_schedLock = NULL; _PRInterruptTable _pr_interruptTable[] = { { 0 } }; BOOL _pr_use_static_tls = TRUE; __declspec(thread) PRThread *_pr_current_fiber; __declspec(thread) PRThread *_pr_fiber_last_run; __declspec(thread) _PRCPU *_pr_current_cpu; __declspec(thread) PRUintn _pr_ints_off; DWORD _pr_currentFiberIndex; DWORD _pr_lastFiberIndex; DWORD _pr_currentCPUIndex; DWORD _pr_intsOffIndex; _MDLock _nt_idleLock; PRCList _nt_idleList; PRUint32 _nt_idleCount; extern __declspec(thread) PRThread *_pr_io_restarted_io; extern DWORD _pr_io_restartedIOIndex; typedef HRESULT (WINAPI *SETTHREADDESCRIPTION)(HANDLE, PCWSTR); static SETTHREADDESCRIPTION sSetThreadDescription = NULL; /* Must check the restarted_io *before* decrementing no_sched to 0 */ #define POST_SWITCH_WORK() \ PR_BEGIN_MACRO \ PRThread *restarted_io = \ (_pr_use_static_tls ? _pr_io_restarted_io \ : (PRThread *) TlsGetValue(_pr_io_restartedIOIndex)); \ if (restarted_io) { \ _nt_handle_restarted_io(restarted_io); \ } \ _PR_MD_LAST_THREAD()->no_sched = 0; \ PR_END_MACRO void _nt_handle_restarted_io(PRThread *restarted_io) { /* After the switch we can resume an IO if needed. * XXXMB - this needs to be done in create thread, since that could * be the result for a context switch too.. */ PR_ASSERT(restarted_io->io_suspended == PR_TRUE); PR_ASSERT(restarted_io->md.thr_bound_cpu == restarted_io->cpu); _PR_THREAD_LOCK(restarted_io); if (restarted_io->io_pending == PR_FALSE) { /* The IO already completed, put us back on the runq. */ int pri = restarted_io->priority; restarted_io->state = _PR_RUNNABLE; _PR_RUNQ_LOCK(restarted_io->cpu); _PR_ADD_RUNQ(restarted_io, restarted_io->cpu, pri); _PR_RUNQ_UNLOCK(restarted_io->cpu); } else { _PR_SLEEPQ_LOCK(restarted_io->cpu); _PR_ADD_SLEEPQ(restarted_io, restarted_io->sleep); _PR_SLEEPQ_UNLOCK(restarted_io->cpu); } restarted_io->io_suspended = PR_FALSE; restarted_io->md.thr_bound_cpu = NULL; _PR_THREAD_UNLOCK(restarted_io); if (_pr_use_static_tls) { _pr_io_restarted_io = NULL; } else { TlsSetValue(_pr_io_restartedIOIndex, NULL); } } void _PR_MD_EARLY_INIT() { HMODULE hModule; _MD_NEW_LOCK( &_nt_idleLock ); _nt_idleCount = 0; PR_INIT_CLIST(&_nt_idleList); #if 0 /* Make the clock tick at least once per millisecond */ if ( timeBeginPeriod(1) == TIMERR_NOCANDO) { /* deep yoghurt; clock doesn't tick fast enough! */ PR_ASSERT(0); } #endif if (!_pr_use_static_tls) { _pr_currentFiberIndex = TlsAlloc(); _pr_lastFiberIndex = TlsAlloc(); _pr_currentCPUIndex = TlsAlloc(); _pr_intsOffIndex = TlsAlloc(); _pr_io_restartedIOIndex = TlsAlloc(); } // SetThreadDescription is Windows 10 build 1607+ hModule = GetModuleHandleW(L"kernel32.dll"); if (hModule) { sSetThreadDescription = (SETTHREADDESCRIPTION) GetProcAddress( hModule, "SetThreadDescription"); } } void _PR_MD_CLEANUP_BEFORE_EXIT(void) { _PR_NT_FreeSids(); WSACleanup(); if (!_pr_use_static_tls) { TlsFree(_pr_currentFiberIndex); TlsFree(_pr_lastFiberIndex); TlsFree(_pr_currentCPUIndex); TlsFree(_pr_intsOffIndex); TlsFree(_pr_io_restartedIOIndex); } } PRStatus _PR_MD_INIT_THREAD(PRThread *thread) { thread->md.overlapped.ioModel = _MD_BlockingIO; thread->md.overlapped.data.mdThread = &thread->md; if (thread->flags & _PR_GLOBAL_SCOPE) { if (thread->flags & (_PR_PRIMORDIAL | _PR_ATTACHED)) { /* ** Warning: ** -------- ** NSPR requires a real handle to every thread. ** GetCurrentThread() returns a pseudo-handle which ** is not suitable for some thread operations (e.g., ** suspending). Therefore, get a real handle from ** the pseudo handle via DuplicateHandle(...) */ DuplicateHandle( GetCurrentProcess(), /* Process of source handle */ GetCurrentThread(), /* Pseudo Handle to dup */ GetCurrentProcess(), /* Process of handle */ &(thread->md.handle), /* resulting handle */ 0L, /* access flags */ FALSE, /* Inheritable */ DUPLICATE_SAME_ACCESS); /* Options */ } /* Create the blocking IO semaphore */ thread->md.blocked_sema = CreateSemaphore(NULL, 0, 1, NULL); if (thread->md.blocked_sema == NULL) { return PR_FAILURE; } if (_native_threads_only) { /* Create the blocking IO semaphore */ thread->md.thr_event = CreateEvent(NULL, TRUE, FALSE, NULL); if (thread->md.thr_event == NULL) { return PR_FAILURE; } } } return PR_SUCCESS; } static unsigned __stdcall pr_root(void *arg) { PRThread *thread = (PRThread *)arg; thread->md.start(thread); return 0; } PRStatus _PR_MD_CREATE_THREAD(PRThread *thread, void (*start)(void *), PRThreadPriority priority, PRThreadScope scope, PRThreadState state, PRUint32 stackSize) { thread->md.start = start; thread->md.handle = (HANDLE) _beginthreadex( NULL, thread->stack->stackSize, pr_root, (void *)thread, CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION, &(thread->id)); if(!thread->md.handle) { PRErrorCode prerror; thread->md.fiber_last_error = GetLastError(); switch (errno) { case ENOMEM: prerror = PR_OUT_OF_MEMORY_ERROR; break; case EAGAIN: prerror = PR_INSUFFICIENT_RESOURCES_ERROR; break; case EINVAL: prerror = PR_INVALID_ARGUMENT_ERROR; break; default: prerror = PR_UNKNOWN_ERROR; } PR_SetError(prerror, errno); return PR_FAILURE; } thread->md.id = thread->id; /* * On windows, a thread is created with a thread priority of * THREAD_PRIORITY_NORMAL. */ if (priority != PR_PRIORITY_NORMAL) { _PR_MD_SET_PRIORITY(&(thread->md), priority); } /* Activate the thread */ if ( ResumeThread( thread->md.handle ) != -1) { return PR_SUCCESS; } PR_SetError(PR_UNKNOWN_ERROR, GetLastError()); return PR_FAILURE; } void _PR_MD_JOIN_THREAD(_MDThread *md) { DWORD rv; rv = WaitForSingleObject(md->handle, INFINITE); PR_ASSERT(WAIT_OBJECT_0 == rv); } void _PR_MD_END_THREAD(void) { _endthreadex(0); } void _PR_MD_YIELD(void) { /* Can NT really yield at all? */ Sleep(0); } void _PR_MD_SET_PRIORITY(_MDThread *thread, PRThreadPriority newPri) { int nativePri; BOOL rv; if (newPri < PR_PRIORITY_FIRST) { newPri = PR_PRIORITY_FIRST; } else if (newPri > PR_PRIORITY_LAST) { newPri = PR_PRIORITY_LAST; } switch (newPri) { case PR_PRIORITY_LOW: nativePri = THREAD_PRIORITY_BELOW_NORMAL; break; case PR_PRIORITY_NORMAL: nativePri = THREAD_PRIORITY_NORMAL; break; case PR_PRIORITY_HIGH: nativePri = THREAD_PRIORITY_ABOVE_NORMAL; break; case PR_PRIORITY_URGENT: nativePri = THREAD_PRIORITY_HIGHEST; } rv = SetThreadPriority(thread->handle, nativePri); PR_ASSERT(rv); if (!rv) { PR_LOG(_pr_thread_lm, PR_LOG_MIN, ("PR_SetThreadPriority: can't set thread priority\n")); } return; } const DWORD MS_VC_EXCEPTION = 0x406D1388; #pragma pack(push,8) typedef struct tagTHREADNAME_INFO { DWORD dwType; // Must be 0x1000. LPCSTR szName; // Pointer to name (in user addr space). DWORD dwThreadID; // Thread ID (-1=caller thread). DWORD dwFlags; // Reserved for future use, must be zero. } THREADNAME_INFO; #pragma pack(pop) void _PR_MD_SET_CURRENT_THREAD_NAME(const char *name) { #ifdef _MSC_VER THREADNAME_INFO info; #endif if (sSetThreadDescription) { WCHAR wideName[MAX_PATH]; if (MultiByteToWideChar(CP_ACP, 0, name, -1, wideName, MAX_PATH)) { sSetThreadDescription(GetCurrentThread(), wideName); } } #ifdef _MSC_VER if (!IsDebuggerPresent()) { return; } info.dwType = 0x1000; info.szName = (char*) name; info.dwThreadID = -1; info.dwFlags = 0; __try { RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); } __except(EXCEPTION_CONTINUE_EXECUTION) { } #endif } void _PR_MD_CLEAN_THREAD(PRThread *thread) { BOOL rv; if (thread->md.acceptex_buf) { PR_DELETE(thread->md.acceptex_buf); } if (thread->md.xmit_bufs) { PR_DELETE(thread->md.xmit_bufs); } if (thread->md.blocked_sema) { rv = CloseHandle(thread->md.blocked_sema); PR_ASSERT(rv); thread->md.blocked_sema = 0; } if (_native_threads_only) { if (thread->md.thr_event) { rv = CloseHandle(thread->md.thr_event); PR_ASSERT(rv); thread->md.thr_event = 0; } } if (thread->md.handle) { rv = CloseHandle(thread->md.handle); PR_ASSERT(rv); thread->md.handle = 0; } /* Don't call DeleteFiber on current fiber or we'll kill the whole thread. * Don't call free(thread) until we've switched off the thread. * So put this fiber (or thread) on a list to be deleted by the idle * fiber next time we have a chance. */ if (!(thread->flags & (_PR_ATTACHED|_PR_GLOBAL_SCOPE))) { _MD_LOCK(&_nt_idleLock); _nt_idleCount++; PR_APPEND_LINK(&thread->links, &_nt_idleList); _MD_UNLOCK(&_nt_idleLock); } } void _PR_MD_EXIT_THREAD(PRThread *thread) { BOOL rv; if (thread->md.acceptex_buf) { PR_DELETE(thread->md.acceptex_buf); } if (thread->md.xmit_bufs) { PR_DELETE(thread->md.xmit_bufs); } if (thread->md.blocked_sema) { rv = CloseHandle(thread->md.blocked_sema); PR_ASSERT(rv); thread->md.blocked_sema = 0; } if (_native_threads_only) { if (thread->md.thr_event) { rv = CloseHandle(thread->md.thr_event); PR_ASSERT(rv); thread->md.thr_event = 0; } } if (thread->md.handle) { rv = CloseHandle(thread->md.handle); PR_ASSERT(rv); thread->md.handle = 0; } if (thread->flags & _PR_GLOBAL_SCOPE) { _MD_SET_CURRENT_THREAD(NULL); } } void _PR_MD_EXIT(PRIntn status) { _exit(status); } #ifdef HAVE_FIBERS void _pr_fiber_mainline(void *unused) { PRThread *fiber = _PR_MD_CURRENT_THREAD(); POST_SWITCH_WORK(); fiber->md.fiber_fn(fiber->md.fiber_arg); } PRThread *_PR_MD_CREATE_USER_THREAD( PRUint32 stacksize, void (*start)(void *), void *arg) { PRThread *thread; if ( (thread = PR_NEW(PRThread)) == NULL ) { return NULL; } memset(thread, 0, sizeof(PRThread)); thread->md.fiber_fn = start; thread->md.fiber_arg = arg; thread->md.fiber_stacksize = stacksize; return thread; } void _PR_MD_CREATE_PRIMORDIAL_USER_THREAD(PRThread *thread) { thread->md.fiber_id = ConvertThreadToFiber(NULL); PR_ASSERT(thread->md.fiber_id); _MD_SET_CURRENT_THREAD(thread); _MD_SET_LAST_THREAD(thread); thread->no_sched = 1; return; } void _PR_MD_INIT_CONTEXT(PRThread *thread, char *top, void (*start) (void), PRBool *status) { thread->md.fiber_fn = (void (*)(void *))start; thread->md.fiber_id = CreateFiber(thread->md.fiber_stacksize, (LPFIBER_START_ROUTINE)_pr_fiber_mainline, NULL); if (thread->md.fiber_id != 0) { *status = PR_TRUE; } else { DWORD oserror = GetLastError(); PRErrorCode prerror; if (oserror == ERROR_NOT_ENOUGH_MEMORY) { prerror = PR_OUT_OF_MEMORY_ERROR; } else { prerror = PR_UNKNOWN_ERROR; } PR_SetError(prerror, oserror); *status = PR_FALSE; } } void _PR_MD_SWITCH_CONTEXT(PRThread *thread) { PR_ASSERT( !_PR_IS_NATIVE_THREAD(thread) ); thread->md.fiber_last_error = GetLastError(); _PR_Schedule(); } void _PR_MD_RESTORE_CONTEXT(PRThread *thread) { PRThread *me = _PR_MD_CURRENT_THREAD(); PR_ASSERT( !_PR_IS_NATIVE_THREAD(thread) ); /* The user-level code for yielding will happily add ourselves to the runq * and then switch to ourselves; the NT fibers can't handle switching to * ourselves. */ if (thread != me) { SetLastError(thread->md.fiber_last_error); _MD_SET_CURRENT_THREAD(thread); _PR_MD_SET_LAST_THREAD(me); thread->no_sched = 1; SwitchToFiber(thread->md.fiber_id); POST_SWITCH_WORK(); } } #endif /* HAVE_FIBERS */ PRInt32 _PR_MD_SETTHREADAFFINITYMASK(PRThread *thread, PRUint32 mask ) { int rv; rv = SetThreadAffinityMask(thread->md.handle, mask); return rv?0:-1; } PRInt32 _PR_MD_GETTHREADAFFINITYMASK(PRThread *thread, PRUint32 *mask) { PRInt32 rv, system_mask; rv = GetProcessAffinityMask(GetCurrentProcess(), mask, &system_mask); return rv?0:-1; } void _PR_MD_SUSPEND_CPU(_PRCPU *cpu) { _PR_MD_SUSPEND_THREAD(cpu->thread); } void _PR_MD_RESUME_CPU(_PRCPU *cpu) { _PR_MD_RESUME_THREAD(cpu->thread); } void _PR_MD_SUSPEND_THREAD(PRThread *thread) { if (_PR_IS_NATIVE_THREAD(thread)) { /* ** There seems to be some doubt about whether or not SuspendThread ** is a synchronous function. The test afterwards is to help veriry ** that it is, which is what Microsoft says it is. */ PRUintn rv = SuspendThread(thread->md.handle); PR_ASSERT(0xffffffffUL != rv); } } void _PR_MD_RESUME_THREAD(PRThread *thread) { if (_PR_IS_NATIVE_THREAD(thread)) { ResumeThread(thread->md.handle); } } PRThread* _MD_CURRENT_THREAD(void) { PRThread *thread; thread = _MD_GET_ATTACHED_THREAD(); if (NULL == thread) { thread = _PRI_AttachThread( PR_USER_THREAD, PR_PRIORITY_NORMAL, NULL, 0); } PR_ASSERT(thread != NULL); return thread; }