From 21a6998eeac6b3b349ab25669d5d64b7c185fcc8 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Wed, 29 Apr 2026 11:23:16 +0300 Subject: [PATCH 01/32] osthreadposix and windows files added --- .../thread/{osthread.d => osthread/package.d} | 0 .../src/core/thread/osthread/posix_impl.d | 57 + .../src/core/thread/osthread/windows_impl.d | 3230 +++++++++++++++++ 3 files changed, 3287 insertions(+) rename druntime/src/core/thread/{osthread.d => osthread/package.d} (100%) create mode 100644 druntime/src/core/thread/osthread/posix_impl.d create mode 100644 druntime/src/core/thread/osthread/windows_impl.d diff --git a/druntime/src/core/thread/osthread.d b/druntime/src/core/thread/osthread/package.d similarity index 100% rename from druntime/src/core/thread/osthread.d rename to druntime/src/core/thread/osthread/package.d diff --git a/druntime/src/core/thread/osthread/posix_impl.d b/druntime/src/core/thread/osthread/posix_impl.d new file mode 100644 index 000000000000..a54442b3f13c --- /dev/null +++ b/druntime/src/core/thread/osthread/posix_impl.d @@ -0,0 +1,57 @@ +/** + * The thread module provides support for thread creation and management. + * + * Copyright: Copyright Sean Kelly 2005 - 2012. + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Sean Kelly, Walter Bright, Alex Rønne Petersen, Martin Nowak + * Source: $(DRUNTIMESRC core/thread/package.d) + */ + +module core.thread; + +public import core.time; +public import core.thread.fiber; +public import core.thread.osthread; +public import core.thread.threadbase; +public import core.thread.threadgroup; +public import core.thread.types; +public import core.thread.context; + + +// this test is here to avoid a cyclic dependency between +// core.thread and core.atomic +@system unittest +{ + import core.atomic; + + shared uint x; + shared bool f; + shared uint r; + + auto thr = new Thread(() + { + while (!atomicLoad(f)) + { + } + + atomicFence(); // make sure load+store below happens after waiting for f + + cast() r = cast() x; + }); + + thr.start(); // new thread will wait until f is set + + cast() x = 42; + + atomicFence(); // make sure x is set before setting f + + cast() f = true; + + atomicFence(); + + thr.join(); + + assert(cast() r == 42); +} diff --git a/druntime/src/core/thread/osthread/windows_impl.d b/druntime/src/core/thread/osthread/windows_impl.d new file mode 100644 index 000000000000..f42f6a9c7409 --- /dev/null +++ b/druntime/src/core/thread/osthread/windows_impl.d @@ -0,0 +1,3230 @@ +/** + * The osthread module provides low-level, OS-dependent code + * for thread creation and management. + * + * Copyright: Copyright Sean Kelly 2005 - 2012. + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Sean Kelly, Walter Bright, Alex Rønne Petersen, Martin Nowak + * Source: $(DRUNTIMESRC core/thread/osthread.d) + */ + +module core.thread.osthread; + +import core.atomic; +import core.exception : onOutOfMemoryError; +import core.internal.traits : externDFunc; +import core.memory : GC, pageSize; +import core.thread.context; +import core.thread.threadbase; +import core.thread.types; +import core.time; + +/////////////////////////////////////////////////////////////////////////////// +// Platform Detection and Memory Allocation +/////////////////////////////////////////////////////////////////////////////// + +version (OSX) + version = Darwin; +else version (iOS) + version = Darwin; +else version (TVOS) + version = Darwin; +else version (WatchOS) + version = Darwin; + +version (D_InlineAsm_X86) +{ + version (Windows) + version = AsmX86_Windows; + else version (Posix) + version = AsmX86_Posix; +} +else version (D_InlineAsm_X86_64) +{ + version (Windows) + { + version = AsmX86_64_Windows; + } + else version (Posix) + { + version = AsmX86_64_Posix; + } +} + +version (Windows) +{ + import core.stdc.stdint : uintptr_t; // for _beginthreadex decl below + import core.stdc.stdlib : free, malloc, realloc; + import core.sys.windows.basetsd /+: HANDLE+/; + import core.sys.windows.threadaux /+: getThreadStackBottom, impersonate_thread, OpenThreadHandle+/; + import core.sys.windows.winbase /+: CloseHandle, CREATE_SUSPENDED, DuplicateHandle, GetCurrentThread, + GetCurrentThreadId, GetCurrentProcess, GetExitCodeThread, GetSystemInfo, GetThreadContext, + GetThreadPriority, INFINITE, ResumeThread, SetThreadPriority, Sleep, STILL_ACTIVE, + SuspendThread, SwitchToThread, SYSTEM_INFO, THREAD_PRIORITY_IDLE, THREAD_PRIORITY_NORMAL, + THREAD_PRIORITY_TIME_CRITICAL, WAIT_OBJECT_0, WaitForSingleObject+/; + import core.sys.windows.windef /+: TRUE+/; + import core.sys.windows.winnt /+: CONTEXT, CONTEXT_CONTROL, CONTEXT_INTEGER+/; + + private extern (Windows) alias btex_fptr = uint function(void*); + private extern (C) uintptr_t _beginthreadex(void*, uint, btex_fptr, void*, uint, uint*) nothrow @nogc; +} +else version (Posix) +{ + static import core.sys.posix.pthread; + static import core.sys.posix.signal; + import core.stdc.errno : EINTR, errno; + import core.sys.posix.pthread : pthread_atfork, pthread_attr_destroy, pthread_attr_getstack, pthread_attr_init, + pthread_attr_setstacksize, pthread_create, pthread_detach, pthread_getschedparam, pthread_join, pthread_self, + pthread_setschedparam, sched_get_priority_max, sched_get_priority_min, sched_param, sched_yield; + import core.sys.posix.semaphore : sem_init, sem_post, sem_t, sem_wait; + import core.sys.posix.signal : pthread_kill, sigaction, sigaction_t, sigdelset, sigfillset, sigset_t, sigsuspend, + SIGUSR1, stack_t; + import core.sys.posix.stdlib : free, malloc, realloc; + import core.sys.posix.sys.types : pthread_attr_t, pthread_key_t, pthread_t; + import core.sys.posix.time : nanosleep, timespec; + + version (Darwin) + { + // Use macOS threads for suspend/resume + import core.sys.darwin.mach.kern_return : KERN_SUCCESS; + import core.sys.darwin.mach.port : mach_port_t; + import core.sys.darwin.mach.thread_act : mach_msg_type_number_t, + thread_get_state, thread_resume, thread_suspend; + import core.sys.darwin.pthread : pthread_mach_thread_np; + version (X86) + { + import core.sys.darwin.mach.thread_act : + x86_THREAD_STATE32, x86_THREAD_STATE32_COUNT, x86_thread_state32_t; + } + else version (X86_64) + { + import core.sys.darwin.mach.thread_act : + x86_THREAD_STATE64, x86_THREAD_STATE64_COUNT, x86_thread_state64_t; + } + else version (AArch64) + { + import core.sys.darwin.mach.thread_act : + ARM_THREAD_STATE64, ARM_THREAD_STATE64_COUNT, arm_thread_state64_t; + } + else version (PPC) + { + import core.sys.darwin.mach.thread_act : + PPC_THREAD_STATE, PPC_THREAD_STATE_COUNT, ppc_thread_state_t; + } + else version (PPC64) + { + import core.sys.darwin.mach.thread_act : + PPC_THREAD_STATE64, PPC_THREAD_STATE64_COUNT, ppc_thread_state64_t; + } + } + else version (Solaris) + { + // Use Solaris threads for suspend/resume + import core.sys.posix.sys.wait : idtype_t; + import core.sys.solaris.sys.priocntl : PC_CLNULL, PC_GETCLINFO, PC_GETPARMS, PC_SETPARMS, pcinfo_t, pcparms_t, priocntl; + import core.sys.solaris.sys.types : P_MYID, pri_t; + import core.sys.solaris.thread : thr_stksegment, thr_suspend, thr_continue; + import core.sys.solaris.sys.procfs : PR_STOPPED, lwpstatus_t; + } + else + { + // Use POSIX threads for suspend/resume + } +} +else + static assert(0, "unsupported operating system"); + +version (GNU) +{ + import gcc.builtins; +} + +/** + * Hook for whatever EH implementation is used to save/restore some data + * per stack. + * + * Params: + * newContext = The return value of the prior call to this function + * where the stack was last swapped out, or null when a fiber stack + * is switched in for the first time. + */ +private extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc; + +version (DigitalMars) +{ + version (Windows) + { + extern(D) void* swapContext(void* newContext) nothrow @nogc + { + return _d_eh_swapContext(newContext); + } + } + else + { + extern(C) void* _d_eh_swapContextDwarf(void* newContext) nothrow @nogc; + + extern(D) void* swapContext(void* newContext) nothrow @nogc + { + /* Detect at runtime which scheme is being used. + * Eventually, determine it statically. + */ + static int which = 0; + final switch (which) + { + case 0: + { + assert(newContext == null); + auto p = _d_eh_swapContext(newContext); + auto pdwarf = _d_eh_swapContextDwarf(newContext); + if (p) + { + which = 1; + return p; + } + else if (pdwarf) + { + which = 2; + return pdwarf; + } + return null; + } + case 1: + return _d_eh_swapContext(newContext); + case 2: + return _d_eh_swapContextDwarf(newContext); + } + } + } +} +else +{ + extern(D) void* swapContext(void* newContext) nothrow @nogc + { + return _d_eh_swapContext(newContext); + } +} + +/** + * This class encapsulates all threading functionality for the D + * programming language. As thread manipulation is a required facility + * for garbage collection, all user threads should derive from this + * class, and instances of this class should never be explicitly deleted. + * A new thread may be created using either derivation or composition, as + * in the following example. + */ +version (CoreDdoc) +class Thread : ThreadBase +{ + /** + * Initializes a thread object which is associated with a static + * D function. + * + * Params: + * fn = The thread function. + * sz = The stack size for this thread. + * + * In: + * fn must not be null. + */ + this( void function() fn, size_t sz = 0 ) @safe pure nothrow @nogc + { + } + + + /** + * Initializes a thread object which is associated with a dynamic + * D function. + * + * Params: + * dg = The thread function. + * sz = The stack size for this thread. + * + * In: + * dg must not be null. + */ + this( void delegate() dg, size_t sz = 0 ) @safe pure nothrow @nogc + { + } + + package this( size_t sz = 0 ) @safe pure nothrow @nogc + { + } + + /** + * Cleans up any remaining resources used by this object. + */ + ~this() nothrow @nogc + { + } + + // + // Thread entry point. Invokes the function or delegate passed on + // construction (if any). + // + private final void run() + { + } + + /** + * Provides a reference to the calling thread. + * + * Returns: + * The thread object representing the calling thread. The result of + * deleting this object is undefined. If the current thread is not + * attached to the runtime, a null reference is returned. + */ + static Thread getThis() @safe nothrow @nogc + { + return null; + } + + /// + override final void[] savedRegisters() nothrow @nogc + { + return null; + } + + /** + * Starts the thread and invokes the function or delegate passed upon + * construction. + * + * In: + * This routine may only be called once per thread instance. + * + * Throws: + * ThreadException if the thread fails to start. + */ + final Thread start() nothrow + { + return null; + } + + /** + * Waits for this thread to complete. If the thread terminated as the + * result of an unhandled exception, this exception will be rethrown. + * + * Params: + * rethrow = Rethrow any unhandled exception which may have caused this + * thread to terminate. + * + * Throws: + * ThreadException if the operation fails. + * Any exception not handled by the joined thread. + * + * Returns: + * Any exception not handled by this thread if rethrow = false, null + * otherwise. + */ + override final Throwable join( bool rethrow = true ) + { + return null; + } + + /** + * The minimum scheduling priority that may be set for a thread. On + * systems where multiple scheduling policies are defined, this value + * represents the minimum valid priority for the scheduling policy of + * the process. + */ + @property static int PRIORITY_MIN() @nogc nothrow pure @trusted + { + return 0; + } + + /** + * The maximum scheduling priority that may be set for a thread. On + * systems where multiple scheduling policies are defined, this value + * represents the maximum valid priority for the scheduling policy of + * the process. + */ + @property static const(int) PRIORITY_MAX() @nogc nothrow pure @trusted + { + return 0; + } + + /** + * The default scheduling priority that is set for a thread. On + * systems where multiple scheduling policies are defined, this value + * represents the default priority for the scheduling policy of + * the process. + */ + @property static int PRIORITY_DEFAULT() @nogc nothrow pure @trusted + { + return 0; + } + + /** + * Gets the scheduling priority for the associated thread. + * + * Note: Getting the priority of a thread that already terminated + * might return the default priority. + * + * Returns: + * The scheduling priority of this thread. + */ + final @property int priority() + { + return 0; + } + + /** + * Sets the scheduling priority for the associated thread. + * + * Note: Setting the priority of a thread that already terminated + * might have no effect. + * + * Params: + * val = The new scheduling priority of this thread. + */ + final @property void priority( int val ) + { + } + + /** + * Tests whether this thread is running. + * + * Returns: + * true if the thread is running, false if not. + */ + override final @property bool isRunning() nothrow @nogc + { + return false; + } + + /** + * Suspends the calling thread for at least the supplied period. This may + * result in multiple OS calls if period is greater than the maximum sleep + * duration supported by the operating system. + * + * Params: + * val = The minimum duration the calling thread should be suspended. + * + * In: + * period must be non-negative. + * + * Example: + * ------------------------------------------------------------------------ + * + * Thread.sleep( dur!("msecs")( 50 ) ); // sleep for 50 milliseconds + * Thread.sleep( dur!("seconds")( 5 ) ); // sleep for 5 seconds + * + * ------------------------------------------------------------------------ + */ + static void sleep( Duration val ) @nogc nothrow @trusted + { + } + + /** + * Forces a context switch to occur away from the calling thread. + */ + static void yield() @nogc nothrow + { + } +} + +version (CoreDdoc) {} else +class Thread : ThreadBase +{ + version (Windows) + { + private HANDLE m_hndl; + } + + version (Posix) + { + private shared bool m_isRunning; + } + + version (Darwin) + { + private mach_port_t m_tmach; + } + + version (Solaris) + { + private __gshared bool m_isRTClass; + } + + version (Windows) + { + alias TLSKey = uint; + } + else version (Posix) + { + alias TLSKey = pthread_key_t; + } + else + static assert(0, "unsupported os"); + + this( void function() fn, size_t sz = 0 ) @safe pure nothrow @nogc + { + super(fn, sz); + } + + this( void delegate() dg, size_t sz = 0 ) @safe pure nothrow @nogc + { + super(dg, sz); + } + + package this( size_t sz = 0 ) @safe pure nothrow @nogc + { + super(sz); + } + + ~this() nothrow @nogc + { + if (super.destructBeforeDtor()) + return; + + version (Windows) + { + m_addr = m_addr.init; + CloseHandle( m_hndl ); + m_hndl = m_hndl.init; + } + else version (Posix) + { + if (m_addr != m_addr.init) + pthread_detach( m_addr ); + m_addr = m_addr.init; + version (Darwin) + { + m_tmach = m_tmach.init; + } + } + else + static assert(0, "unsupported OS"); + } + + private final void run() + { + super.run(); + } + + static Thread getThis() @safe nothrow @nogc + { + return ThreadBase.getThis().toThread; + } + + version (Windows) + { + version (X86) + { + uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax + } + else version (X86_64) + { + ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax + // r8,r9,r10,r11,r12,r13,r14,r15 + } + else + { + static assert(false, "Architecture not supported." ); + } + } + else version (Darwin) + { + version (X86) + { + uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax + } + else version (X86_64) + { + ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax + // r8,r9,r10,r11,r12,r13,r14,r15 + } + else version (AArch64) + { + ulong[33] m_reg; // x0-x31, pc + } + else version (ARM) + { + uint[16] m_reg; // r0-r15 + } + else version (PPC) + { + // Make the assumption that we only care about non-fp and non-vr regs. + // ??? : it seems plausible that a valid address can be copied into a VR. + uint[32] m_reg; // r0-31 + } + else version (PPC64) + { + // As above. + ulong[32] m_reg; // r0-31 + } + else + { + static assert(false, "Architecture not supported." ); + } + } + else version (Solaris) + { + version (X86) + { + uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax + } + else version (X86_64) + { + ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax + // r8,r9,r10,r11,r12,r13,r14,r15 + } + else version (SPARC) + { + int[33] m_reg; // g0-7, o0-7, l0-7, i0-7, pc + } + else version (SPARC64) + { + long[33] m_reg; // g0-7, o0-7, l0-7, i0-7, pc + } + else + { + static assert(false, "Architecture not supported." ); + } + } + + override final void[] savedRegisters() nothrow @nogc + { + version (Windows) + { + return m_reg; + } + else version (Darwin) + { + return m_reg; + } + else version (Solaris) + { + return m_reg; + } + else + { + return null; + } + } + + final Thread start() nothrow + in + { + assert( !next && !prev ); + } + do + { + auto wasThreaded = multiThreadedFlag; + multiThreadedFlag = true; + scope( failure ) + { + if ( !wasThreaded ) + multiThreadedFlag = false; + } + + version (Windows) + { + // NOTE: If a thread is just executing DllMain() + // while another thread is started here, it holds an OS internal + // lock that serializes DllMain with CreateThread. As the code + // might request a synchronization on slock (e.g. in thread_findByAddr()), + // we cannot hold that lock while creating the thread without + // creating a deadlock + // + // Solution: Create the thread in suspended state and then + // add and resume it with slock acquired + assert(m_sz <= uint.max, "m_sz must be less than or equal to uint.max"); + m_hndl = cast(HANDLE) _beginthreadex( null, cast(uint) m_sz, &thread_entryPoint, cast(void*) this, CREATE_SUSPENDED, &m_addr ); + if ( cast(size_t) m_hndl == 0 ) + onThreadError( "Error creating thread" ); + } + else version (Posix) + { + size_t stksz = adjustStackSize( m_sz ); + + pthread_attr_t attr; + + if ( pthread_attr_init( &attr ) ) + onThreadError( "Error initializing thread attributes" ); + if ( stksz && pthread_attr_setstacksize( &attr, stksz ) ) + onThreadError( "Error initializing thread stack size" ); + } + else + static assert(0, "unsupported OS"); + + slock.lock_nothrow(); + scope(exit) slock.unlock_nothrow(); + { + incrementAboutToStart(this); + + version (Windows) + { + if ( ResumeThread( m_hndl ) == -1 ) + onThreadError( "Error resuming thread" ); + } + else version (Posix) + { + // NOTE: This is also set to true by thread_entryPoint, but set it + // here as well so the calling thread will see the isRunning + // state immediately. + atomicStore!(MemoryOrder.raw)(m_isRunning, true); + scope( failure ) atomicStore!(MemoryOrder.raw)(m_isRunning, false); + + version (Shared) + { + auto libs = externDFunc!("rt.sections_elf_shared.pinLoadedLibraries", + void* function() @nogc nothrow)(); + + auto ps = cast(void**).malloc(2 * size_t.sizeof); + if (ps is null) onOutOfMemoryError(); + ps[0] = cast(void*)this; + ps[1] = cast(void*)libs; + if ( pthread_create( &m_addr, &attr, &thread_entryPoint, ps ) != 0 ) + { + externDFunc!("rt.sections_elf_shared.unpinLoadedLibraries", + void function(void*) @nogc nothrow)(libs); + .free(ps); + onThreadError( "Error creating thread" ); + } + } + else + { + if ( pthread_create( &m_addr, &attr, &thread_entryPoint, cast(void*) this ) != 0 ) + onThreadError( "Error creating thread" ); + } + if ( pthread_attr_destroy( &attr ) != 0 ) + onThreadError( "Error destroying thread attributes" ); + + version (Darwin) + { + m_tmach = pthread_mach_thread_np( m_addr ); + if ( m_tmach == m_tmach.init ) + onThreadError( "Error creating thread" ); + } + } + else + static assert(0, "unsupported OS"); + + return this; + } + } + + override final Throwable join( bool rethrow = true ) + { + version (Windows) + { + if ( m_addr != m_addr.init && WaitForSingleObject( m_hndl, INFINITE ) != WAIT_OBJECT_0 ) + throw new ThreadException( "Unable to join thread" ); + // NOTE: m_addr must be cleared before m_hndl is closed to avoid + // a race condition with isRunning. The operation is done + // with atomicStore to prevent compiler reordering. + atomicStore!(MemoryOrder.raw)(*cast(shared)&m_addr, m_addr.init); + CloseHandle( m_hndl ); + m_hndl = m_hndl.init; + } + else version (Posix) + { + if ( m_addr != m_addr.init && pthread_join( m_addr, null ) != 0 ) + throw new ThreadException( "Unable to join thread" ); + // NOTE: pthread_join acts as a substitute for pthread_detach, + // which is normally called by the dtor. Setting m_addr + // to zero ensures that pthread_detach will not be called + // on object destruction. + m_addr = m_addr.init; + } + else + static assert(0, "unsupported OS"); + + if ( m_unhandled ) + { + if ( rethrow ) + throw m_unhandled; + return m_unhandled; + } + return null; + } + + version (Windows) + { + @property static int PRIORITY_MIN() @nogc nothrow pure @safe + { + return THREAD_PRIORITY_IDLE; + } + + @property static const(int) PRIORITY_MAX() @nogc nothrow pure @safe + { + return THREAD_PRIORITY_TIME_CRITICAL; + } + + @property static int PRIORITY_DEFAULT() @nogc nothrow pure @safe + { + return THREAD_PRIORITY_NORMAL; + } + } + else version (Posix) + { + private struct Priority + { + int PRIORITY_MIN = int.min; + int PRIORITY_DEFAULT = int.min; + int PRIORITY_MAX = int.min; + } + + /* + Lazily loads one of the members stored in a hidden global variable of + type `Priority`. Upon the first access of either member, the entire + `Priority` structure is initialized. Multiple initializations from + different threads calling this function are tolerated. + + `which` must be one of `PRIORITY_MIN`, `PRIORITY_DEFAULT`, + `PRIORITY_MAX`. + */ + private static shared Priority cache; + private static int loadGlobal(string which)() + { + auto local = atomicLoad(mixin("cache." ~ which)); + if (local != local.min) return local; + // There will be benign races + auto loaded = loadPriorities; + static foreach (i, _; loaded.tupleof) + atomicStore(cache.tupleof[i], loaded.tupleof[i]); + return atomicLoad(mixin("cache." ~ which)); + } + + /* + Loads all priorities and returns them as a `Priority` structure. This + function is thread-neutral. + */ + private static Priority loadPriorities() @nogc nothrow @trusted + { + Priority result; + version (Solaris) + { + pcparms_t pcParms; + pcinfo_t pcInfo; + + pcParms.pc_cid = PC_CLNULL; + if (priocntl(idtype_t.P_PID, P_MYID, PC_GETPARMS, &pcParms) == -1) + assert( 0, "Unable to get scheduling class" ); + + pcInfo.pc_cid = pcParms.pc_cid; + // PC_GETCLINFO ignores the first two args, use dummy values + if (priocntl(idtype_t.P_PID, 0, PC_GETCLINFO, &pcInfo) == -1) + assert( 0, "Unable to get scheduling class info" ); + + pri_t* clparms = cast(pri_t*)&pcParms.pc_clparms; + pri_t* clinfo = cast(pri_t*)&pcInfo.pc_clinfo; + + result.PRIORITY_MAX = clparms[0]; + + if (pcInfo.pc_clname == "RT") + { + m_isRTClass = true; + + // For RT class, just assume it can't be changed + result.PRIORITY_MIN = clparms[0]; + result.PRIORITY_DEFAULT = clparms[0]; + } + else + { + m_isRTClass = false; + + // For all other scheduling classes, there are + // two key values -- uprilim and maxupri. + // maxupri is the maximum possible priority defined + // for the scheduling class, and valid priorities + // range are in [-maxupri, maxupri]. + // + // However, uprilim is an upper limit that the + // current thread can set for the current scheduling + // class, which can be less than maxupri. As such, + // use this value for priorityMax since this is + // the effective maximum. + + // maxupri + result.PRIORITY_MIN = -cast(int)(clinfo[0]); + // by definition + result.PRIORITY_DEFAULT = 0; + } + } + else + { + int policy; + sched_param param; + pthread_getschedparam( pthread_self(), &policy, ¶m ) == 0 + || assert(0, "Internal error in pthread_getschedparam"); + + result.PRIORITY_MIN = sched_get_priority_min( policy ); + result.PRIORITY_MIN != -1 + || assert(0, "Internal error in sched_get_priority_min"); + result.PRIORITY_DEFAULT = param.sched_priority; + result.PRIORITY_MAX = sched_get_priority_max( policy ); + result.PRIORITY_MAX != -1 || + assert(0, "Internal error in sched_get_priority_max"); + } + return result; + } + + @property static int PRIORITY_MIN() @nogc nothrow pure @trusted + { + return (cast(int function() @nogc nothrow pure @safe) + &loadGlobal!"PRIORITY_MIN")(); + } + + @property static const(int) PRIORITY_MAX() @nogc nothrow pure @trusted + { + return (cast(int function() @nogc nothrow pure @safe) + &loadGlobal!"PRIORITY_MAX")(); + } + + @property static int PRIORITY_DEFAULT() @nogc nothrow pure @trusted + { + return (cast(int function() @nogc nothrow pure @safe) + &loadGlobal!"PRIORITY_DEFAULT")(); + } + } + else + static assert(0, "unsupported OS"); + + + version (NetBSD) + { + //NetBSD does not support priority for default policy + // and it is not possible change policy without root access + int fakePriority = int.max; + } + + final @property int priority() + { + version (Windows) + { + return GetThreadPriority( m_hndl ); + } + else version (NetBSD) + { + return fakePriority==int.max? PRIORITY_DEFAULT : fakePriority; + } + else version (Posix) + { + int policy; + sched_param param; + + if (auto err = pthread_getschedparam(m_addr, &policy, ¶m)) + { + // ignore error if thread is not running => Bugzilla 8960 + if (!atomicLoad(m_isRunning)) return PRIORITY_DEFAULT; + throw new ThreadException("Unable to get thread priority"); + } + return param.sched_priority; + } + else + static assert(0, "unsupported os"); + } + + final @property void priority( int val ) + in + { + assert(val >= PRIORITY_MIN); + assert(val <= PRIORITY_MAX); + } + do + { + version (Windows) + { + if ( !SetThreadPriority( m_hndl, val ) ) + throw new ThreadException( "Unable to set thread priority" ); + } + else version (Solaris) + { + // the pthread_setschedprio(3c) and pthread_setschedparam functions + // are broken for the default (TS / time sharing) scheduling class. + // instead, we use priocntl(2) which gives us the desired behavior. + + // We hardcode the min and max priorities to the current value + // so this is a no-op for RT threads. + if (m_isRTClass) + return; + + pcparms_t pcparm; + + pcparm.pc_cid = PC_CLNULL; + if (priocntl(idtype_t.P_LWPID, P_MYID, PC_GETPARMS, &pcparm) == -1) + throw new ThreadException( "Unable to get scheduling class" ); + + pri_t* clparms = cast(pri_t*)&pcparm.pc_clparms; + + // clparms is filled in by the PC_GETPARMS call, only necessary + // to adjust the element that contains the thread priority + clparms[1] = cast(pri_t) val; + + if (priocntl(idtype_t.P_LWPID, P_MYID, PC_SETPARMS, &pcparm) == -1) + throw new ThreadException( "Unable to set scheduling class" ); + } + else version (NetBSD) + { + fakePriority = val; + } + else version (Posix) + { + static if (__traits(compiles, core.sys.posix.pthread.pthread_setschedprio)) + { + import core.sys.posix.pthread : pthread_setschedprio; + + if (auto err = pthread_setschedprio(m_addr, val)) + { + // ignore error if thread is not running => Bugzilla 8960 + if (!atomicLoad(m_isRunning)) return; + throw new ThreadException("Unable to set thread priority"); + } + } + else + { + // NOTE: pthread_setschedprio is not implemented on Darwin, FreeBSD, OpenBSD, + // or DragonFlyBSD, so use the more complicated get/set sequence below. + int policy; + sched_param param; + + if (auto err = pthread_getschedparam(m_addr, &policy, ¶m)) + { + // ignore error if thread is not running => Bugzilla 8960 + if (!atomicLoad(m_isRunning)) return; + throw new ThreadException("Unable to set thread priority"); + } + param.sched_priority = val; + if (auto err = pthread_setschedparam(m_addr, policy, ¶m)) + { + // ignore error if thread is not running => Bugzilla 8960 + if (!atomicLoad(m_isRunning)) return; + throw new ThreadException("Unable to set thread priority"); + } + } + } + else + static assert(0, "unsupported os"); + } + + + unittest + { + auto thr = Thread.getThis(); + immutable prio = thr.priority; + scope (exit) thr.priority = prio; + + assert(prio == PRIORITY_DEFAULT); + assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); + thr.priority = PRIORITY_MIN; + assert(thr.priority == PRIORITY_MIN); + thr.priority = PRIORITY_MAX; + assert(thr.priority == PRIORITY_MAX); + } + + unittest // Bugzilla 8960 + { + import core.sync.semaphore; + + auto thr = new Thread({}); + thr.start(); + Thread.sleep(1.msecs); // wait a little so the thread likely has finished + thr.priority = PRIORITY_MAX; // setting priority doesn't cause error + auto prio = thr.priority; // getting priority doesn't cause error + assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); + } + + override final @property bool isRunning() nothrow @nogc + { + if (!super.isRunning()) + return false; + + version (Windows) + { + uint ecode = 0; + GetExitCodeThread( m_hndl, &ecode ); + return ecode == STILL_ACTIVE; + } + else version (Posix) + { + return atomicLoad(m_isRunning); + } + else + static assert(0, "unsupported os"); + } + + static void sleep( Duration val ) @nogc nothrow @trusted + in + { + assert( !val.isNegative ); + } + do + { + version (Windows) + { + auto maxSleepMillis = dur!("msecs")( uint.max - 1 ); + + // avoid a non-zero time to be round down to 0 + if ( val > dur!"msecs"( 0 ) && val < dur!"msecs"( 1 ) ) + val = dur!"msecs"( 1 ); + + // NOTE: In instances where all other threads in the process have a + // lower priority than the current thread, the current thread + // will not yield with a sleep time of zero. However, unlike + // yield(), the user is not asking for a yield to occur but + // only for execution to suspend for the requested interval. + // Therefore, expected performance may not be met if a yield + // is forced upon the user. + while ( val > maxSleepMillis ) + { + Sleep( cast(uint) + maxSleepMillis.total!"msecs" ); + val -= maxSleepMillis; + } + Sleep( cast(uint) val.total!"msecs" ); + } + else version (Posix) + { + timespec tin = void; + timespec tout = void; + + val.split!("seconds", "nsecs")(tin.tv_sec, tin.tv_nsec); + if ( val.total!"seconds" > tin.tv_sec.max ) + tin.tv_sec = tin.tv_sec.max; + while ( true ) + { + if ( !nanosleep( &tin, &tout ) ) + return; + if ( errno != EINTR ) + assert(0, "Unable to sleep for the specified duration"); + tin = tout; + } + } + else + static assert(0, "unsupported os"); + } + + static void yield() @nogc nothrow + { + version (Windows) + SwitchToThread(); + else version (Posix) + sched_yield(); + else + static assert(0, "unsupported os"); + } +} + +private Thread toThread(return scope ThreadBase t) @trusted nothrow @nogc pure +{ + return cast(Thread) cast(void*) t; +} + +private extern(D) static void thread_yield() @nogc nothrow +{ + Thread.yield(); +} + +/// +unittest +{ + class DerivedThread : Thread + { + this() + { + super(&run); + } + + private: + void run() + { + // Derived thread running. + } + } + + void threadFunc() + { + // Composed thread running. + } + + // create and start instances of each type + auto derived = new DerivedThread().start(); + auto composed = new Thread(&threadFunc).start(); + new Thread({ + // Codes to run in the newly created thread. + }).start(); +} + +unittest +{ + int x = 0; + + new Thread( + { + x++; + }).start().join(); + assert( x == 1 ); +} + + +unittest +{ + enum MSG = "Test message."; + string caughtMsg; + + try + { + new Thread( + function() + { + throw new Exception( MSG ); + }).start().join(); + assert( false, "Expected rethrown exception." ); + } + catch ( Throwable t ) + { + assert( t.msg == MSG ); + } +} + + +unittest +{ + // use >pageSize to avoid stack overflow (e.g. in an syscall) + auto thr = new Thread(function{}, 4096 + 1).start(); + thr.join(); +} + + +unittest +{ + import core.memory : GC; + + auto t1 = new Thread({ + foreach (_; 0 .. 20) + ThreadBase.getAll; + }).start; + auto t2 = new Thread({ + foreach (_; 0 .. 20) + GC.collect; + }).start; + t1.join(); + t2.join(); +} + +unittest +{ + import core.sync.semaphore; + auto sem = new Semaphore(); + + auto t = new Thread( + { + sem.notify(); + Thread.sleep(100.msecs); + }).start(); + + sem.wait(); // thread cannot be detached while being started + thread_detachInstance(t); + foreach (t2; Thread) + assert(t !is t2); + t.join(); +} + +// https://issues.dlang.org/show_bug.cgi?id=22124 +unittest +{ + Thread thread = new Thread({}); + auto fun(Thread t, int x) + { + t.__ctor({x = 3;}); + return t; + } + static assert(!__traits(compiles, () @nogc => fun(thread, 3) )); +} + +@nogc @safe nothrow +unittest +{ + Thread.sleep(1.msecs); +} + +/////////////////////////////////////////////////////////////////////////////// +// GC Support Routines +/////////////////////////////////////////////////////////////////////////////// + +version (CoreDdoc) +{ + /** + * Instruct the thread module, when initialized, to use a different set of + * signals besides SIGRTMIN and SIGRTMIN + 1 for suspension and resumption of threads. + * This function should be called at most once, prior to thread_init(). + * This function is Posix-only. + */ + extern (C) void thread_setGCSignals(int suspendSignalNo, int resumeSignalNo) nothrow @nogc + { + } + + /** + * Get the GC signals set by the thread module. This function should be called either + * after thread_init() has finished, or after a call thread_setGCSignals(). + * This function is Posix-only. + */ + extern (C) void thread_getGCSignals(out int suspendSignalNo, out int resumeSignalNo) nothrow @nogc + { + } +} +else version (Posix) +{ + extern (C) void thread_setGCSignals(int suspendSignalNo, int resumeSignalNo) nothrow @nogc + in + { + assert(suspendSignalNo != 0); + assert(resumeSignalNo != 0); + } + out + { + assert(suspendSignalNumber != 0); + assert(resumeSignalNumber != 0); + } + do + { + suspendSignalNumber = suspendSignalNo; + resumeSignalNumber = resumeSignalNo; + } + + extern (C) void thread_getGCSignals(out int suspendSignalNo, out int resumeSignalNo) nothrow @nogc + in + { + assert(suspendSignalNumber != 0); + assert(resumeSignalNumber != 0); + } + out + { + assert(suspendSignalNo != 0); + assert(resumeSignalNo != 0); + } + do + { + suspendSignalNo = suspendSignalNumber; + resumeSignalNo = resumeSignalNumber; + } +} + +version (Posix) +{ + private __gshared int suspendSignalNumber; + private __gshared int resumeSignalNumber; +} + +version (CoreDdoc) {} else +private extern (D) ThreadBase attachThread(ThreadBase _thisThread) @nogc nothrow +{ + Thread thisThread = _thisThread.toThread(); + + StackContext* thisContext = &thisThread.m_main; + assert( thisContext == thisThread.m_curr ); + + version (Windows) + { + thisThread.m_addr = GetCurrentThreadId(); + thisThread.m_hndl = GetCurrentThreadHandle(); + thisContext.bstack = getStackBottom(); + thisContext.tstack = thisContext.bstack; + } + else version (Posix) + { + thisThread.m_addr = pthread_self(); + thisContext.bstack = getStackBottom(); + thisContext.tstack = thisContext.bstack; + + atomicStore!(MemoryOrder.raw)(thisThread.toThread.m_isRunning, true); + } + else + static assert(0, "unsupported os"); + thisThread.m_isDaemon = true; + thisThread.tlsRTdataInit(); + Thread.setThis( thisThread ); + + version (Darwin) + { + thisThread.m_tmach = pthread_mach_thread_np( thisThread.m_addr ); + assert( thisThread.m_tmach != thisThread.m_tmach.init ); + } + + Thread.add( thisThread, false ); + Thread.add( thisContext ); + if ( Thread.sm_main !is null ) + multiThreadedFlag = true; + return thisThread; +} + +/** + * Registers the calling thread for use with the D Runtime. If this routine + * is called for a thread which is already registered, no action is performed. + * + * NOTE: This routine does not run thread-local static constructors when called. + * If full functionality as a D thread is desired, the following function + * must be called after thread_attachThis: + * + * extern (C) void rt_moduleTlsCtor(); + * + * See_Also: + * $(REF thread_detachThis, core,thread,threadbase) + */ +extern(C) Thread thread_attachThis() +{ + return thread_attachThis_tpl!Thread(); +} + + +version (Windows) +{ + // NOTE: These calls are not safe on Posix systems that use signals to + // perform garbage collection. The suspendHandler uses getThis() + // to get the thread handle so getThis() must be a simple call. + // Mutexes can't safely be acquired inside signal handlers, and + // even if they could, the mutex needed (Thread.slock) is held by + // thread_suspendAll(). So in short, these routines will remain + // Windows-specific. If they are truly needed elsewhere, the + // suspendHandler will need a way to call a version of getThis() + // that only does the TLS lookup without the fancy fallback stuff. + + /// ditto + extern (C) Thread thread_attachByAddr( ThreadID addr ) + { + return thread_attachByAddrB( addr, getThreadStackBottom( addr ) ); + } + + + /// ditto + extern (C) Thread thread_attachByAddrB( ThreadID addr, void* bstack ) + { + GC.disable(); scope(exit) GC.enable(); + + if (auto t = thread_findByAddr(addr).toThread) + return t; + + Thread thisThread = new Thread(); + StackContext* thisContext = &thisThread.m_main; + assert( thisContext == thisThread.m_curr ); + + thisThread.m_addr = addr; + thisContext.bstack = bstack; + thisContext.tstack = thisContext.bstack; + + thisThread.m_isDaemon = true; + + if ( addr == GetCurrentThreadId() ) + { + thisThread.m_hndl = GetCurrentThreadHandle(); + thisThread.tlsRTdataInit(); + Thread.setThis( thisThread ); + } + else + { + thisThread.m_hndl = OpenThreadHandle( addr ); + impersonate_thread(addr, + { + thisThread.tlsRTdataInit(); + Thread.setThis( thisThread ); + }); + } + + Thread.add( thisThread, false ); + Thread.add( thisContext ); + if ( Thread.sm_main !is null ) + multiThreadedFlag = true; + return thisThread; + } +} + + +// Calls the given delegate, passing the current thread's stack pointer to it. +package extern(D) void callWithStackShell(scope callWithStackShellDg fn) nothrow +in (fn) +{ + // The purpose of the 'shell' is to ensure all the registers get + // put on the stack so they'll be scanned. We only need to push + // the callee-save registers. + void *sp = void; + version (GNU) + { + // The generic solution below using a call to __builtin_unwind_init () + // followed by an assignment to sp has two issues: + // 1) On some archs it stores a huge amount of FP and Vector state which + // is not the subject of the scan - and, indeed might produce false + // hits. + // 2) Even on archs like X86, where there are no callee-saved FPRs/VRs there + // tend to be 'holes' in the frame allocations (to deal with alignment) which + // also will contain random data which could produce false positives. + // This solution stores only the integer callee-saved registers. + version (X86) + { + void*[3] regs = void; + asm pure nothrow @nogc + { + "movl %%ebx, %0" : "=m" (regs[0]); + "movl %%esi, %0" : "=m" (regs[1]); + "movl %%edi, %0" : "=m" (regs[2]); + } + sp = cast(void*)®s[0]; + } + else version (X86_64) + { + void*[5] regs = void; + asm pure nothrow @nogc + { + "movq %%rbx, %0" : "=m" (regs[0]); + "movq %%r12, %0" : "=m" (regs[1]); + "movq %%r13, %0" : "=m" (regs[2]); + "movq %%r14, %0" : "=m" (regs[3]); + "movq %%r15, %0" : "=m" (regs[4]); + } + sp = cast(void*)®s[0]; + } + else version (PPC) + { + void*[19] regs = void; + version (Darwin) + enum regname = "r"; + else + enum regname = ""; + static foreach (i; 0 .. regs.length) + {{ + enum int j = 13 + i; // source register + asm pure nothrow @nogc + { + ("stw "~regname~j.stringof~", %0") : "=m" (regs[i]); + } + }} + sp = cast(void*)®s[0]; + } + else version (PPC64) + { + void*[19] regs = void; + version (Darwin) + enum regname = "r"; + else + enum regname = ""; + static foreach (i; 0 .. regs.length) + {{ + enum int j = 13 + i; // source register + asm pure nothrow @nogc + { + ("std "~regname~j.stringof~", %0") : "=m" (regs[i]); + } + }} + sp = cast(void*)®s[0]; + } + else version (AArch64) + { + // Callee-save registers, x19-x28 according to AAPCS64, section + // 5.1.1. Include x29 fp because it optionally can be a callee + // saved reg + size_t[11] regs = void; + // store the registers in pairs + asm pure nothrow @nogc + { + "stp x19, x20, %0" : "=m" (regs[ 0]), "=m" (regs[1]); + "stp x21, x22, %0" : "=m" (regs[ 2]), "=m" (regs[3]); + "stp x23, x24, %0" : "=m" (regs[ 4]), "=m" (regs[5]); + "stp x25, x26, %0" : "=m" (regs[ 6]), "=m" (regs[7]); + "stp x27, x28, %0" : "=m" (regs[ 8]), "=m" (regs[9]); + "str x29, %0" : "=m" (regs[10]); + "mov %0, sp" : "=r" (sp); + } + } + else version (ARM) + { + // Callee-save registers, according to AAPCS, section 5.1.1. + // arm and thumb2 instructions + size_t[8] regs = void; + asm pure nothrow @nogc + { + "stm %0, {r4-r11}" : : "r" (regs.ptr) : "memory"; + "mov %0, sp" : "=r" (sp); + } + } + else + { + __builtin_unwind_init(); + sp = &sp; + } + } + else version (AsmX86_Posix) + { + size_t[3] regs = void; + asm pure nothrow @nogc + { + mov [regs + 0 * 4], EBX; + mov [regs + 1 * 4], ESI; + mov [regs + 2 * 4], EDI; + + mov sp[EBP], ESP; + } + } + else version (AsmX86_Windows) + { + size_t[3] regs = void; + asm pure nothrow @nogc + { + mov [regs + 0 * 4], EBX; + mov [regs + 1 * 4], ESI; + mov [regs + 2 * 4], EDI; + + mov sp[EBP], ESP; + } + } + else version (AsmX86_64_Posix) + { + size_t[5] regs = void; + asm pure nothrow @nogc + { + mov [regs + 0 * 8], RBX; + mov [regs + 1 * 8], R12; + mov [regs + 2 * 8], R13; + mov [regs + 3 * 8], R14; + mov [regs + 4 * 8], R15; + + mov sp[RBP], RSP; + } + } + else version (AsmX86_64_Windows) + { + size_t[7] regs = void; + asm pure nothrow @nogc + { + mov [regs + 0 * 8], RBX; + mov [regs + 1 * 8], RSI; + mov [regs + 2 * 8], RDI; + mov [regs + 3 * 8], R12; + mov [regs + 4 * 8], R13; + mov [regs + 5 * 8], R14; + mov [regs + 6 * 8], R15; + + mov sp[RBP], RSP; + } + } + else version (AArch64) + { + // Callee-save registers, x19-x28 according to AAPCS64, section + // 5.1.1. Include x29 fp because it optionally can be a callee + // saved reg + size_t[11] regs = void; + // store the registers in pairs + asm pure nothrow @nogc + { + /* + stp x19, x20, regs[0]; + stp x21, x22, regs[2]; + stp x23, x24, regs[4]; + stp x25, x26, regs[6]; + stp x27, x28, regs[8]; + str x29, regs[10]; + mov [sp], sp; + */ + } + assert(0, "implement AArch64 inline assembler for callWithStackShell()"); // TODO AArch64 + } + else + { + static assert(false, "Architecture not supported."); + } + + fn(sp); +} + +/** + * Returns the process ID of the calling process, which is guaranteed to be + * unique on the system. This call is always successful. + * + * Example: + * --- + * writefln("Current process id: %s", getpid()); + * --- + */ +version (Posix) +{ + alias getpid = imported!"core.sys.posix.unistd".getpid; +} +else version (Windows) +{ + alias getpid = imported!"core.sys.windows.winbase".GetCurrentProcessId; +} +else + static assert(0, "unsupported os"); + +extern (C) @nogc nothrow +{ + version (CRuntime_Glibc) version = PThread_Getattr_NP; + version (CRuntime_Bionic) version = PThread_Getattr_NP; + version (CRuntime_Musl) version = PThread_Getattr_NP; + version (CRuntime_UClibc) version = PThread_Getattr_NP; + + version (FreeBSD) version = PThread_Attr_Get_NP; + version (NetBSD) version = PThread_Attr_Get_NP; + version (DragonFlyBSD) version = PThread_Attr_Get_NP; + + version (PThread_Getattr_NP) int pthread_getattr_np(pthread_t thread, pthread_attr_t* attr); + version (PThread_Attr_Get_NP) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr); + version (OpenBSD) int pthread_stackseg_np(pthread_t thread, stack_t* sinfo); +} + + +private extern(D) void* getStackTop() nothrow @nogc +{ + version (D_InlineAsm_X86) + asm pure nothrow @nogc { naked; mov EAX, ESP; ret; } + else version (D_InlineAsm_X86_64) + asm pure nothrow @nogc { naked; mov RAX, RSP; ret; } + else version (AArch64) + //asm pure nothrow @nogc { naked; mov x0, SP; ret; } // TODO AArch64 + { + return null; + } + else version (GNU) + return __builtin_frame_address(0); + else + static assert(false, "Architecture not supported."); +} + + +private extern(D) void* getStackBottom() nothrow @nogc +{ + version (Windows) + { + version (D_InlineAsm_X86) + asm pure nothrow @nogc { naked; mov EAX, FS:4; ret; } + else version (D_InlineAsm_X86_64) + asm pure nothrow @nogc + { naked; + mov RAX, 8; + mov RAX, GS:[RAX]; + ret; + } + else version (GNU_InlineAsm) + { + void *bottom; + + version (X86) + asm pure nothrow @nogc { "movl %%fs:4, %0;" : "=r" (bottom); } + else version (X86_64) + asm pure nothrow @nogc { "movq %%gs:8, %0;" : "=r" (bottom); } + else + static assert(false, "Architecture not supported."); + + return bottom; + } + else + static assert(false, "Architecture not supported."); + } + else version (Darwin) + { + import core.sys.darwin.pthread : pthread_get_stackaddr_np; + return pthread_get_stackaddr_np(pthread_self()); + } + else version (PThread_Getattr_NP) + { + pthread_attr_t attr; + void* addr; size_t size; + + pthread_attr_init(&attr); + pthread_getattr_np(pthread_self(), &attr); + pthread_attr_getstack(&attr, &addr, &size); + pthread_attr_destroy(&attr); + static if (isStackGrowingDown) + addr += size; + return addr; + } + else version (PThread_Attr_Get_NP) + { + pthread_attr_t attr; + void* addr; size_t size; + + pthread_attr_init(&attr); + pthread_attr_get_np(pthread_self(), &attr); + pthread_attr_getstack(&attr, &addr, &size); + pthread_attr_destroy(&attr); + static if (isStackGrowingDown) + addr += size; + return addr; + } + else version (OpenBSD) + { + stack_t stk; + + pthread_stackseg_np(pthread_self(), &stk); + return stk.ss_sp; + } + else version (Solaris) + { + stack_t stk; + + thr_stksegment(&stk); + return stk.ss_sp; + } + else + static assert(false, "Platform not supported."); +} + +/** + * Suspend the specified thread and load stack and register information for + * use by thread_scanAll. If the supplied thread is the calling thread, + * stack and register information will be loaded but the thread will not + * be suspended. If the suspend operation fails and the thread is not + * running then it will be removed from the global thread list, otherwise + * an exception will be thrown. + * + * Params: + * t = The thread to suspend. + * + * Throws: + * ThreadError if the suspend operation fails for a running thread. + * Returns: + * Whether the thread is now suspended (true) or terminated (false). + */ +private extern (D) bool suspend( Thread t ) nothrow @nogc +{ + if (!t.isRunning) + { + Thread.remove(t); + return false; + } + + version (Windows) + { + if ( t.m_addr != GetCurrentThreadId() && SuspendThread( t.m_hndl ) == 0xFFFFFFFF ) + { + if ( !t.isRunning ) + { + Thread.remove( t ); + return false; + } + onThreadError( "Unable to suspend thread" ); + } + + CONTEXT context = void; + context.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL; + + if ( !GetThreadContext( t.m_hndl, &context ) ) + onThreadError( "Unable to load thread context" ); + version (X86) + { + if ( !t.m_lock ) + t.m_curr.tstack = cast(void*) context.Esp; + // eax,ebx,ecx,edx,edi,esi,ebp,esp + t.m_reg[0] = context.Eax; + t.m_reg[1] = context.Ebx; + t.m_reg[2] = context.Ecx; + t.m_reg[3] = context.Edx; + t.m_reg[4] = context.Edi; + t.m_reg[5] = context.Esi; + t.m_reg[6] = context.Ebp; + t.m_reg[7] = context.Esp; + } + else version (X86_64) + { + if ( !t.m_lock ) + t.m_curr.tstack = cast(void*) context.Rsp; + // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp + t.m_reg[0] = context.Rax; + t.m_reg[1] = context.Rbx; + t.m_reg[2] = context.Rcx; + t.m_reg[3] = context.Rdx; + t.m_reg[4] = context.Rdi; + t.m_reg[5] = context.Rsi; + t.m_reg[6] = context.Rbp; + t.m_reg[7] = context.Rsp; + // r8,r9,r10,r11,r12,r13,r14,r15 + t.m_reg[8] = context.R8; + t.m_reg[9] = context.R9; + t.m_reg[10] = context.R10; + t.m_reg[11] = context.R11; + t.m_reg[12] = context.R12; + t.m_reg[13] = context.R13; + t.m_reg[14] = context.R14; + t.m_reg[15] = context.R15; + } + else + { + static assert(false, "Architecture not supported." ); + } + } + else version (Darwin) + { + if ( t.m_addr != pthread_self() && thread_suspend( t.m_tmach ) != KERN_SUCCESS ) + { + if ( !t.isRunning ) + { + Thread.remove( t ); + return false; + } + onThreadError( "Unable to suspend thread" ); + } + + version (X86) + { + x86_thread_state32_t state = void; + mach_msg_type_number_t count = x86_THREAD_STATE32_COUNT; + + if ( thread_get_state( t.m_tmach, x86_THREAD_STATE32, &state, &count ) != KERN_SUCCESS ) + onThreadError( "Unable to load thread state" ); + if ( !t.m_lock ) + t.m_curr.tstack = cast(void*) state.esp; + // eax,ebx,ecx,edx,edi,esi,ebp,esp + t.m_reg[0] = state.eax; + t.m_reg[1] = state.ebx; + t.m_reg[2] = state.ecx; + t.m_reg[3] = state.edx; + t.m_reg[4] = state.edi; + t.m_reg[5] = state.esi; + t.m_reg[6] = state.ebp; + t.m_reg[7] = state.esp; + } + else version (X86_64) + { + x86_thread_state64_t state = void; + mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; + + if ( thread_get_state( t.m_tmach, x86_THREAD_STATE64, &state, &count ) != KERN_SUCCESS ) + onThreadError( "Unable to load thread state" ); + if ( !t.m_lock ) + t.m_curr.tstack = cast(void*) state.rsp; + // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp + t.m_reg[0] = state.rax; + t.m_reg[1] = state.rbx; + t.m_reg[2] = state.rcx; + t.m_reg[3] = state.rdx; + t.m_reg[4] = state.rdi; + t.m_reg[5] = state.rsi; + t.m_reg[6] = state.rbp; + t.m_reg[7] = state.rsp; + // r8,r9,r10,r11,r12,r13,r14,r15 + t.m_reg[8] = state.r8; + t.m_reg[9] = state.r9; + t.m_reg[10] = state.r10; + t.m_reg[11] = state.r11; + t.m_reg[12] = state.r12; + t.m_reg[13] = state.r13; + t.m_reg[14] = state.r14; + t.m_reg[15] = state.r15; + } + else version (AArch64) + { + arm_thread_state64_t state = void; + mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT; + + if (thread_get_state(t.m_tmach, ARM_THREAD_STATE64, &state, &count) != KERN_SUCCESS) + onThreadError("Unable to load thread state"); + // TODO: ThreadException here recurses forever! Does it + //still using onThreadError? + //printf("state count %d (expect %d)\n", count ,ARM_THREAD_STATE64_COUNT); + if (!t.m_lock) + t.m_curr.tstack = cast(void*) state.sp; + + t.m_reg[0..29] = state.x; // x0-x28 + t.m_reg[29] = state.fp; // x29 + t.m_reg[30] = state.lr; // x30 + t.m_reg[31] = state.sp; // x31 + t.m_reg[32] = state.pc; + } + else version (ARM) + { + arm_thread_state32_t state = void; + mach_msg_type_number_t count = ARM_THREAD_STATE32_COUNT; + + // Thought this would be ARM_THREAD_STATE32, but that fails. + // Mystery + if (thread_get_state(t.m_tmach, ARM_THREAD_STATE, &state, &count) != KERN_SUCCESS) + onThreadError("Unable to load thread state"); + // TODO: in past, ThreadException here recurses forever! Does it + //still using onThreadError? + //printf("state count %d (expect %d)\n", count ,ARM_THREAD_STATE32_COUNT); + if (!t.m_lock) + t.m_curr.tstack = cast(void*) state.sp; + + t.m_reg[0..13] = state.r; // r0 - r13 + t.m_reg[13] = state.sp; + t.m_reg[14] = state.lr; + t.m_reg[15] = state.pc; + } + else version (PPC) + { + ppc_thread_state_t state = void; + mach_msg_type_number_t count = PPC_THREAD_STATE_COUNT; + + if (thread_get_state(t.m_tmach, PPC_THREAD_STATE, &state, &count) != KERN_SUCCESS) + onThreadError("Unable to load thread state"); + if (!t.m_lock) + t.m_curr.tstack = cast(void*) state.r[1]; + t.m_reg[] = state.r[]; + } + else version (PPC64) + { + ppc_thread_state64_t state = void; + mach_msg_type_number_t count = PPC_THREAD_STATE64_COUNT; + + if (thread_get_state(t.m_tmach, PPC_THREAD_STATE64, &state, &count) != KERN_SUCCESS) + onThreadError("Unable to load thread state"); + if (!t.m_lock) + t.m_curr.tstack = cast(void*) state.r[1]; + t.m_reg[] = state.r[]; + } + else + { + static assert(false, "Architecture not supported." ); + } + } + else version (Solaris) + { + if (t.m_addr != pthread_self()) + { + if (thr_suspend(t.m_addr) != 0) + { + if (!t.isRunning) + { + Thread.remove(t); + return false; + } + onThreadError("Unable to suspend thread"); + } + + static int getLwpStatus(ulong lwpid, out lwpstatus_t status) + { + import core.sys.posix.fcntl : open, O_RDONLY; + import core.sys.posix.unistd : pread, close; + import core.internal.string : unsignedToTempString; + + char[100] path = void; + auto pslice = path[0 .. $]; + immutable n = unsignedToTempString(lwpid); + immutable ndigits = n.length; + + // Construct path "/proc/self/lwp/%u/lwpstatus" + pslice[0 .. 15] = "/proc/self/lwp/"; + pslice = pslice[15 .. $]; + pslice[0 .. ndigits] = n[]; + pslice = pslice[ndigits .. $]; + pslice[0 .. 10] = "/lwpstatus"; + pslice[10] = '\0'; + + // Read in lwpstatus data + int fd = open(path.ptr, O_RDONLY, 0); + if (fd >= 0) + { + while (pread(fd, &status, status.sizeof, 0) == status.sizeof) + { + // Should only attempt to read the thread state once it + // has been stopped by thr_suspend + if (status.pr_flags & PR_STOPPED) + { + close(fd); + return 0; + } + // Give it a chance to stop + thread_yield(); + } + close(fd); + } + return -1; + } + + lwpstatus_t status = void; + if (getLwpStatus(t.m_addr, status) != 0) + onThreadError("Unable to load thread state"); + + version (X86) + { + import core.sys.solaris.sys.regset; // REG_xxx + + if (!t.m_lock) + t.m_curr.tstack = cast(void*) status.pr_reg[REG_ESP]; + // eax,ebx,ecx,edx,edi,esi,ebp,esp + t.m_reg[0] = status.pr_reg[REG_EAX]; + t.m_reg[1] = status.pr_reg[REG_EBX]; + t.m_reg[2] = status.pr_reg[REG_ECX]; + t.m_reg[3] = status.pr_reg[REG_EDX]; + t.m_reg[4] = status.pr_reg[REG_EDI]; + t.m_reg[5] = status.pr_reg[REG_ESI]; + t.m_reg[6] = status.pr_reg[REG_EBP]; + t.m_reg[7] = status.pr_reg[REG_ESP]; + } + else version (X86_64) + { + import core.sys.solaris.sys.regset; // REG_xxx + + if (!t.m_lock) + t.m_curr.tstack = cast(void*) status.pr_reg[REG_RSP]; + // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp + t.m_reg[0] = status.pr_reg[REG_RAX]; + t.m_reg[1] = status.pr_reg[REG_RBX]; + t.m_reg[2] = status.pr_reg[REG_RCX]; + t.m_reg[3] = status.pr_reg[REG_RDX]; + t.m_reg[4] = status.pr_reg[REG_RDI]; + t.m_reg[5] = status.pr_reg[REG_RSI]; + t.m_reg[6] = status.pr_reg[REG_RBP]; + t.m_reg[7] = status.pr_reg[REG_RSP]; + // r8,r9,r10,r11,r12,r13,r14,r15 + t.m_reg[8] = status.pr_reg[REG_R8]; + t.m_reg[9] = status.pr_reg[REG_R9]; + t.m_reg[10] = status.pr_reg[REG_R10]; + t.m_reg[11] = status.pr_reg[REG_R11]; + t.m_reg[12] = status.pr_reg[REG_R12]; + t.m_reg[13] = status.pr_reg[REG_R13]; + t.m_reg[14] = status.pr_reg[REG_R14]; + t.m_reg[15] = status.pr_reg[REG_R15]; + } + else version (SPARC) + { + import core.sys.solaris.sys.procfs : R_SP, R_PC; + + if (!t.m_lock) + t.m_curr.tstack = cast(void*) status.pr_reg[R_SP]; + // g0..g7, o0..o7, l0..l7, i0..i7 + t.m_reg[0 .. 32] = status.pr_reg[0 .. 32]; + // pc + t.m_reg[32] = status.pr_reg[R_PC]; + } + else version (SPARC64) + { + import core.sys.solaris.sys.procfs : R_SP, R_PC; + + if (!t.m_lock) + { + // SPARC V9 has a stack bias of 2047 bytes which must be added to get + // the actual data of the stack frame. + auto tstack = status.pr_reg[R_SP] + 2047; + assert(tstack % 16 == 0); + t.m_curr.tstack = cast(void*) tstack; + } + // g0..g7, o0..o7, l0..l7, i0..i7 + t.m_reg[0 .. 32] = status.pr_reg[0 .. 32]; + // pc + t.m_reg[32] = status.pr_reg[R_PC]; + } + else + { + static assert(false, "Architecture not supported."); + } + } + else if (!t.m_lock) + { + t.m_curr.tstack = getStackTop(); + } + } + else version (Posix) + { + if ( t.m_addr != pthread_self() ) + { + if ( pthread_kill( t.m_addr, suspendSignalNumber ) != 0 ) + { + if ( !t.isRunning ) + { + Thread.remove( t ); + return false; + } + onThreadError( "Unable to suspend thread" ); + } + } + else if ( !t.m_lock ) + { + t.m_curr.tstack = getStackTop(); + } + } + else + static assert(0, "unsupported os"); + return true; +} + +/** + * Runs the necessary operations required before stopping the world. + */ +extern (C) void thread_preStopTheWorld() nothrow { + Thread.slock.lock_nothrow(); +} + +/** + * Suspend all threads but the calling thread for "stop the world" garbage + * collection runs. This function may be called multiple times, and must + * be followed by a matching number of calls to thread_resumeAll before + * processing is resumed. + * + * Throws: + * ThreadError if the suspend operation fails for a running thread. + */ +extern (C) void thread_suspendAll() nothrow +{ + // NOTE: We've got an odd chicken & egg problem here, because while the GC + // is required to call thread_init before calling any other thread + // routines, thread_init may allocate memory which could in turn + // trigger a collection. Thus, thread_suspendAll, thread_scanAll, + // and thread_resumeAll must be callable before thread_init + // completes, with the assumption that no other GC memory has yet + // been allocated by the system, and thus there is no risk of losing + // data if the global thread list is empty. The check of + // Thread.sm_tbeg below is done to ensure thread_init has completed, + // and therefore that calling Thread.getThis will not result in an + // error. For the short time when Thread.sm_tbeg is null, there is + // no reason not to simply call the multithreaded code below, with + // the expectation that the foreach loop will never be entered. + if ( !multiThreadedFlag && Thread.sm_tbeg ) + { + if ( ++suspendDepth == 1 ) + suspend( Thread.getThis() ); + + return; + } + + thread_preStopTheWorld(); + { + if ( ++suspendDepth > 1 ) + return; + + size_t cnt; + bool suspendedSelf; + Thread t = ThreadBase.sm_tbeg.toThread; + while (t) + { + auto tn = t.next.toThread; + if (suspend(t)) + { + if (t is ThreadBase.getThis()) + suspendedSelf = true; + ++cnt; + } + t = tn; + } + + version (Darwin) + {} + else version (Solaris) + {} + else version (Posix) + { + // Subtract own thread if we called suspend() on ourselves. + // For example, suspendedSelf would be false if the current + // thread ran thread_detachThis(). + assert(cnt >= 1); + if (suspendedSelf) + --cnt; + // wait for semaphore notifications + for (; cnt; --cnt) + { + while (sem_wait(&suspendCount) != 0) + { + if (errno != EINTR) + onThreadError("Unable to wait for semaphore"); + errno = 0; + } + } + } + else version (Windows) + { + } + else + static assert(0, "unsupported os"); + } +} + +/** + * Resume the specified thread and unload stack and register information. + * If the supplied thread is the calling thread, stack and register + * information will be unloaded but the thread will not be resumed. If + * the resume operation fails and the thread is not running then it will + * be removed from the global thread list, otherwise an exception will be + * thrown. + * + * Params: + * t = The thread to resume. + * + * Throws: + * ThreadError if the resume fails for a running thread. + */ +private extern (D) void resume(ThreadBase _t) nothrow @nogc +{ + Thread t = _t.toThread; + + version (Windows) + { + if ( t.m_addr != GetCurrentThreadId() && ResumeThread( t.m_hndl ) == 0xFFFFFFFF ) + { + if ( !t.isRunning ) + { + Thread.remove( t ); + return; + } + onThreadError( "Unable to resume thread" ); + } + + if ( !t.m_lock ) + t.m_curr.tstack = t.m_curr.bstack; + t.m_reg[0 .. $] = 0; + } + else version (Darwin) + { + if ( t.m_addr != pthread_self() && thread_resume( t.m_tmach ) != KERN_SUCCESS ) + { + if ( !t.isRunning ) + { + Thread.remove( t ); + return; + } + onThreadError( "Unable to resume thread" ); + } + + if ( !t.m_lock ) + t.m_curr.tstack = t.m_curr.bstack; + t.m_reg[0 .. $] = 0; + } + else version (Solaris) + { + if (t.m_addr != pthread_self() && thr_continue(t.m_addr) != 0) + { + if (!t.isRunning) + { + Thread.remove(t); + return; + } + onThreadError("Unable to resume thread"); + } + + if (!t.m_lock) + t.m_curr.tstack = t.m_curr.bstack; + t.m_reg[0 .. $] = 0; + } + else version (Posix) + { + if ( t.m_addr != pthread_self() ) + { + if ( pthread_kill( t.m_addr, resumeSignalNumber ) != 0 ) + { + if ( !t.isRunning ) + { + Thread.remove( t ); + return; + } + onThreadError( "Unable to resume thread" ); + } + } + else if ( !t.m_lock ) + { + t.m_curr.tstack = t.m_curr.bstack; + } + } + else + static assert(false, "Platform not supported."); +} + + +/** + * Initializes the thread module. This function must be called by the + * garbage collector on startup and before any other thread routines + * are called. + */ +version (CoreDdoc) + extern (C) void thread_init() @nogc nothrow {} +else +extern (C) void thread_init() @nogc nothrow +{ + // NOTE: If thread_init itself performs any allocations then the thread + // routines reserved for garbage collector use may be called while + // thread_init is being processed. However, since no memory should + // exist to be scanned at this point, it is sufficient for these + // functions to detect the condition and return immediately. + + initLowlevelThreads(); + Thread.initLocks(); + + version (Windows) + { + } + else version (Darwin) + { + // thread id different in forked child process + static extern(C) void initChildAfterFork() + { + auto thisThread = Thread.getThis(); + if (!thisThread) + { + // It is possible that runtime was not properly initialized in the current process or thread - + // it may happen after `fork` call when using a dynamically loaded shared library written in D from a multithreaded non-D program. + // In such case getThis will return null. + return; + } + thisThread.m_addr = pthread_self(); + assert( thisThread.m_addr != thisThread.m_addr.init ); + thisThread.m_tmach = pthread_mach_thread_np( thisThread.m_addr ); + assert( thisThread.m_tmach != thisThread.m_tmach.init ); + } + pthread_atfork(null, null, &initChildAfterFork); + } + else version (Solaris) + { + } + else version (Posix) + { + version (OpenBSD) + { + // OpenBSD does not support SIGRTMIN or SIGRTMAX + // Use SIGUSR1 for SIGRTMIN, SIGUSR2 for SIGRTMIN + 1 + // And use 32 for SIGRTMAX (32 is the max signal number on OpenBSD) + enum SIGRTMIN = SIGUSR1; + enum SIGRTMAX = 32; + } + else + { + import core.sys.posix.signal : SIGRTMAX, SIGRTMIN; + } + + if ( suspendSignalNumber == 0 ) + { + suspendSignalNumber = SIGRTMIN; + } + + if ( resumeSignalNumber == 0 ) + { + resumeSignalNumber = SIGRTMIN + 1; + assert(resumeSignalNumber <= SIGRTMAX); + } + int status; + sigaction_t suspend = void; + sigaction_t resume = void; + + // This is a quick way to zero-initialize the structs without using + // memset or creating a link dependency on their static initializer. + (cast(byte*) &suspend)[0 .. sigaction_t.sizeof] = 0; + (cast(byte*) &resume)[0 .. sigaction_t.sizeof] = 0; + + // NOTE: SA_RESTART indicates that system calls should restart if they + // are interrupted by a signal, but this is not available on all + // Posix systems, even those that support multithreading. + static if (__traits(compiles, core.sys.posix.signal.SA_RESTART)) + { + import core.sys.posix.signal : SA_RESTART; + + suspend.sa_flags = SA_RESTART; + } + + suspend.sa_handler = &thread_suspendHandler; + // NOTE: We want to ignore all signals while in this handler, so fill + // sa_mask to indicate this. + status = sigfillset( &suspend.sa_mask ); + assert( status == 0 ); + + // NOTE: Since resumeSignalNumber should only be issued for threads within the + // suspend handler, we don't want this signal to trigger a + // restart. + resume.sa_flags = 0; + resume.sa_handler = &thread_resumeHandler; + // NOTE: We want to ignore all signals while in this handler, so fill + // sa_mask to indicate this. + status = sigfillset( &resume.sa_mask ); + assert( status == 0 ); + + status = sigaction( suspendSignalNumber, &suspend, null ); + assert( status == 0 ); + + status = sigaction( resumeSignalNumber, &resume, null ); + assert( status == 0 ); + + status = sem_init( &suspendCount, 0, 0 ); + assert( status == 0 ); + } + else + static assert(0, "unsupported os"); + _mainThreadStore[] = cast(void[]) __traits(initSymbol, Thread)[]; + Thread.sm_main = attachThread((cast(Thread)_mainThreadStore.ptr).__ctor()); +} + +private alias MainThreadStore = void[__traits(classInstanceSize, Thread)]; +package __gshared align(__traits(classInstanceAlignment, Thread)) MainThreadStore _mainThreadStore; + +/** + * Terminates the thread module. No other thread routine may be called + * afterwards. + */ +extern (C) void thread_term() @nogc nothrow +{ + thread_term_tpl!(Thread)(_mainThreadStore); +} + + +/////////////////////////////////////////////////////////////////////////////// +// Thread Entry Point and Signal Handlers +/////////////////////////////////////////////////////////////////////////////// + + +version (Windows) +{ + private + { + // + // Entry point for Windows threads + // + extern (Windows) uint thread_entryPoint( void* arg ) nothrow + { + Thread obj = cast(Thread) arg; + assert( obj ); + + obj.initDataStorage(); + + Thread.registerThis(obj); + + scope (exit) + { + // allow the GC to clean up any resources it allocated for this thread. + import core.internal.gc.proxy : gc_getProxy; + gc_getProxy().cleanupThread(obj); + + Thread.remove(obj); + obj.destroyDataStorage(); + } + Thread.add(&obj.m_main); + + // NOTE: No GC allocations may occur until the stack pointers have + // been set and Thread.getThis returns a valid reference to + // this thread object (this latter condition is not strictly + // necessary on Windows but it should be followed for the + // sake of consistency). + + // TODO: Consider putting an auto exception object here (using + // alloca) forOutOfMemoryError plus something to track + // whether an exception is in-flight? + + void append( Throwable t ) + { + obj.filterCaughtThrowable(t); + if (t !is null) + obj.m_unhandled = Throwable.chainTogether(obj.m_unhandled, t); + } + + version (D_InlineAsm_X86) + { + asm nothrow @nogc { fninit; } + } + + try + { + rt_moduleTlsCtor(); + try + { + obj.run(); + } + catch ( Throwable t ) + { + append( t ); + } + rt_moduleTlsDtor(); + } + catch ( Throwable t ) + { + append( t ); + } + return 0; + } + + + HANDLE GetCurrentThreadHandle() nothrow @nogc + { + const uint DUPLICATE_SAME_ACCESS = 0x00000002; + + HANDLE curr = GetCurrentThread(), + proc = GetCurrentProcess(), + hndl; + + DuplicateHandle( proc, curr, proc, &hndl, 0, TRUE, DUPLICATE_SAME_ACCESS ); + return hndl; + } + } +} +else version (Posix) +{ + // NOTE: A thread's cancelability state, determined by pthread_setcancelstate, + // can be enabled (the default for new threads) or disabled. + // If a thread has disabled cancelation, then a cancelation request remains + // queued until the thread enables cancelation. If a thread has enabled + // cancelation, then its cancelability type determines when cancelation occurs. + // + // Call these routines when entering/leaving critical sections of the code that + // are not cancellation points. + + extern (C) int thread_cancelDisable() nothrow + { + static if (__traits(compiles, core.sys.posix.pthread.PTHREAD_CANCEL_DISABLE)) + { + import core.sys.posix.pthread : pthread_setcancelstate, PTHREAD_CANCEL_DISABLE; + int oldstate; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); + return oldstate; + } + else + { + return 0; // No thread cancellation on platform + } + } + + extern (C) void thread_cancelRestore(int oldstate) nothrow + { + static if (__traits(compiles, core.sys.posix.pthread.PTHREAD_CANCEL_DISABLE)) + { + import core.sys.posix.pthread : pthread_setcancelstate; + pthread_setcancelstate(oldstate, null); + } + } + + private + { + // + // Entry point for POSIX threads + // + version (CoreDdoc) {} else + extern (C) void* thread_entryPoint( void* arg ) nothrow + { + version (Shared) + { + Thread obj = cast(Thread)(cast(void**)arg)[0]; + auto loadedLibraries = (cast(void**)arg)[1]; + .free(arg); + } + else + { + Thread obj = cast(Thread)arg; + } + assert( obj ); + + // loadedLibraries need to be inherited from parent thread + // before initilizing GC for TLS (rt_tlsgc_init) + version (Shared) + { + externDFunc!("rt.sections_elf_shared.inheritLoadedLibraries", + void function(void*) @nogc nothrow)(loadedLibraries); + } + + obj.initDataStorage(); + + atomicStore!(MemoryOrder.raw)(obj.m_isRunning, true); + + Thread.registerThis(obj); // can only receive signals from here on + + scope (exit) + { + // allow the GC to clean up any resources it allocated for this thread. + import core.internal.gc.proxy : gc_getProxy; + gc_getProxy().cleanupThread(obj); + + Thread.remove(obj); + atomicStore!(MemoryOrder.raw)(obj.m_isRunning, false); + obj.destroyDataStorage(); + } + Thread.add(&obj.m_main); + + static extern (C) void thread_cleanupHandler( void* arg ) nothrow @nogc + { + Thread obj = cast(Thread) arg; + assert( obj ); + + // NOTE: If the thread terminated abnormally, just set it as + // not running and let thread_suspendAll remove it from + // the thread list. This is safer and is consistent + // with the Windows thread code. + atomicStore!(MemoryOrder.raw)(obj.m_isRunning,false); + } + + // NOTE: Using void to skip the initialization here relies on + // knowledge of how pthread_cleanup is implemented. It may + // not be appropriate for all platforms. However, it does + // avoid the need to link the pthread module. If any + // implementation actually requires default initialization + // then pthread_cleanup should be restructured to maintain + // the current lack of a link dependency. + static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup)) + { + import core.sys.posix.pthread : pthread_cleanup; + + pthread_cleanup cleanup = void; + cleanup.push( &thread_cleanupHandler, cast(void*) obj ); + } + else static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup_push)) + { + import core.sys.posix.pthread : pthread_cleanup_push; + + pthread_cleanup_push(&thread_cleanupHandler, cast(void*) obj); + } + else + { + static assert( false, "Platform not supported." ); + } + + // NOTE: No GC allocations may occur until the stack pointers have + // been set and Thread.getThis returns a valid reference to + // this thread object (this latter condition is not strictly + // necessary on Windows but it should be followed for the + // sake of consistency). + + // TODO: Consider putting an auto exception object here (using + // alloca) forOutOfMemoryError plus something to track + // whether an exception is in-flight? + + void append( Throwable t ) + { + obj.filterCaughtThrowable(t); + if (t !is null) + obj.m_unhandled = Throwable.chainTogether(obj.m_unhandled, t); + } + try + { + rt_moduleTlsCtor(); + try + { + obj.run(); + } + catch ( Throwable t ) + { + append( t ); + } + rt_moduleTlsDtor(); + version (Shared) + { + externDFunc!("rt.sections_elf_shared.cleanupLoadedLibraries", + void function() @nogc nothrow)(); + } + } + catch ( Throwable t ) + { + append( t ); + } + + // NOTE: Normal cleanup is handled by scope(exit). + + static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup)) + { + cleanup.pop( 0 ); + } + else static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup_push)) + { + import core.sys.posix.pthread : pthread_cleanup_pop; + + pthread_cleanup_pop( 0 ); + } + + return null; + } + + + // + // Used to track the number of suspended threads + // + __gshared sem_t suspendCount; + + + extern (C) bool thread_preSuspend( void* sp ) nothrow { + // NOTE: Since registers are being pushed and popped from the + // stack, any other stack data used by this function should + // be gone before the stack cleanup code is called below. + Thread obj = Thread.getThis(); + if (obj is null) + { + return false; + } + + if ( !obj.m_lock ) + { + obj.m_curr.tstack = sp; + } + + return true; + } + + extern (C) bool thread_postSuspend() nothrow { + Thread obj = Thread.getThis(); + if (obj is null) + { + return false; + } + + if ( !obj.m_lock ) + { + obj.m_curr.tstack = obj.m_curr.bstack; + } + + return true; + } + + extern (C) void thread_suspendHandler( int sig ) nothrow + in + { + assert( sig == suspendSignalNumber ); + } + do + { + void op(void* sp) nothrow + { + int cancel_state = thread_cancelDisable(); + scope(exit) thread_cancelRestore(cancel_state); + + bool supported = thread_preSuspend(getStackTop()); + assert(supported, "Tried to suspend a detached thread!"); + + scope(exit) + { + supported = thread_postSuspend(); + assert(supported, "Tried to suspend a detached thread!"); + } + + sigset_t sigres = void; + int status; + + status = sigfillset( &sigres ); + assert( status == 0 ); + + status = sigdelset( &sigres, resumeSignalNumber ); + assert( status == 0 ); + + status = sem_post( &suspendCount ); + assert( status == 0 ); + + sigsuspend( &sigres ); + } + callWithStackShell(&op); + } + + + extern (C) void thread_resumeHandler( int sig ) nothrow + in + { + assert( sig == resumeSignalNumber ); + } + do + { + + } + } +} +else +{ + // NOTE: This is the only place threading versions are checked. If a new + // version is added, the module code will need to be searched for + // places where version-specific code may be required. This can be + // easily accomlished by searching for 'Windows' or 'Posix'. + static assert( false, "Unknown threading implementation." ); +} + +// +// exposed by compiler runtime +// +extern (C) void rt_moduleTlsCtor(); +extern (C) void rt_moduleTlsDtor(); + + +// regression test for Issue 13416 +version (FreeBSD) unittest +{ + static void loop() + { + pthread_attr_t attr; + pthread_attr_init(&attr); + auto thr = pthread_self(); + foreach (i; 0 .. 50) + pthread_attr_get_np(thr, &attr); + pthread_attr_destroy(&attr); + } + + auto thr = new Thread(&loop).start(); + foreach (i; 0 .. 50) + { + thread_suspendAll(); + thread_resumeAll(); + } + thr.join(); +} + +version (DragonFlyBSD) unittest +{ + static void loop() + { + pthread_attr_t attr; + pthread_attr_init(&attr); + auto thr = pthread_self(); + foreach (i; 0 .. 50) + pthread_attr_get_np(thr, &attr); + pthread_attr_destroy(&attr); + } + + auto thr = new Thread(&loop).start(); + foreach (i; 0 .. 50) + { + thread_suspendAll(); + thread_resumeAll(); + } + thr.join(); +} + + +/////////////////////////////////////////////////////////////////////////////// +// lowlovel threading support +/////////////////////////////////////////////////////////////////////////////// + +private +{ + version (Windows): + // If the runtime is dynamically loaded as a DLL, there is a problem with + // threads still running when the DLL is supposed to be unloaded: + // + // - with the VC runtime starting with VS2015 (i.e. using the Universal CRT) + // a thread created with _beginthreadex increments the DLL reference count + // and decrements it when done, so that the DLL is no longer unloaded unless + // all the threads have terminated. With the DLL reference count held up + // by a thread that is only stopped by a signal from a static destructor or + // the termination of the runtime will cause the DLL to never be unloaded. + // + // - with the DigitalMars runtime and VC runtime up to VS2013, the thread + // continues to run, but crashes once the DLL is unloaded from memory as + // the code memory is no longer accessible. Stopping the threads is not possible + // from within the runtime termination as it is invoked from + // DllMain(DLL_PROCESS_DETACH) holding a lock that prevents threads from + // terminating. + // + // Solution: start a watchdog thread that keeps the DLL reference count above 0 and + // checks it periodically. If it is equal to 1 (plus the number of started threads), no + // external references to the DLL exist anymore, threads can be stopped + // and runtime termination and DLL unload can be invoked via FreeLibraryAndExitThread. + // Note: runtime termination is then performed by a different thread than at startup. + // + // Note: if the DLL is never unloaded, process termination kills all threads + // and signals their handles before unconditionally calling DllMain(DLL_PROCESS_DETACH). + + import core.sys.windows.dll : dll_getRefCount; + import core.sys.windows.winbase : FreeLibraryAndExitThread, GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, GetModuleHandleExW; + import core.sys.windows.windef : HMODULE; + + version (CRuntime_Microsoft) + extern(C) extern __gshared ubyte msvcUsesUCRT; // from rt/msvc.d + extern(C) extern __gshared void* __ImageBase; // symbol at the beginning of module, added by linker + enum HMODULE runtimeModule = &__ImageBase; + + /// set during termination of a DLL on Windows, i.e. while executing DllMain(DLL_PROCESS_DETACH) + public __gshared bool thread_DLLProcessDetaching; + + __gshared ThreadID ll_dllMonitorThread; + + int ll_countLowLevelThreadsWithDLLUnloadCallback(HMODULE hMod) nothrow + { + lowlevelLock.lock_nothrow(); + scope(exit) lowlevelLock.unlock_nothrow(); + + int cnt = 0; + foreach (i; 0 .. ll_nThreads) + if (ll_pThreads[i].cbDllUnload && ll_pThreads[i].hMod == hMod) + cnt++; + return cnt; + } + + bool ll_dllHasExternalReferences(HMODULE hMod) nothrow + { + int unloadCallbacks = ll_countLowLevelThreadsWithDLLUnloadCallback(hMod); + int internalReferences = hMod != runtimeModule ? unloadCallbacks + : (ll_dllMonitorThread ? 1 : 0) + (msvcUsesUCRT ? unloadCallbacks : 0); + int refcnt = dll_getRefCount(hMod); + return refcnt > internalReferences; + } + + void notifyUnloadLowLevelThreads(HMODULE hMod) nothrow + { + HMODULE toFree; + for (;;) + { + ThreadID tid; + void delegate() nothrow cbDllUnload; + { + lowlevelLock.lock_nothrow(); + scope(exit) lowlevelLock.unlock_nothrow(); + + foreach (i; 0 .. ll_nThreads) + if (ll_pThreads[i].cbDllUnload && ll_pThreads[i].hMod == hMod) + { + if (!toFree) + toFree = ll_getModuleHandle(hMod, true); // keep the module alive until the callback returns + cbDllUnload = ll_pThreads[i].cbDllUnload; + tid = ll_pThreads[i].tid; + break; + } + } + if (!cbDllUnload) + break; + cbDllUnload(); // must wait for thread termination + assert(!findLowLevelThread(tid)); + } + if (toFree) + FreeLibrary(toFree); + } + + private void monitorDLLRefCnt() nothrow + { + // this thread keeps the DLL alive until all external references are gone + // (including those from DLLs using druntime in a shared DLL) + while (ll_dllHasExternalReferences(runtimeModule)) + { + // find and unload module that only has internal references left + HMODULE hMod; + { + lowlevelLock.lock_nothrow(); + scope(exit) lowlevelLock.unlock_nothrow(); + + foreach (i; 0 .. ll_nThreads) + if (ll_pThreads[i].cbDllUnload && ll_pThreads[i].hMod != runtimeModule) + if (!ll_dllHasExternalReferences(ll_pThreads[i].hMod)) + { + hMod = ll_pThreads[i].hMod; + break; + } + } + if (hMod) + notifyUnloadLowLevelThreads(hMod); + else + Thread.sleep(100.msecs); + } + + notifyUnloadLowLevelThreads(runtimeModule); + + // the current thread will be terminated without cleanup within the thread + ll_removeThread(GetCurrentThreadId()); + + FreeLibraryAndExitThread(runtimeModule, 0); + } + + HMODULE ll_getModuleHandle(void* funcptr, bool addref = false) nothrow @nogc + { + HMODULE hmod; + DWORD refflag = addref ? 0 : GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT; + if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | refflag, + cast(const(wchar)*) funcptr, &hmod)) + return null; + return hmod; + } + + bool ll_startDLLUnloadThread() nothrow @nogc + { + if (ll_dllMonitorThread !is ThreadID.init) + return true; + + // if a thread is created from a DLL, the MS runtime (starting with VC2015) increments the DLL reference count + // to avoid the DLL being unloaded while the thread is still running. Mimick this behavior here for all + // runtimes not doing this + bool needRef = !msvcUsesUCRT; + if (needRef) + ll_getModuleHandle(runtimeModule, true); + + // the monitor thread must be a low-level thread so the runtime does not attach to it + ll_dllMonitorThread = createLowLevelThread(() { monitorDLLRefCnt(); }); + return ll_dllMonitorThread != ThreadID.init; + } +} + +/** + * Create a thread not under control of the runtime, i.e. TLS module constructors are + * not run and the GC does not suspend it during a collection. + * + * Params: + * dg = delegate to execute in the created thread. + * stacksize = size of the stack of the created thread. The default of 0 will select the + * platform-specific default size. + * cbDllUnload = Windows only: if running in a dynamically loaded DLL, this delegate will be called + * if the DLL is supposed to be unloaded, but the thread is still running. + * The thread must be terminated via `joinLowLevelThread` by the callback. + * + * Returns: the platform specific thread ID of the new thread. If an error occurs, `ThreadID.init` + * is returned. + */ +ThreadID createLowLevelThread(void delegate() nothrow dg, uint stacksize = 0, + void delegate() nothrow cbDllUnload = null) nothrow @nogc +{ + static struct Context + { + void delegate() nothrow dg; + version (Windows) + HMODULE cbMod; + } + auto context = cast(Context*)malloc(Context.sizeof); + scope(exit) free(context); + context.dg = dg; + + ThreadID tid; + version (Windows) + { + // the thread won't start until after the DLL is unloaded + if (thread_DLLProcessDetaching) + return ThreadID.init; + context.cbMod = cbDllUnload ? ll_getModuleHandle(cbDllUnload.funcptr) : null; + if (context.cbMod) + { + int refcnt = dll_getRefCount(context.cbMod); + if (refcnt < 0) + { + // not a dynamically loaded DLL, so never unloaded + cbDllUnload = null; + context.cbMod = null; + } + if (refcnt == 0) + return ThreadID.init; // createLowLevelThread called while DLL is unloading + } + + static extern (Windows) uint thread_lowlevelEntry(void* ctx) nothrow + { + auto context = *cast(Context*)ctx; + free(ctx); + + context.dg(); + + ll_removeThread(GetCurrentThreadId()); + if (context.cbMod && context.cbMod != runtimeModule) + FreeLibrary(context.cbMod); + return 0; + } + + // see Thread.start() for why thread is created in suspended state + HANDLE hThread = cast(HANDLE) _beginthreadex(null, stacksize, &thread_lowlevelEntry, + context, CREATE_SUSPENDED, &tid); + if (!hThread) + return ThreadID.init; + } + + lowlevelLock.lock_nothrow(); + scope(exit) lowlevelLock.unlock_nothrow(); + + ll_nThreads++; + ll_pThreads = cast(ll_ThreadData*)realloc(ll_pThreads, ll_ThreadData.sizeof * ll_nThreads); + ll_pThreads[ll_nThreads - 1] = ll_ThreadData.init; + + version (Windows) + { + ll_pThreads[ll_nThreads - 1].tid = tid; + // ignore callback if not a dynamically loaded DLL + if (cbDllUnload) + { + ll_pThreads[ll_nThreads - 1].cbDllUnload = cbDllUnload; + ll_pThreads[ll_nThreads - 1].hMod = context.cbMod; + if (context.cbMod != runtimeModule) + ll_getModuleHandle(context.cbMod, true); // increment ref count + } + + if (ResumeThread(hThread) == -1) + onThreadError("Error resuming thread"); + CloseHandle(hThread); + + if (cbDllUnload) + ll_startDLLUnloadThread(); + } + else version (Posix) + { + static extern (C) void* thread_lowlevelEntry(void* ctx) nothrow + { + auto context = *cast(Context*)ctx; + free(ctx); + + context.dg(); + ll_removeThread(pthread_self()); + return null; + } + + size_t stksz = adjustStackSize(stacksize); + + pthread_attr_t attr; + + int rc; + if ((rc = pthread_attr_init(&attr)) != 0) + return ThreadID.init; + if (stksz && (rc = pthread_attr_setstacksize(&attr, stksz)) != 0) + return ThreadID.init; + if ((rc = pthread_create(&tid, &attr, &thread_lowlevelEntry, context)) != 0) + return ThreadID.init; + rc = pthread_attr_destroy(&attr); + assert(rc == 0); + + ll_pThreads[ll_nThreads - 1].tid = tid; + } + else + static assert(0, "unsupported os"); + context = null; // free'd in thread + return tid; +} + +/** + * Wait for a thread created with `createLowLevelThread` to terminate. + * + * Note: In a Windows DLL, if this function is called via DllMain with + * argument DLL_PROCESS_DETACH, the thread is terminated forcefully + * without proper cleanup as a deadlock would happen otherwise. + * + * Params: + * tid = the thread ID returned by `createLowLevelThread`. + */ +void joinLowLevelThread(ThreadID tid) nothrow @nogc +{ + version (Windows) + { + HANDLE handle = OpenThreadHandle(tid); + if (!handle) + return; + + if (thread_DLLProcessDetaching) + { + // When being called from DllMain/DLL_DETACH_PROCESS, threads cannot stop + // due to the loader lock being held by the current thread. + // On the other hand, the thread must not continue to run as it will crash + // if the DLL is unloaded. The best guess is to terminate it immediately. + TerminateThread(handle, 1); + WaitForSingleObject(handle, 10); // give it some time to terminate, but don't wait indefinitely + } + else + WaitForSingleObject(handle, INFINITE); + CloseHandle(handle); + } + else version (Posix) + { + if (pthread_join(tid, null) != 0) + onThreadError("Unable to join thread"); + } + else + static assert(0, "unsupported os"); +} + +nothrow @nogc unittest +{ + struct TaskWithContect + { + shared int n = 0; + void run() nothrow + { + n.atomicOp!"+="(1); + } + } + TaskWithContect task; + + ThreadID[8] tids; + for (int i = 0; i < tids.length; i++) + { + tids[i] = createLowLevelThread(&task.run); + assert(tids[i] != ThreadID.init); + } + + for (int i = 0; i < tids.length; i++) + joinLowLevelThread(tids[i]); + + assert(task.n == tids.length); +} + +version (Posix) +private size_t adjustStackSize(size_t sz) nothrow @nogc +{ + if (sz == 0) + return 0; + + // stack size must be at least PTHREAD_STACK_MIN for most platforms. + if (PTHREAD_STACK_MIN > sz) + sz = PTHREAD_STACK_MIN; + + version (CRuntime_Glibc) + { + // On glibc, TLS uses the top of the stack, so add its size to the requested size + sz += externDFunc!("rt.sections_elf_shared.sizeOfTLS", + size_t function() @nogc nothrow)(); + } + + // stack size must be a multiple of pageSize + sz = ((sz + pageSize - 1) & ~(pageSize - 1)); + + return sz; +} From 4d58430848af20d794c5ac07fff59b2ae10853f0 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Wed, 29 Apr 2026 11:34:50 +0300 Subject: [PATCH 02/32] osthreadposix and windows files added --- druntime/src/core/thread/{osthread/package.d => osthread.d} | 0 druntime/src/core/thread/{osthread => }/posix_impl.d | 0 druntime/src/core/thread/{osthread => }/windows_impl.d | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename druntime/src/core/thread/{osthread/package.d => osthread.d} (100%) rename druntime/src/core/thread/{osthread => }/posix_impl.d (100%) rename druntime/src/core/thread/{osthread => }/windows_impl.d (100%) diff --git a/druntime/src/core/thread/osthread/package.d b/druntime/src/core/thread/osthread.d similarity index 100% rename from druntime/src/core/thread/osthread/package.d rename to druntime/src/core/thread/osthread.d diff --git a/druntime/src/core/thread/osthread/posix_impl.d b/druntime/src/core/thread/posix_impl.d similarity index 100% rename from druntime/src/core/thread/osthread/posix_impl.d rename to druntime/src/core/thread/posix_impl.d diff --git a/druntime/src/core/thread/osthread/windows_impl.d b/druntime/src/core/thread/windows_impl.d similarity index 100% rename from druntime/src/core/thread/osthread/windows_impl.d rename to druntime/src/core/thread/windows_impl.d From c14a699c069c2a435bdafc8531381bca47111614 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Wed, 29 Apr 2026 12:32:02 +0300 Subject: [PATCH 03/32] posix_impl replaced from osthread --- druntime/src/core/thread/posix_impl.d | 3229 ++++++++++++++++++++++++- 1 file changed, 3201 insertions(+), 28 deletions(-) diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index a54442b3f13c..f42f6a9c7409 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -1,57 +1,3230 @@ /** - * The thread module provides support for thread creation and management. + * The osthread module provides low-level, OS-dependent code + * for thread creation and management. * * Copyright: Copyright Sean Kelly 2005 - 2012. * License: Distributed under the * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Sean Kelly, Walter Bright, Alex Rønne Petersen, Martin Nowak - * Source: $(DRUNTIMESRC core/thread/package.d) + * Source: $(DRUNTIMESRC core/thread/osthread.d) */ -module core.thread; +module core.thread.osthread; -public import core.time; -public import core.thread.fiber; -public import core.thread.osthread; -public import core.thread.threadbase; -public import core.thread.threadgroup; -public import core.thread.types; -public import core.thread.context; +import core.atomic; +import core.exception : onOutOfMemoryError; +import core.internal.traits : externDFunc; +import core.memory : GC, pageSize; +import core.thread.context; +import core.thread.threadbase; +import core.thread.types; +import core.time; +/////////////////////////////////////////////////////////////////////////////// +// Platform Detection and Memory Allocation +/////////////////////////////////////////////////////////////////////////////// -// this test is here to avoid a cyclic dependency between -// core.thread and core.atomic -@system unittest +version (OSX) + version = Darwin; +else version (iOS) + version = Darwin; +else version (TVOS) + version = Darwin; +else version (WatchOS) + version = Darwin; + +version (D_InlineAsm_X86) +{ + version (Windows) + version = AsmX86_Windows; + else version (Posix) + version = AsmX86_Posix; +} +else version (D_InlineAsm_X86_64) +{ + version (Windows) + { + version = AsmX86_64_Windows; + } + else version (Posix) + { + version = AsmX86_64_Posix; + } +} + +version (Windows) +{ + import core.stdc.stdint : uintptr_t; // for _beginthreadex decl below + import core.stdc.stdlib : free, malloc, realloc; + import core.sys.windows.basetsd /+: HANDLE+/; + import core.sys.windows.threadaux /+: getThreadStackBottom, impersonate_thread, OpenThreadHandle+/; + import core.sys.windows.winbase /+: CloseHandle, CREATE_SUSPENDED, DuplicateHandle, GetCurrentThread, + GetCurrentThreadId, GetCurrentProcess, GetExitCodeThread, GetSystemInfo, GetThreadContext, + GetThreadPriority, INFINITE, ResumeThread, SetThreadPriority, Sleep, STILL_ACTIVE, + SuspendThread, SwitchToThread, SYSTEM_INFO, THREAD_PRIORITY_IDLE, THREAD_PRIORITY_NORMAL, + THREAD_PRIORITY_TIME_CRITICAL, WAIT_OBJECT_0, WaitForSingleObject+/; + import core.sys.windows.windef /+: TRUE+/; + import core.sys.windows.winnt /+: CONTEXT, CONTEXT_CONTROL, CONTEXT_INTEGER+/; + + private extern (Windows) alias btex_fptr = uint function(void*); + private extern (C) uintptr_t _beginthreadex(void*, uint, btex_fptr, void*, uint, uint*) nothrow @nogc; +} +else version (Posix) +{ + static import core.sys.posix.pthread; + static import core.sys.posix.signal; + import core.stdc.errno : EINTR, errno; + import core.sys.posix.pthread : pthread_atfork, pthread_attr_destroy, pthread_attr_getstack, pthread_attr_init, + pthread_attr_setstacksize, pthread_create, pthread_detach, pthread_getschedparam, pthread_join, pthread_self, + pthread_setschedparam, sched_get_priority_max, sched_get_priority_min, sched_param, sched_yield; + import core.sys.posix.semaphore : sem_init, sem_post, sem_t, sem_wait; + import core.sys.posix.signal : pthread_kill, sigaction, sigaction_t, sigdelset, sigfillset, sigset_t, sigsuspend, + SIGUSR1, stack_t; + import core.sys.posix.stdlib : free, malloc, realloc; + import core.sys.posix.sys.types : pthread_attr_t, pthread_key_t, pthread_t; + import core.sys.posix.time : nanosleep, timespec; + + version (Darwin) + { + // Use macOS threads for suspend/resume + import core.sys.darwin.mach.kern_return : KERN_SUCCESS; + import core.sys.darwin.mach.port : mach_port_t; + import core.sys.darwin.mach.thread_act : mach_msg_type_number_t, + thread_get_state, thread_resume, thread_suspend; + import core.sys.darwin.pthread : pthread_mach_thread_np; + version (X86) + { + import core.sys.darwin.mach.thread_act : + x86_THREAD_STATE32, x86_THREAD_STATE32_COUNT, x86_thread_state32_t; + } + else version (X86_64) + { + import core.sys.darwin.mach.thread_act : + x86_THREAD_STATE64, x86_THREAD_STATE64_COUNT, x86_thread_state64_t; + } + else version (AArch64) + { + import core.sys.darwin.mach.thread_act : + ARM_THREAD_STATE64, ARM_THREAD_STATE64_COUNT, arm_thread_state64_t; + } + else version (PPC) + { + import core.sys.darwin.mach.thread_act : + PPC_THREAD_STATE, PPC_THREAD_STATE_COUNT, ppc_thread_state_t; + } + else version (PPC64) + { + import core.sys.darwin.mach.thread_act : + PPC_THREAD_STATE64, PPC_THREAD_STATE64_COUNT, ppc_thread_state64_t; + } + } + else version (Solaris) + { + // Use Solaris threads for suspend/resume + import core.sys.posix.sys.wait : idtype_t; + import core.sys.solaris.sys.priocntl : PC_CLNULL, PC_GETCLINFO, PC_GETPARMS, PC_SETPARMS, pcinfo_t, pcparms_t, priocntl; + import core.sys.solaris.sys.types : P_MYID, pri_t; + import core.sys.solaris.thread : thr_stksegment, thr_suspend, thr_continue; + import core.sys.solaris.sys.procfs : PR_STOPPED, lwpstatus_t; + } + else + { + // Use POSIX threads for suspend/resume + } +} +else + static assert(0, "unsupported operating system"); + +version (GNU) +{ + import gcc.builtins; +} + +/** + * Hook for whatever EH implementation is used to save/restore some data + * per stack. + * + * Params: + * newContext = The return value of the prior call to this function + * where the stack was last swapped out, or null when a fiber stack + * is switched in for the first time. + */ +private extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc; + +version (DigitalMars) +{ + version (Windows) + { + extern(D) void* swapContext(void* newContext) nothrow @nogc + { + return _d_eh_swapContext(newContext); + } + } + else + { + extern(C) void* _d_eh_swapContextDwarf(void* newContext) nothrow @nogc; + + extern(D) void* swapContext(void* newContext) nothrow @nogc + { + /* Detect at runtime which scheme is being used. + * Eventually, determine it statically. + */ + static int which = 0; + final switch (which) + { + case 0: + { + assert(newContext == null); + auto p = _d_eh_swapContext(newContext); + auto pdwarf = _d_eh_swapContextDwarf(newContext); + if (p) + { + which = 1; + return p; + } + else if (pdwarf) + { + which = 2; + return pdwarf; + } + return null; + } + case 1: + return _d_eh_swapContext(newContext); + case 2: + return _d_eh_swapContextDwarf(newContext); + } + } + } +} +else +{ + extern(D) void* swapContext(void* newContext) nothrow @nogc + { + return _d_eh_swapContext(newContext); + } +} + +/** + * This class encapsulates all threading functionality for the D + * programming language. As thread manipulation is a required facility + * for garbage collection, all user threads should derive from this + * class, and instances of this class should never be explicitly deleted. + * A new thread may be created using either derivation or composition, as + * in the following example. + */ +version (CoreDdoc) +class Thread : ThreadBase +{ + /** + * Initializes a thread object which is associated with a static + * D function. + * + * Params: + * fn = The thread function. + * sz = The stack size for this thread. + * + * In: + * fn must not be null. + */ + this( void function() fn, size_t sz = 0 ) @safe pure nothrow @nogc + { + } + + + /** + * Initializes a thread object which is associated with a dynamic + * D function. + * + * Params: + * dg = The thread function. + * sz = The stack size for this thread. + * + * In: + * dg must not be null. + */ + this( void delegate() dg, size_t sz = 0 ) @safe pure nothrow @nogc + { + } + + package this( size_t sz = 0 ) @safe pure nothrow @nogc + { + } + + /** + * Cleans up any remaining resources used by this object. + */ + ~this() nothrow @nogc + { + } + + // + // Thread entry point. Invokes the function or delegate passed on + // construction (if any). + // + private final void run() + { + } + + /** + * Provides a reference to the calling thread. + * + * Returns: + * The thread object representing the calling thread. The result of + * deleting this object is undefined. If the current thread is not + * attached to the runtime, a null reference is returned. + */ + static Thread getThis() @safe nothrow @nogc + { + return null; + } + + /// + override final void[] savedRegisters() nothrow @nogc + { + return null; + } + + /** + * Starts the thread and invokes the function or delegate passed upon + * construction. + * + * In: + * This routine may only be called once per thread instance. + * + * Throws: + * ThreadException if the thread fails to start. + */ + final Thread start() nothrow + { + return null; + } + + /** + * Waits for this thread to complete. If the thread terminated as the + * result of an unhandled exception, this exception will be rethrown. + * + * Params: + * rethrow = Rethrow any unhandled exception which may have caused this + * thread to terminate. + * + * Throws: + * ThreadException if the operation fails. + * Any exception not handled by the joined thread. + * + * Returns: + * Any exception not handled by this thread if rethrow = false, null + * otherwise. + */ + override final Throwable join( bool rethrow = true ) + { + return null; + } + + /** + * The minimum scheduling priority that may be set for a thread. On + * systems where multiple scheduling policies are defined, this value + * represents the minimum valid priority for the scheduling policy of + * the process. + */ + @property static int PRIORITY_MIN() @nogc nothrow pure @trusted + { + return 0; + } + + /** + * The maximum scheduling priority that may be set for a thread. On + * systems where multiple scheduling policies are defined, this value + * represents the maximum valid priority for the scheduling policy of + * the process. + */ + @property static const(int) PRIORITY_MAX() @nogc nothrow pure @trusted + { + return 0; + } + + /** + * The default scheduling priority that is set for a thread. On + * systems where multiple scheduling policies are defined, this value + * represents the default priority for the scheduling policy of + * the process. + */ + @property static int PRIORITY_DEFAULT() @nogc nothrow pure @trusted + { + return 0; + } + + /** + * Gets the scheduling priority for the associated thread. + * + * Note: Getting the priority of a thread that already terminated + * might return the default priority. + * + * Returns: + * The scheduling priority of this thread. + */ + final @property int priority() + { + return 0; + } + + /** + * Sets the scheduling priority for the associated thread. + * + * Note: Setting the priority of a thread that already terminated + * might have no effect. + * + * Params: + * val = The new scheduling priority of this thread. + */ + final @property void priority( int val ) + { + } + + /** + * Tests whether this thread is running. + * + * Returns: + * true if the thread is running, false if not. + */ + override final @property bool isRunning() nothrow @nogc + { + return false; + } + + /** + * Suspends the calling thread for at least the supplied period. This may + * result in multiple OS calls if period is greater than the maximum sleep + * duration supported by the operating system. + * + * Params: + * val = The minimum duration the calling thread should be suspended. + * + * In: + * period must be non-negative. + * + * Example: + * ------------------------------------------------------------------------ + * + * Thread.sleep( dur!("msecs")( 50 ) ); // sleep for 50 milliseconds + * Thread.sleep( dur!("seconds")( 5 ) ); // sleep for 5 seconds + * + * ------------------------------------------------------------------------ + */ + static void sleep( Duration val ) @nogc nothrow @trusted + { + } + + /** + * Forces a context switch to occur away from the calling thread. + */ + static void yield() @nogc nothrow + { + } +} + +version (CoreDdoc) {} else +class Thread : ThreadBase +{ + version (Windows) + { + private HANDLE m_hndl; + } + + version (Posix) + { + private shared bool m_isRunning; + } + + version (Darwin) + { + private mach_port_t m_tmach; + } + + version (Solaris) + { + private __gshared bool m_isRTClass; + } + + version (Windows) + { + alias TLSKey = uint; + } + else version (Posix) + { + alias TLSKey = pthread_key_t; + } + else + static assert(0, "unsupported os"); + + this( void function() fn, size_t sz = 0 ) @safe pure nothrow @nogc + { + super(fn, sz); + } + + this( void delegate() dg, size_t sz = 0 ) @safe pure nothrow @nogc + { + super(dg, sz); + } + + package this( size_t sz = 0 ) @safe pure nothrow @nogc + { + super(sz); + } + + ~this() nothrow @nogc + { + if (super.destructBeforeDtor()) + return; + + version (Windows) + { + m_addr = m_addr.init; + CloseHandle( m_hndl ); + m_hndl = m_hndl.init; + } + else version (Posix) + { + if (m_addr != m_addr.init) + pthread_detach( m_addr ); + m_addr = m_addr.init; + version (Darwin) + { + m_tmach = m_tmach.init; + } + } + else + static assert(0, "unsupported OS"); + } + + private final void run() + { + super.run(); + } + + static Thread getThis() @safe nothrow @nogc + { + return ThreadBase.getThis().toThread; + } + + version (Windows) + { + version (X86) + { + uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax + } + else version (X86_64) + { + ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax + // r8,r9,r10,r11,r12,r13,r14,r15 + } + else + { + static assert(false, "Architecture not supported." ); + } + } + else version (Darwin) + { + version (X86) + { + uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax + } + else version (X86_64) + { + ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax + // r8,r9,r10,r11,r12,r13,r14,r15 + } + else version (AArch64) + { + ulong[33] m_reg; // x0-x31, pc + } + else version (ARM) + { + uint[16] m_reg; // r0-r15 + } + else version (PPC) + { + // Make the assumption that we only care about non-fp and non-vr regs. + // ??? : it seems plausible that a valid address can be copied into a VR. + uint[32] m_reg; // r0-31 + } + else version (PPC64) + { + // As above. + ulong[32] m_reg; // r0-31 + } + else + { + static assert(false, "Architecture not supported." ); + } + } + else version (Solaris) + { + version (X86) + { + uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax + } + else version (X86_64) + { + ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax + // r8,r9,r10,r11,r12,r13,r14,r15 + } + else version (SPARC) + { + int[33] m_reg; // g0-7, o0-7, l0-7, i0-7, pc + } + else version (SPARC64) + { + long[33] m_reg; // g0-7, o0-7, l0-7, i0-7, pc + } + else + { + static assert(false, "Architecture not supported." ); + } + } + + override final void[] savedRegisters() nothrow @nogc + { + version (Windows) + { + return m_reg; + } + else version (Darwin) + { + return m_reg; + } + else version (Solaris) + { + return m_reg; + } + else + { + return null; + } + } + + final Thread start() nothrow + in + { + assert( !next && !prev ); + } + do + { + auto wasThreaded = multiThreadedFlag; + multiThreadedFlag = true; + scope( failure ) + { + if ( !wasThreaded ) + multiThreadedFlag = false; + } + + version (Windows) + { + // NOTE: If a thread is just executing DllMain() + // while another thread is started here, it holds an OS internal + // lock that serializes DllMain with CreateThread. As the code + // might request a synchronization on slock (e.g. in thread_findByAddr()), + // we cannot hold that lock while creating the thread without + // creating a deadlock + // + // Solution: Create the thread in suspended state and then + // add and resume it with slock acquired + assert(m_sz <= uint.max, "m_sz must be less than or equal to uint.max"); + m_hndl = cast(HANDLE) _beginthreadex( null, cast(uint) m_sz, &thread_entryPoint, cast(void*) this, CREATE_SUSPENDED, &m_addr ); + if ( cast(size_t) m_hndl == 0 ) + onThreadError( "Error creating thread" ); + } + else version (Posix) + { + size_t stksz = adjustStackSize( m_sz ); + + pthread_attr_t attr; + + if ( pthread_attr_init( &attr ) ) + onThreadError( "Error initializing thread attributes" ); + if ( stksz && pthread_attr_setstacksize( &attr, stksz ) ) + onThreadError( "Error initializing thread stack size" ); + } + else + static assert(0, "unsupported OS"); + + slock.lock_nothrow(); + scope(exit) slock.unlock_nothrow(); + { + incrementAboutToStart(this); + + version (Windows) + { + if ( ResumeThread( m_hndl ) == -1 ) + onThreadError( "Error resuming thread" ); + } + else version (Posix) + { + // NOTE: This is also set to true by thread_entryPoint, but set it + // here as well so the calling thread will see the isRunning + // state immediately. + atomicStore!(MemoryOrder.raw)(m_isRunning, true); + scope( failure ) atomicStore!(MemoryOrder.raw)(m_isRunning, false); + + version (Shared) + { + auto libs = externDFunc!("rt.sections_elf_shared.pinLoadedLibraries", + void* function() @nogc nothrow)(); + + auto ps = cast(void**).malloc(2 * size_t.sizeof); + if (ps is null) onOutOfMemoryError(); + ps[0] = cast(void*)this; + ps[1] = cast(void*)libs; + if ( pthread_create( &m_addr, &attr, &thread_entryPoint, ps ) != 0 ) + { + externDFunc!("rt.sections_elf_shared.unpinLoadedLibraries", + void function(void*) @nogc nothrow)(libs); + .free(ps); + onThreadError( "Error creating thread" ); + } + } + else + { + if ( pthread_create( &m_addr, &attr, &thread_entryPoint, cast(void*) this ) != 0 ) + onThreadError( "Error creating thread" ); + } + if ( pthread_attr_destroy( &attr ) != 0 ) + onThreadError( "Error destroying thread attributes" ); + + version (Darwin) + { + m_tmach = pthread_mach_thread_np( m_addr ); + if ( m_tmach == m_tmach.init ) + onThreadError( "Error creating thread" ); + } + } + else + static assert(0, "unsupported OS"); + + return this; + } + } + + override final Throwable join( bool rethrow = true ) + { + version (Windows) + { + if ( m_addr != m_addr.init && WaitForSingleObject( m_hndl, INFINITE ) != WAIT_OBJECT_0 ) + throw new ThreadException( "Unable to join thread" ); + // NOTE: m_addr must be cleared before m_hndl is closed to avoid + // a race condition with isRunning. The operation is done + // with atomicStore to prevent compiler reordering. + atomicStore!(MemoryOrder.raw)(*cast(shared)&m_addr, m_addr.init); + CloseHandle( m_hndl ); + m_hndl = m_hndl.init; + } + else version (Posix) + { + if ( m_addr != m_addr.init && pthread_join( m_addr, null ) != 0 ) + throw new ThreadException( "Unable to join thread" ); + // NOTE: pthread_join acts as a substitute for pthread_detach, + // which is normally called by the dtor. Setting m_addr + // to zero ensures that pthread_detach will not be called + // on object destruction. + m_addr = m_addr.init; + } + else + static assert(0, "unsupported OS"); + + if ( m_unhandled ) + { + if ( rethrow ) + throw m_unhandled; + return m_unhandled; + } + return null; + } + + version (Windows) + { + @property static int PRIORITY_MIN() @nogc nothrow pure @safe + { + return THREAD_PRIORITY_IDLE; + } + + @property static const(int) PRIORITY_MAX() @nogc nothrow pure @safe + { + return THREAD_PRIORITY_TIME_CRITICAL; + } + + @property static int PRIORITY_DEFAULT() @nogc nothrow pure @safe + { + return THREAD_PRIORITY_NORMAL; + } + } + else version (Posix) + { + private struct Priority + { + int PRIORITY_MIN = int.min; + int PRIORITY_DEFAULT = int.min; + int PRIORITY_MAX = int.min; + } + + /* + Lazily loads one of the members stored in a hidden global variable of + type `Priority`. Upon the first access of either member, the entire + `Priority` structure is initialized. Multiple initializations from + different threads calling this function are tolerated. + + `which` must be one of `PRIORITY_MIN`, `PRIORITY_DEFAULT`, + `PRIORITY_MAX`. + */ + private static shared Priority cache; + private static int loadGlobal(string which)() + { + auto local = atomicLoad(mixin("cache." ~ which)); + if (local != local.min) return local; + // There will be benign races + auto loaded = loadPriorities; + static foreach (i, _; loaded.tupleof) + atomicStore(cache.tupleof[i], loaded.tupleof[i]); + return atomicLoad(mixin("cache." ~ which)); + } + + /* + Loads all priorities and returns them as a `Priority` structure. This + function is thread-neutral. + */ + private static Priority loadPriorities() @nogc nothrow @trusted + { + Priority result; + version (Solaris) + { + pcparms_t pcParms; + pcinfo_t pcInfo; + + pcParms.pc_cid = PC_CLNULL; + if (priocntl(idtype_t.P_PID, P_MYID, PC_GETPARMS, &pcParms) == -1) + assert( 0, "Unable to get scheduling class" ); + + pcInfo.pc_cid = pcParms.pc_cid; + // PC_GETCLINFO ignores the first two args, use dummy values + if (priocntl(idtype_t.P_PID, 0, PC_GETCLINFO, &pcInfo) == -1) + assert( 0, "Unable to get scheduling class info" ); + + pri_t* clparms = cast(pri_t*)&pcParms.pc_clparms; + pri_t* clinfo = cast(pri_t*)&pcInfo.pc_clinfo; + + result.PRIORITY_MAX = clparms[0]; + + if (pcInfo.pc_clname == "RT") + { + m_isRTClass = true; + + // For RT class, just assume it can't be changed + result.PRIORITY_MIN = clparms[0]; + result.PRIORITY_DEFAULT = clparms[0]; + } + else + { + m_isRTClass = false; + + // For all other scheduling classes, there are + // two key values -- uprilim and maxupri. + // maxupri is the maximum possible priority defined + // for the scheduling class, and valid priorities + // range are in [-maxupri, maxupri]. + // + // However, uprilim is an upper limit that the + // current thread can set for the current scheduling + // class, which can be less than maxupri. As such, + // use this value for priorityMax since this is + // the effective maximum. + + // maxupri + result.PRIORITY_MIN = -cast(int)(clinfo[0]); + // by definition + result.PRIORITY_DEFAULT = 0; + } + } + else + { + int policy; + sched_param param; + pthread_getschedparam( pthread_self(), &policy, ¶m ) == 0 + || assert(0, "Internal error in pthread_getschedparam"); + + result.PRIORITY_MIN = sched_get_priority_min( policy ); + result.PRIORITY_MIN != -1 + || assert(0, "Internal error in sched_get_priority_min"); + result.PRIORITY_DEFAULT = param.sched_priority; + result.PRIORITY_MAX = sched_get_priority_max( policy ); + result.PRIORITY_MAX != -1 || + assert(0, "Internal error in sched_get_priority_max"); + } + return result; + } + + @property static int PRIORITY_MIN() @nogc nothrow pure @trusted + { + return (cast(int function() @nogc nothrow pure @safe) + &loadGlobal!"PRIORITY_MIN")(); + } + + @property static const(int) PRIORITY_MAX() @nogc nothrow pure @trusted + { + return (cast(int function() @nogc nothrow pure @safe) + &loadGlobal!"PRIORITY_MAX")(); + } + + @property static int PRIORITY_DEFAULT() @nogc nothrow pure @trusted + { + return (cast(int function() @nogc nothrow pure @safe) + &loadGlobal!"PRIORITY_DEFAULT")(); + } + } + else + static assert(0, "unsupported OS"); + + + version (NetBSD) + { + //NetBSD does not support priority for default policy + // and it is not possible change policy without root access + int fakePriority = int.max; + } + + final @property int priority() + { + version (Windows) + { + return GetThreadPriority( m_hndl ); + } + else version (NetBSD) + { + return fakePriority==int.max? PRIORITY_DEFAULT : fakePriority; + } + else version (Posix) + { + int policy; + sched_param param; + + if (auto err = pthread_getschedparam(m_addr, &policy, ¶m)) + { + // ignore error if thread is not running => Bugzilla 8960 + if (!atomicLoad(m_isRunning)) return PRIORITY_DEFAULT; + throw new ThreadException("Unable to get thread priority"); + } + return param.sched_priority; + } + else + static assert(0, "unsupported os"); + } + + final @property void priority( int val ) + in + { + assert(val >= PRIORITY_MIN); + assert(val <= PRIORITY_MAX); + } + do + { + version (Windows) + { + if ( !SetThreadPriority( m_hndl, val ) ) + throw new ThreadException( "Unable to set thread priority" ); + } + else version (Solaris) + { + // the pthread_setschedprio(3c) and pthread_setschedparam functions + // are broken for the default (TS / time sharing) scheduling class. + // instead, we use priocntl(2) which gives us the desired behavior. + + // We hardcode the min and max priorities to the current value + // so this is a no-op for RT threads. + if (m_isRTClass) + return; + + pcparms_t pcparm; + + pcparm.pc_cid = PC_CLNULL; + if (priocntl(idtype_t.P_LWPID, P_MYID, PC_GETPARMS, &pcparm) == -1) + throw new ThreadException( "Unable to get scheduling class" ); + + pri_t* clparms = cast(pri_t*)&pcparm.pc_clparms; + + // clparms is filled in by the PC_GETPARMS call, only necessary + // to adjust the element that contains the thread priority + clparms[1] = cast(pri_t) val; + + if (priocntl(idtype_t.P_LWPID, P_MYID, PC_SETPARMS, &pcparm) == -1) + throw new ThreadException( "Unable to set scheduling class" ); + } + else version (NetBSD) + { + fakePriority = val; + } + else version (Posix) + { + static if (__traits(compiles, core.sys.posix.pthread.pthread_setschedprio)) + { + import core.sys.posix.pthread : pthread_setschedprio; + + if (auto err = pthread_setschedprio(m_addr, val)) + { + // ignore error if thread is not running => Bugzilla 8960 + if (!atomicLoad(m_isRunning)) return; + throw new ThreadException("Unable to set thread priority"); + } + } + else + { + // NOTE: pthread_setschedprio is not implemented on Darwin, FreeBSD, OpenBSD, + // or DragonFlyBSD, so use the more complicated get/set sequence below. + int policy; + sched_param param; + + if (auto err = pthread_getschedparam(m_addr, &policy, ¶m)) + { + // ignore error if thread is not running => Bugzilla 8960 + if (!atomicLoad(m_isRunning)) return; + throw new ThreadException("Unable to set thread priority"); + } + param.sched_priority = val; + if (auto err = pthread_setschedparam(m_addr, policy, ¶m)) + { + // ignore error if thread is not running => Bugzilla 8960 + if (!atomicLoad(m_isRunning)) return; + throw new ThreadException("Unable to set thread priority"); + } + } + } + else + static assert(0, "unsupported os"); + } + + + unittest + { + auto thr = Thread.getThis(); + immutable prio = thr.priority; + scope (exit) thr.priority = prio; + + assert(prio == PRIORITY_DEFAULT); + assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); + thr.priority = PRIORITY_MIN; + assert(thr.priority == PRIORITY_MIN); + thr.priority = PRIORITY_MAX; + assert(thr.priority == PRIORITY_MAX); + } + + unittest // Bugzilla 8960 + { + import core.sync.semaphore; + + auto thr = new Thread({}); + thr.start(); + Thread.sleep(1.msecs); // wait a little so the thread likely has finished + thr.priority = PRIORITY_MAX; // setting priority doesn't cause error + auto prio = thr.priority; // getting priority doesn't cause error + assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); + } + + override final @property bool isRunning() nothrow @nogc + { + if (!super.isRunning()) + return false; + + version (Windows) + { + uint ecode = 0; + GetExitCodeThread( m_hndl, &ecode ); + return ecode == STILL_ACTIVE; + } + else version (Posix) + { + return atomicLoad(m_isRunning); + } + else + static assert(0, "unsupported os"); + } + + static void sleep( Duration val ) @nogc nothrow @trusted + in + { + assert( !val.isNegative ); + } + do + { + version (Windows) + { + auto maxSleepMillis = dur!("msecs")( uint.max - 1 ); + + // avoid a non-zero time to be round down to 0 + if ( val > dur!"msecs"( 0 ) && val < dur!"msecs"( 1 ) ) + val = dur!"msecs"( 1 ); + + // NOTE: In instances where all other threads in the process have a + // lower priority than the current thread, the current thread + // will not yield with a sleep time of zero. However, unlike + // yield(), the user is not asking for a yield to occur but + // only for execution to suspend for the requested interval. + // Therefore, expected performance may not be met if a yield + // is forced upon the user. + while ( val > maxSleepMillis ) + { + Sleep( cast(uint) + maxSleepMillis.total!"msecs" ); + val -= maxSleepMillis; + } + Sleep( cast(uint) val.total!"msecs" ); + } + else version (Posix) + { + timespec tin = void; + timespec tout = void; + + val.split!("seconds", "nsecs")(tin.tv_sec, tin.tv_nsec); + if ( val.total!"seconds" > tin.tv_sec.max ) + tin.tv_sec = tin.tv_sec.max; + while ( true ) + { + if ( !nanosleep( &tin, &tout ) ) + return; + if ( errno != EINTR ) + assert(0, "Unable to sleep for the specified duration"); + tin = tout; + } + } + else + static assert(0, "unsupported os"); + } + + static void yield() @nogc nothrow + { + version (Windows) + SwitchToThread(); + else version (Posix) + sched_yield(); + else + static assert(0, "unsupported os"); + } +} + +private Thread toThread(return scope ThreadBase t) @trusted nothrow @nogc pure +{ + return cast(Thread) cast(void*) t; +} + +private extern(D) static void thread_yield() @nogc nothrow +{ + Thread.yield(); +} + +/// +unittest +{ + class DerivedThread : Thread + { + this() + { + super(&run); + } + + private: + void run() + { + // Derived thread running. + } + } + + void threadFunc() + { + // Composed thread running. + } + + // create and start instances of each type + auto derived = new DerivedThread().start(); + auto composed = new Thread(&threadFunc).start(); + new Thread({ + // Codes to run in the newly created thread. + }).start(); +} + +unittest +{ + int x = 0; + + new Thread( + { + x++; + }).start().join(); + assert( x == 1 ); +} + + +unittest +{ + enum MSG = "Test message."; + string caughtMsg; + + try + { + new Thread( + function() + { + throw new Exception( MSG ); + }).start().join(); + assert( false, "Expected rethrown exception." ); + } + catch ( Throwable t ) + { + assert( t.msg == MSG ); + } +} + + +unittest +{ + // use >pageSize to avoid stack overflow (e.g. in an syscall) + auto thr = new Thread(function{}, 4096 + 1).start(); + thr.join(); +} + + +unittest +{ + import core.memory : GC; + + auto t1 = new Thread({ + foreach (_; 0 .. 20) + ThreadBase.getAll; + }).start; + auto t2 = new Thread({ + foreach (_; 0 .. 20) + GC.collect; + }).start; + t1.join(); + t2.join(); +} + +unittest +{ + import core.sync.semaphore; + auto sem = new Semaphore(); + + auto t = new Thread( + { + sem.notify(); + Thread.sleep(100.msecs); + }).start(); + + sem.wait(); // thread cannot be detached while being started + thread_detachInstance(t); + foreach (t2; Thread) + assert(t !is t2); + t.join(); +} + +// https://issues.dlang.org/show_bug.cgi?id=22124 +unittest +{ + Thread thread = new Thread({}); + auto fun(Thread t, int x) + { + t.__ctor({x = 3;}); + return t; + } + static assert(!__traits(compiles, () @nogc => fun(thread, 3) )); +} + +@nogc @safe nothrow +unittest +{ + Thread.sleep(1.msecs); +} + +/////////////////////////////////////////////////////////////////////////////// +// GC Support Routines +/////////////////////////////////////////////////////////////////////////////// + +version (CoreDdoc) +{ + /** + * Instruct the thread module, when initialized, to use a different set of + * signals besides SIGRTMIN and SIGRTMIN + 1 for suspension and resumption of threads. + * This function should be called at most once, prior to thread_init(). + * This function is Posix-only. + */ + extern (C) void thread_setGCSignals(int suspendSignalNo, int resumeSignalNo) nothrow @nogc + { + } + + /** + * Get the GC signals set by the thread module. This function should be called either + * after thread_init() has finished, or after a call thread_setGCSignals(). + * This function is Posix-only. + */ + extern (C) void thread_getGCSignals(out int suspendSignalNo, out int resumeSignalNo) nothrow @nogc + { + } +} +else version (Posix) +{ + extern (C) void thread_setGCSignals(int suspendSignalNo, int resumeSignalNo) nothrow @nogc + in + { + assert(suspendSignalNo != 0); + assert(resumeSignalNo != 0); + } + out + { + assert(suspendSignalNumber != 0); + assert(resumeSignalNumber != 0); + } + do + { + suspendSignalNumber = suspendSignalNo; + resumeSignalNumber = resumeSignalNo; + } + + extern (C) void thread_getGCSignals(out int suspendSignalNo, out int resumeSignalNo) nothrow @nogc + in + { + assert(suspendSignalNumber != 0); + assert(resumeSignalNumber != 0); + } + out + { + assert(suspendSignalNo != 0); + assert(resumeSignalNo != 0); + } + do + { + suspendSignalNo = suspendSignalNumber; + resumeSignalNo = resumeSignalNumber; + } +} + +version (Posix) +{ + private __gshared int suspendSignalNumber; + private __gshared int resumeSignalNumber; +} + +version (CoreDdoc) {} else +private extern (D) ThreadBase attachThread(ThreadBase _thisThread) @nogc nothrow +{ + Thread thisThread = _thisThread.toThread(); + + StackContext* thisContext = &thisThread.m_main; + assert( thisContext == thisThread.m_curr ); + + version (Windows) + { + thisThread.m_addr = GetCurrentThreadId(); + thisThread.m_hndl = GetCurrentThreadHandle(); + thisContext.bstack = getStackBottom(); + thisContext.tstack = thisContext.bstack; + } + else version (Posix) + { + thisThread.m_addr = pthread_self(); + thisContext.bstack = getStackBottom(); + thisContext.tstack = thisContext.bstack; + + atomicStore!(MemoryOrder.raw)(thisThread.toThread.m_isRunning, true); + } + else + static assert(0, "unsupported os"); + thisThread.m_isDaemon = true; + thisThread.tlsRTdataInit(); + Thread.setThis( thisThread ); + + version (Darwin) + { + thisThread.m_tmach = pthread_mach_thread_np( thisThread.m_addr ); + assert( thisThread.m_tmach != thisThread.m_tmach.init ); + } + + Thread.add( thisThread, false ); + Thread.add( thisContext ); + if ( Thread.sm_main !is null ) + multiThreadedFlag = true; + return thisThread; +} + +/** + * Registers the calling thread for use with the D Runtime. If this routine + * is called for a thread which is already registered, no action is performed. + * + * NOTE: This routine does not run thread-local static constructors when called. + * If full functionality as a D thread is desired, the following function + * must be called after thread_attachThis: + * + * extern (C) void rt_moduleTlsCtor(); + * + * See_Also: + * $(REF thread_detachThis, core,thread,threadbase) + */ +extern(C) Thread thread_attachThis() +{ + return thread_attachThis_tpl!Thread(); +} + + +version (Windows) +{ + // NOTE: These calls are not safe on Posix systems that use signals to + // perform garbage collection. The suspendHandler uses getThis() + // to get the thread handle so getThis() must be a simple call. + // Mutexes can't safely be acquired inside signal handlers, and + // even if they could, the mutex needed (Thread.slock) is held by + // thread_suspendAll(). So in short, these routines will remain + // Windows-specific. If they are truly needed elsewhere, the + // suspendHandler will need a way to call a version of getThis() + // that only does the TLS lookup without the fancy fallback stuff. + + /// ditto + extern (C) Thread thread_attachByAddr( ThreadID addr ) + { + return thread_attachByAddrB( addr, getThreadStackBottom( addr ) ); + } + + + /// ditto + extern (C) Thread thread_attachByAddrB( ThreadID addr, void* bstack ) + { + GC.disable(); scope(exit) GC.enable(); + + if (auto t = thread_findByAddr(addr).toThread) + return t; + + Thread thisThread = new Thread(); + StackContext* thisContext = &thisThread.m_main; + assert( thisContext == thisThread.m_curr ); + + thisThread.m_addr = addr; + thisContext.bstack = bstack; + thisContext.tstack = thisContext.bstack; + + thisThread.m_isDaemon = true; + + if ( addr == GetCurrentThreadId() ) + { + thisThread.m_hndl = GetCurrentThreadHandle(); + thisThread.tlsRTdataInit(); + Thread.setThis( thisThread ); + } + else + { + thisThread.m_hndl = OpenThreadHandle( addr ); + impersonate_thread(addr, + { + thisThread.tlsRTdataInit(); + Thread.setThis( thisThread ); + }); + } + + Thread.add( thisThread, false ); + Thread.add( thisContext ); + if ( Thread.sm_main !is null ) + multiThreadedFlag = true; + return thisThread; + } +} + + +// Calls the given delegate, passing the current thread's stack pointer to it. +package extern(D) void callWithStackShell(scope callWithStackShellDg fn) nothrow +in (fn) +{ + // The purpose of the 'shell' is to ensure all the registers get + // put on the stack so they'll be scanned. We only need to push + // the callee-save registers. + void *sp = void; + version (GNU) + { + // The generic solution below using a call to __builtin_unwind_init () + // followed by an assignment to sp has two issues: + // 1) On some archs it stores a huge amount of FP and Vector state which + // is not the subject of the scan - and, indeed might produce false + // hits. + // 2) Even on archs like X86, where there are no callee-saved FPRs/VRs there + // tend to be 'holes' in the frame allocations (to deal with alignment) which + // also will contain random data which could produce false positives. + // This solution stores only the integer callee-saved registers. + version (X86) + { + void*[3] regs = void; + asm pure nothrow @nogc + { + "movl %%ebx, %0" : "=m" (regs[0]); + "movl %%esi, %0" : "=m" (regs[1]); + "movl %%edi, %0" : "=m" (regs[2]); + } + sp = cast(void*)®s[0]; + } + else version (X86_64) + { + void*[5] regs = void; + asm pure nothrow @nogc + { + "movq %%rbx, %0" : "=m" (regs[0]); + "movq %%r12, %0" : "=m" (regs[1]); + "movq %%r13, %0" : "=m" (regs[2]); + "movq %%r14, %0" : "=m" (regs[3]); + "movq %%r15, %0" : "=m" (regs[4]); + } + sp = cast(void*)®s[0]; + } + else version (PPC) + { + void*[19] regs = void; + version (Darwin) + enum regname = "r"; + else + enum regname = ""; + static foreach (i; 0 .. regs.length) + {{ + enum int j = 13 + i; // source register + asm pure nothrow @nogc + { + ("stw "~regname~j.stringof~", %0") : "=m" (regs[i]); + } + }} + sp = cast(void*)®s[0]; + } + else version (PPC64) + { + void*[19] regs = void; + version (Darwin) + enum regname = "r"; + else + enum regname = ""; + static foreach (i; 0 .. regs.length) + {{ + enum int j = 13 + i; // source register + asm pure nothrow @nogc + { + ("std "~regname~j.stringof~", %0") : "=m" (regs[i]); + } + }} + sp = cast(void*)®s[0]; + } + else version (AArch64) + { + // Callee-save registers, x19-x28 according to AAPCS64, section + // 5.1.1. Include x29 fp because it optionally can be a callee + // saved reg + size_t[11] regs = void; + // store the registers in pairs + asm pure nothrow @nogc + { + "stp x19, x20, %0" : "=m" (regs[ 0]), "=m" (regs[1]); + "stp x21, x22, %0" : "=m" (regs[ 2]), "=m" (regs[3]); + "stp x23, x24, %0" : "=m" (regs[ 4]), "=m" (regs[5]); + "stp x25, x26, %0" : "=m" (regs[ 6]), "=m" (regs[7]); + "stp x27, x28, %0" : "=m" (regs[ 8]), "=m" (regs[9]); + "str x29, %0" : "=m" (regs[10]); + "mov %0, sp" : "=r" (sp); + } + } + else version (ARM) + { + // Callee-save registers, according to AAPCS, section 5.1.1. + // arm and thumb2 instructions + size_t[8] regs = void; + asm pure nothrow @nogc + { + "stm %0, {r4-r11}" : : "r" (regs.ptr) : "memory"; + "mov %0, sp" : "=r" (sp); + } + } + else + { + __builtin_unwind_init(); + sp = &sp; + } + } + else version (AsmX86_Posix) + { + size_t[3] regs = void; + asm pure nothrow @nogc + { + mov [regs + 0 * 4], EBX; + mov [regs + 1 * 4], ESI; + mov [regs + 2 * 4], EDI; + + mov sp[EBP], ESP; + } + } + else version (AsmX86_Windows) + { + size_t[3] regs = void; + asm pure nothrow @nogc + { + mov [regs + 0 * 4], EBX; + mov [regs + 1 * 4], ESI; + mov [regs + 2 * 4], EDI; + + mov sp[EBP], ESP; + } + } + else version (AsmX86_64_Posix) + { + size_t[5] regs = void; + asm pure nothrow @nogc + { + mov [regs + 0 * 8], RBX; + mov [regs + 1 * 8], R12; + mov [regs + 2 * 8], R13; + mov [regs + 3 * 8], R14; + mov [regs + 4 * 8], R15; + + mov sp[RBP], RSP; + } + } + else version (AsmX86_64_Windows) + { + size_t[7] regs = void; + asm pure nothrow @nogc + { + mov [regs + 0 * 8], RBX; + mov [regs + 1 * 8], RSI; + mov [regs + 2 * 8], RDI; + mov [regs + 3 * 8], R12; + mov [regs + 4 * 8], R13; + mov [regs + 5 * 8], R14; + mov [regs + 6 * 8], R15; + + mov sp[RBP], RSP; + } + } + else version (AArch64) + { + // Callee-save registers, x19-x28 according to AAPCS64, section + // 5.1.1. Include x29 fp because it optionally can be a callee + // saved reg + size_t[11] regs = void; + // store the registers in pairs + asm pure nothrow @nogc + { + /* + stp x19, x20, regs[0]; + stp x21, x22, regs[2]; + stp x23, x24, regs[4]; + stp x25, x26, regs[6]; + stp x27, x28, regs[8]; + str x29, regs[10]; + mov [sp], sp; + */ + } + assert(0, "implement AArch64 inline assembler for callWithStackShell()"); // TODO AArch64 + } + else + { + static assert(false, "Architecture not supported."); + } + + fn(sp); +} + +/** + * Returns the process ID of the calling process, which is guaranteed to be + * unique on the system. This call is always successful. + * + * Example: + * --- + * writefln("Current process id: %s", getpid()); + * --- + */ +version (Posix) +{ + alias getpid = imported!"core.sys.posix.unistd".getpid; +} +else version (Windows) +{ + alias getpid = imported!"core.sys.windows.winbase".GetCurrentProcessId; +} +else + static assert(0, "unsupported os"); + +extern (C) @nogc nothrow +{ + version (CRuntime_Glibc) version = PThread_Getattr_NP; + version (CRuntime_Bionic) version = PThread_Getattr_NP; + version (CRuntime_Musl) version = PThread_Getattr_NP; + version (CRuntime_UClibc) version = PThread_Getattr_NP; + + version (FreeBSD) version = PThread_Attr_Get_NP; + version (NetBSD) version = PThread_Attr_Get_NP; + version (DragonFlyBSD) version = PThread_Attr_Get_NP; + + version (PThread_Getattr_NP) int pthread_getattr_np(pthread_t thread, pthread_attr_t* attr); + version (PThread_Attr_Get_NP) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr); + version (OpenBSD) int pthread_stackseg_np(pthread_t thread, stack_t* sinfo); +} + + +private extern(D) void* getStackTop() nothrow @nogc +{ + version (D_InlineAsm_X86) + asm pure nothrow @nogc { naked; mov EAX, ESP; ret; } + else version (D_InlineAsm_X86_64) + asm pure nothrow @nogc { naked; mov RAX, RSP; ret; } + else version (AArch64) + //asm pure nothrow @nogc { naked; mov x0, SP; ret; } // TODO AArch64 + { + return null; + } + else version (GNU) + return __builtin_frame_address(0); + else + static assert(false, "Architecture not supported."); +} + + +private extern(D) void* getStackBottom() nothrow @nogc +{ + version (Windows) + { + version (D_InlineAsm_X86) + asm pure nothrow @nogc { naked; mov EAX, FS:4; ret; } + else version (D_InlineAsm_X86_64) + asm pure nothrow @nogc + { naked; + mov RAX, 8; + mov RAX, GS:[RAX]; + ret; + } + else version (GNU_InlineAsm) + { + void *bottom; + + version (X86) + asm pure nothrow @nogc { "movl %%fs:4, %0;" : "=r" (bottom); } + else version (X86_64) + asm pure nothrow @nogc { "movq %%gs:8, %0;" : "=r" (bottom); } + else + static assert(false, "Architecture not supported."); + + return bottom; + } + else + static assert(false, "Architecture not supported."); + } + else version (Darwin) + { + import core.sys.darwin.pthread : pthread_get_stackaddr_np; + return pthread_get_stackaddr_np(pthread_self()); + } + else version (PThread_Getattr_NP) + { + pthread_attr_t attr; + void* addr; size_t size; + + pthread_attr_init(&attr); + pthread_getattr_np(pthread_self(), &attr); + pthread_attr_getstack(&attr, &addr, &size); + pthread_attr_destroy(&attr); + static if (isStackGrowingDown) + addr += size; + return addr; + } + else version (PThread_Attr_Get_NP) + { + pthread_attr_t attr; + void* addr; size_t size; + + pthread_attr_init(&attr); + pthread_attr_get_np(pthread_self(), &attr); + pthread_attr_getstack(&attr, &addr, &size); + pthread_attr_destroy(&attr); + static if (isStackGrowingDown) + addr += size; + return addr; + } + else version (OpenBSD) + { + stack_t stk; + + pthread_stackseg_np(pthread_self(), &stk); + return stk.ss_sp; + } + else version (Solaris) + { + stack_t stk; + + thr_stksegment(&stk); + return stk.ss_sp; + } + else + static assert(false, "Platform not supported."); +} + +/** + * Suspend the specified thread and load stack and register information for + * use by thread_scanAll. If the supplied thread is the calling thread, + * stack and register information will be loaded but the thread will not + * be suspended. If the suspend operation fails and the thread is not + * running then it will be removed from the global thread list, otherwise + * an exception will be thrown. + * + * Params: + * t = The thread to suspend. + * + * Throws: + * ThreadError if the suspend operation fails for a running thread. + * Returns: + * Whether the thread is now suspended (true) or terminated (false). + */ +private extern (D) bool suspend( Thread t ) nothrow @nogc +{ + if (!t.isRunning) + { + Thread.remove(t); + return false; + } + + version (Windows) + { + if ( t.m_addr != GetCurrentThreadId() && SuspendThread( t.m_hndl ) == 0xFFFFFFFF ) + { + if ( !t.isRunning ) + { + Thread.remove( t ); + return false; + } + onThreadError( "Unable to suspend thread" ); + } + + CONTEXT context = void; + context.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL; + + if ( !GetThreadContext( t.m_hndl, &context ) ) + onThreadError( "Unable to load thread context" ); + version (X86) + { + if ( !t.m_lock ) + t.m_curr.tstack = cast(void*) context.Esp; + // eax,ebx,ecx,edx,edi,esi,ebp,esp + t.m_reg[0] = context.Eax; + t.m_reg[1] = context.Ebx; + t.m_reg[2] = context.Ecx; + t.m_reg[3] = context.Edx; + t.m_reg[4] = context.Edi; + t.m_reg[5] = context.Esi; + t.m_reg[6] = context.Ebp; + t.m_reg[7] = context.Esp; + } + else version (X86_64) + { + if ( !t.m_lock ) + t.m_curr.tstack = cast(void*) context.Rsp; + // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp + t.m_reg[0] = context.Rax; + t.m_reg[1] = context.Rbx; + t.m_reg[2] = context.Rcx; + t.m_reg[3] = context.Rdx; + t.m_reg[4] = context.Rdi; + t.m_reg[5] = context.Rsi; + t.m_reg[6] = context.Rbp; + t.m_reg[7] = context.Rsp; + // r8,r9,r10,r11,r12,r13,r14,r15 + t.m_reg[8] = context.R8; + t.m_reg[9] = context.R9; + t.m_reg[10] = context.R10; + t.m_reg[11] = context.R11; + t.m_reg[12] = context.R12; + t.m_reg[13] = context.R13; + t.m_reg[14] = context.R14; + t.m_reg[15] = context.R15; + } + else + { + static assert(false, "Architecture not supported." ); + } + } + else version (Darwin) + { + if ( t.m_addr != pthread_self() && thread_suspend( t.m_tmach ) != KERN_SUCCESS ) + { + if ( !t.isRunning ) + { + Thread.remove( t ); + return false; + } + onThreadError( "Unable to suspend thread" ); + } + + version (X86) + { + x86_thread_state32_t state = void; + mach_msg_type_number_t count = x86_THREAD_STATE32_COUNT; + + if ( thread_get_state( t.m_tmach, x86_THREAD_STATE32, &state, &count ) != KERN_SUCCESS ) + onThreadError( "Unable to load thread state" ); + if ( !t.m_lock ) + t.m_curr.tstack = cast(void*) state.esp; + // eax,ebx,ecx,edx,edi,esi,ebp,esp + t.m_reg[0] = state.eax; + t.m_reg[1] = state.ebx; + t.m_reg[2] = state.ecx; + t.m_reg[3] = state.edx; + t.m_reg[4] = state.edi; + t.m_reg[5] = state.esi; + t.m_reg[6] = state.ebp; + t.m_reg[7] = state.esp; + } + else version (X86_64) + { + x86_thread_state64_t state = void; + mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; + + if ( thread_get_state( t.m_tmach, x86_THREAD_STATE64, &state, &count ) != KERN_SUCCESS ) + onThreadError( "Unable to load thread state" ); + if ( !t.m_lock ) + t.m_curr.tstack = cast(void*) state.rsp; + // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp + t.m_reg[0] = state.rax; + t.m_reg[1] = state.rbx; + t.m_reg[2] = state.rcx; + t.m_reg[3] = state.rdx; + t.m_reg[4] = state.rdi; + t.m_reg[5] = state.rsi; + t.m_reg[6] = state.rbp; + t.m_reg[7] = state.rsp; + // r8,r9,r10,r11,r12,r13,r14,r15 + t.m_reg[8] = state.r8; + t.m_reg[9] = state.r9; + t.m_reg[10] = state.r10; + t.m_reg[11] = state.r11; + t.m_reg[12] = state.r12; + t.m_reg[13] = state.r13; + t.m_reg[14] = state.r14; + t.m_reg[15] = state.r15; + } + else version (AArch64) + { + arm_thread_state64_t state = void; + mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT; + + if (thread_get_state(t.m_tmach, ARM_THREAD_STATE64, &state, &count) != KERN_SUCCESS) + onThreadError("Unable to load thread state"); + // TODO: ThreadException here recurses forever! Does it + //still using onThreadError? + //printf("state count %d (expect %d)\n", count ,ARM_THREAD_STATE64_COUNT); + if (!t.m_lock) + t.m_curr.tstack = cast(void*) state.sp; + + t.m_reg[0..29] = state.x; // x0-x28 + t.m_reg[29] = state.fp; // x29 + t.m_reg[30] = state.lr; // x30 + t.m_reg[31] = state.sp; // x31 + t.m_reg[32] = state.pc; + } + else version (ARM) + { + arm_thread_state32_t state = void; + mach_msg_type_number_t count = ARM_THREAD_STATE32_COUNT; + + // Thought this would be ARM_THREAD_STATE32, but that fails. + // Mystery + if (thread_get_state(t.m_tmach, ARM_THREAD_STATE, &state, &count) != KERN_SUCCESS) + onThreadError("Unable to load thread state"); + // TODO: in past, ThreadException here recurses forever! Does it + //still using onThreadError? + //printf("state count %d (expect %d)\n", count ,ARM_THREAD_STATE32_COUNT); + if (!t.m_lock) + t.m_curr.tstack = cast(void*) state.sp; + + t.m_reg[0..13] = state.r; // r0 - r13 + t.m_reg[13] = state.sp; + t.m_reg[14] = state.lr; + t.m_reg[15] = state.pc; + } + else version (PPC) + { + ppc_thread_state_t state = void; + mach_msg_type_number_t count = PPC_THREAD_STATE_COUNT; + + if (thread_get_state(t.m_tmach, PPC_THREAD_STATE, &state, &count) != KERN_SUCCESS) + onThreadError("Unable to load thread state"); + if (!t.m_lock) + t.m_curr.tstack = cast(void*) state.r[1]; + t.m_reg[] = state.r[]; + } + else version (PPC64) + { + ppc_thread_state64_t state = void; + mach_msg_type_number_t count = PPC_THREAD_STATE64_COUNT; + + if (thread_get_state(t.m_tmach, PPC_THREAD_STATE64, &state, &count) != KERN_SUCCESS) + onThreadError("Unable to load thread state"); + if (!t.m_lock) + t.m_curr.tstack = cast(void*) state.r[1]; + t.m_reg[] = state.r[]; + } + else + { + static assert(false, "Architecture not supported." ); + } + } + else version (Solaris) + { + if (t.m_addr != pthread_self()) + { + if (thr_suspend(t.m_addr) != 0) + { + if (!t.isRunning) + { + Thread.remove(t); + return false; + } + onThreadError("Unable to suspend thread"); + } + + static int getLwpStatus(ulong lwpid, out lwpstatus_t status) + { + import core.sys.posix.fcntl : open, O_RDONLY; + import core.sys.posix.unistd : pread, close; + import core.internal.string : unsignedToTempString; + + char[100] path = void; + auto pslice = path[0 .. $]; + immutable n = unsignedToTempString(lwpid); + immutable ndigits = n.length; + + // Construct path "/proc/self/lwp/%u/lwpstatus" + pslice[0 .. 15] = "/proc/self/lwp/"; + pslice = pslice[15 .. $]; + pslice[0 .. ndigits] = n[]; + pslice = pslice[ndigits .. $]; + pslice[0 .. 10] = "/lwpstatus"; + pslice[10] = '\0'; + + // Read in lwpstatus data + int fd = open(path.ptr, O_RDONLY, 0); + if (fd >= 0) + { + while (pread(fd, &status, status.sizeof, 0) == status.sizeof) + { + // Should only attempt to read the thread state once it + // has been stopped by thr_suspend + if (status.pr_flags & PR_STOPPED) + { + close(fd); + return 0; + } + // Give it a chance to stop + thread_yield(); + } + close(fd); + } + return -1; + } + + lwpstatus_t status = void; + if (getLwpStatus(t.m_addr, status) != 0) + onThreadError("Unable to load thread state"); + + version (X86) + { + import core.sys.solaris.sys.regset; // REG_xxx + + if (!t.m_lock) + t.m_curr.tstack = cast(void*) status.pr_reg[REG_ESP]; + // eax,ebx,ecx,edx,edi,esi,ebp,esp + t.m_reg[0] = status.pr_reg[REG_EAX]; + t.m_reg[1] = status.pr_reg[REG_EBX]; + t.m_reg[2] = status.pr_reg[REG_ECX]; + t.m_reg[3] = status.pr_reg[REG_EDX]; + t.m_reg[4] = status.pr_reg[REG_EDI]; + t.m_reg[5] = status.pr_reg[REG_ESI]; + t.m_reg[6] = status.pr_reg[REG_EBP]; + t.m_reg[7] = status.pr_reg[REG_ESP]; + } + else version (X86_64) + { + import core.sys.solaris.sys.regset; // REG_xxx + + if (!t.m_lock) + t.m_curr.tstack = cast(void*) status.pr_reg[REG_RSP]; + // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp + t.m_reg[0] = status.pr_reg[REG_RAX]; + t.m_reg[1] = status.pr_reg[REG_RBX]; + t.m_reg[2] = status.pr_reg[REG_RCX]; + t.m_reg[3] = status.pr_reg[REG_RDX]; + t.m_reg[4] = status.pr_reg[REG_RDI]; + t.m_reg[5] = status.pr_reg[REG_RSI]; + t.m_reg[6] = status.pr_reg[REG_RBP]; + t.m_reg[7] = status.pr_reg[REG_RSP]; + // r8,r9,r10,r11,r12,r13,r14,r15 + t.m_reg[8] = status.pr_reg[REG_R8]; + t.m_reg[9] = status.pr_reg[REG_R9]; + t.m_reg[10] = status.pr_reg[REG_R10]; + t.m_reg[11] = status.pr_reg[REG_R11]; + t.m_reg[12] = status.pr_reg[REG_R12]; + t.m_reg[13] = status.pr_reg[REG_R13]; + t.m_reg[14] = status.pr_reg[REG_R14]; + t.m_reg[15] = status.pr_reg[REG_R15]; + } + else version (SPARC) + { + import core.sys.solaris.sys.procfs : R_SP, R_PC; + + if (!t.m_lock) + t.m_curr.tstack = cast(void*) status.pr_reg[R_SP]; + // g0..g7, o0..o7, l0..l7, i0..i7 + t.m_reg[0 .. 32] = status.pr_reg[0 .. 32]; + // pc + t.m_reg[32] = status.pr_reg[R_PC]; + } + else version (SPARC64) + { + import core.sys.solaris.sys.procfs : R_SP, R_PC; + + if (!t.m_lock) + { + // SPARC V9 has a stack bias of 2047 bytes which must be added to get + // the actual data of the stack frame. + auto tstack = status.pr_reg[R_SP] + 2047; + assert(tstack % 16 == 0); + t.m_curr.tstack = cast(void*) tstack; + } + // g0..g7, o0..o7, l0..l7, i0..i7 + t.m_reg[0 .. 32] = status.pr_reg[0 .. 32]; + // pc + t.m_reg[32] = status.pr_reg[R_PC]; + } + else + { + static assert(false, "Architecture not supported."); + } + } + else if (!t.m_lock) + { + t.m_curr.tstack = getStackTop(); + } + } + else version (Posix) + { + if ( t.m_addr != pthread_self() ) + { + if ( pthread_kill( t.m_addr, suspendSignalNumber ) != 0 ) + { + if ( !t.isRunning ) + { + Thread.remove( t ); + return false; + } + onThreadError( "Unable to suspend thread" ); + } + } + else if ( !t.m_lock ) + { + t.m_curr.tstack = getStackTop(); + } + } + else + static assert(0, "unsupported os"); + return true; +} + +/** + * Runs the necessary operations required before stopping the world. + */ +extern (C) void thread_preStopTheWorld() nothrow { + Thread.slock.lock_nothrow(); +} + +/** + * Suspend all threads but the calling thread for "stop the world" garbage + * collection runs. This function may be called multiple times, and must + * be followed by a matching number of calls to thread_resumeAll before + * processing is resumed. + * + * Throws: + * ThreadError if the suspend operation fails for a running thread. + */ +extern (C) void thread_suspendAll() nothrow +{ + // NOTE: We've got an odd chicken & egg problem here, because while the GC + // is required to call thread_init before calling any other thread + // routines, thread_init may allocate memory which could in turn + // trigger a collection. Thus, thread_suspendAll, thread_scanAll, + // and thread_resumeAll must be callable before thread_init + // completes, with the assumption that no other GC memory has yet + // been allocated by the system, and thus there is no risk of losing + // data if the global thread list is empty. The check of + // Thread.sm_tbeg below is done to ensure thread_init has completed, + // and therefore that calling Thread.getThis will not result in an + // error. For the short time when Thread.sm_tbeg is null, there is + // no reason not to simply call the multithreaded code below, with + // the expectation that the foreach loop will never be entered. + if ( !multiThreadedFlag && Thread.sm_tbeg ) + { + if ( ++suspendDepth == 1 ) + suspend( Thread.getThis() ); + + return; + } + + thread_preStopTheWorld(); + { + if ( ++suspendDepth > 1 ) + return; + + size_t cnt; + bool suspendedSelf; + Thread t = ThreadBase.sm_tbeg.toThread; + while (t) + { + auto tn = t.next.toThread; + if (suspend(t)) + { + if (t is ThreadBase.getThis()) + suspendedSelf = true; + ++cnt; + } + t = tn; + } + + version (Darwin) + {} + else version (Solaris) + {} + else version (Posix) + { + // Subtract own thread if we called suspend() on ourselves. + // For example, suspendedSelf would be false if the current + // thread ran thread_detachThis(). + assert(cnt >= 1); + if (suspendedSelf) + --cnt; + // wait for semaphore notifications + for (; cnt; --cnt) + { + while (sem_wait(&suspendCount) != 0) + { + if (errno != EINTR) + onThreadError("Unable to wait for semaphore"); + errno = 0; + } + } + } + else version (Windows) + { + } + else + static assert(0, "unsupported os"); + } +} + +/** + * Resume the specified thread and unload stack and register information. + * If the supplied thread is the calling thread, stack and register + * information will be unloaded but the thread will not be resumed. If + * the resume operation fails and the thread is not running then it will + * be removed from the global thread list, otherwise an exception will be + * thrown. + * + * Params: + * t = The thread to resume. + * + * Throws: + * ThreadError if the resume fails for a running thread. + */ +private extern (D) void resume(ThreadBase _t) nothrow @nogc +{ + Thread t = _t.toThread; + + version (Windows) + { + if ( t.m_addr != GetCurrentThreadId() && ResumeThread( t.m_hndl ) == 0xFFFFFFFF ) + { + if ( !t.isRunning ) + { + Thread.remove( t ); + return; + } + onThreadError( "Unable to resume thread" ); + } + + if ( !t.m_lock ) + t.m_curr.tstack = t.m_curr.bstack; + t.m_reg[0 .. $] = 0; + } + else version (Darwin) + { + if ( t.m_addr != pthread_self() && thread_resume( t.m_tmach ) != KERN_SUCCESS ) + { + if ( !t.isRunning ) + { + Thread.remove( t ); + return; + } + onThreadError( "Unable to resume thread" ); + } + + if ( !t.m_lock ) + t.m_curr.tstack = t.m_curr.bstack; + t.m_reg[0 .. $] = 0; + } + else version (Solaris) + { + if (t.m_addr != pthread_self() && thr_continue(t.m_addr) != 0) + { + if (!t.isRunning) + { + Thread.remove(t); + return; + } + onThreadError("Unable to resume thread"); + } + + if (!t.m_lock) + t.m_curr.tstack = t.m_curr.bstack; + t.m_reg[0 .. $] = 0; + } + else version (Posix) + { + if ( t.m_addr != pthread_self() ) + { + if ( pthread_kill( t.m_addr, resumeSignalNumber ) != 0 ) + { + if ( !t.isRunning ) + { + Thread.remove( t ); + return; + } + onThreadError( "Unable to resume thread" ); + } + } + else if ( !t.m_lock ) + { + t.m_curr.tstack = t.m_curr.bstack; + } + } + else + static assert(false, "Platform not supported."); +} + + +/** + * Initializes the thread module. This function must be called by the + * garbage collector on startup and before any other thread routines + * are called. + */ +version (CoreDdoc) + extern (C) void thread_init() @nogc nothrow {} +else +extern (C) void thread_init() @nogc nothrow +{ + // NOTE: If thread_init itself performs any allocations then the thread + // routines reserved for garbage collector use may be called while + // thread_init is being processed. However, since no memory should + // exist to be scanned at this point, it is sufficient for these + // functions to detect the condition and return immediately. + + initLowlevelThreads(); + Thread.initLocks(); + + version (Windows) + { + } + else version (Darwin) + { + // thread id different in forked child process + static extern(C) void initChildAfterFork() + { + auto thisThread = Thread.getThis(); + if (!thisThread) + { + // It is possible that runtime was not properly initialized in the current process or thread - + // it may happen after `fork` call when using a dynamically loaded shared library written in D from a multithreaded non-D program. + // In such case getThis will return null. + return; + } + thisThread.m_addr = pthread_self(); + assert( thisThread.m_addr != thisThread.m_addr.init ); + thisThread.m_tmach = pthread_mach_thread_np( thisThread.m_addr ); + assert( thisThread.m_tmach != thisThread.m_tmach.init ); + } + pthread_atfork(null, null, &initChildAfterFork); + } + else version (Solaris) + { + } + else version (Posix) + { + version (OpenBSD) + { + // OpenBSD does not support SIGRTMIN or SIGRTMAX + // Use SIGUSR1 for SIGRTMIN, SIGUSR2 for SIGRTMIN + 1 + // And use 32 for SIGRTMAX (32 is the max signal number on OpenBSD) + enum SIGRTMIN = SIGUSR1; + enum SIGRTMAX = 32; + } + else + { + import core.sys.posix.signal : SIGRTMAX, SIGRTMIN; + } + + if ( suspendSignalNumber == 0 ) + { + suspendSignalNumber = SIGRTMIN; + } + + if ( resumeSignalNumber == 0 ) + { + resumeSignalNumber = SIGRTMIN + 1; + assert(resumeSignalNumber <= SIGRTMAX); + } + int status; + sigaction_t suspend = void; + sigaction_t resume = void; + + // This is a quick way to zero-initialize the structs without using + // memset or creating a link dependency on their static initializer. + (cast(byte*) &suspend)[0 .. sigaction_t.sizeof] = 0; + (cast(byte*) &resume)[0 .. sigaction_t.sizeof] = 0; + + // NOTE: SA_RESTART indicates that system calls should restart if they + // are interrupted by a signal, but this is not available on all + // Posix systems, even those that support multithreading. + static if (__traits(compiles, core.sys.posix.signal.SA_RESTART)) + { + import core.sys.posix.signal : SA_RESTART; + + suspend.sa_flags = SA_RESTART; + } + + suspend.sa_handler = &thread_suspendHandler; + // NOTE: We want to ignore all signals while in this handler, so fill + // sa_mask to indicate this. + status = sigfillset( &suspend.sa_mask ); + assert( status == 0 ); + + // NOTE: Since resumeSignalNumber should only be issued for threads within the + // suspend handler, we don't want this signal to trigger a + // restart. + resume.sa_flags = 0; + resume.sa_handler = &thread_resumeHandler; + // NOTE: We want to ignore all signals while in this handler, so fill + // sa_mask to indicate this. + status = sigfillset( &resume.sa_mask ); + assert( status == 0 ); + + status = sigaction( suspendSignalNumber, &suspend, null ); + assert( status == 0 ); + + status = sigaction( resumeSignalNumber, &resume, null ); + assert( status == 0 ); + + status = sem_init( &suspendCount, 0, 0 ); + assert( status == 0 ); + } + else + static assert(0, "unsupported os"); + _mainThreadStore[] = cast(void[]) __traits(initSymbol, Thread)[]; + Thread.sm_main = attachThread((cast(Thread)_mainThreadStore.ptr).__ctor()); +} + +private alias MainThreadStore = void[__traits(classInstanceSize, Thread)]; +package __gshared align(__traits(classInstanceAlignment, Thread)) MainThreadStore _mainThreadStore; + +/** + * Terminates the thread module. No other thread routine may be called + * afterwards. + */ +extern (C) void thread_term() @nogc nothrow +{ + thread_term_tpl!(Thread)(_mainThreadStore); +} + + +/////////////////////////////////////////////////////////////////////////////// +// Thread Entry Point and Signal Handlers +/////////////////////////////////////////////////////////////////////////////// + + +version (Windows) +{ + private + { + // + // Entry point for Windows threads + // + extern (Windows) uint thread_entryPoint( void* arg ) nothrow + { + Thread obj = cast(Thread) arg; + assert( obj ); + + obj.initDataStorage(); + + Thread.registerThis(obj); + + scope (exit) + { + // allow the GC to clean up any resources it allocated for this thread. + import core.internal.gc.proxy : gc_getProxy; + gc_getProxy().cleanupThread(obj); + + Thread.remove(obj); + obj.destroyDataStorage(); + } + Thread.add(&obj.m_main); + + // NOTE: No GC allocations may occur until the stack pointers have + // been set and Thread.getThis returns a valid reference to + // this thread object (this latter condition is not strictly + // necessary on Windows but it should be followed for the + // sake of consistency). + + // TODO: Consider putting an auto exception object here (using + // alloca) forOutOfMemoryError plus something to track + // whether an exception is in-flight? + + void append( Throwable t ) + { + obj.filterCaughtThrowable(t); + if (t !is null) + obj.m_unhandled = Throwable.chainTogether(obj.m_unhandled, t); + } + + version (D_InlineAsm_X86) + { + asm nothrow @nogc { fninit; } + } + + try + { + rt_moduleTlsCtor(); + try + { + obj.run(); + } + catch ( Throwable t ) + { + append( t ); + } + rt_moduleTlsDtor(); + } + catch ( Throwable t ) + { + append( t ); + } + return 0; + } + + + HANDLE GetCurrentThreadHandle() nothrow @nogc + { + const uint DUPLICATE_SAME_ACCESS = 0x00000002; + + HANDLE curr = GetCurrentThread(), + proc = GetCurrentProcess(), + hndl; + + DuplicateHandle( proc, curr, proc, &hndl, 0, TRUE, DUPLICATE_SAME_ACCESS ); + return hndl; + } + } +} +else version (Posix) { - import core.atomic; + // NOTE: A thread's cancelability state, determined by pthread_setcancelstate, + // can be enabled (the default for new threads) or disabled. + // If a thread has disabled cancelation, then a cancelation request remains + // queued until the thread enables cancelation. If a thread has enabled + // cancelation, then its cancelability type determines when cancelation occurs. + // + // Call these routines when entering/leaving critical sections of the code that + // are not cancellation points. + + extern (C) int thread_cancelDisable() nothrow + { + static if (__traits(compiles, core.sys.posix.pthread.PTHREAD_CANCEL_DISABLE)) + { + import core.sys.posix.pthread : pthread_setcancelstate, PTHREAD_CANCEL_DISABLE; + int oldstate; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); + return oldstate; + } + else + { + return 0; // No thread cancellation on platform + } + } - shared uint x; - shared bool f; - shared uint r; + extern (C) void thread_cancelRestore(int oldstate) nothrow + { + static if (__traits(compiles, core.sys.posix.pthread.PTHREAD_CANCEL_DISABLE)) + { + import core.sys.posix.pthread : pthread_setcancelstate; + pthread_setcancelstate(oldstate, null); + } + } - auto thr = new Thread(() + private { - while (!atomicLoad(f)) + // + // Entry point for POSIX threads + // + version (CoreDdoc) {} else + extern (C) void* thread_entryPoint( void* arg ) nothrow + { + version (Shared) + { + Thread obj = cast(Thread)(cast(void**)arg)[0]; + auto loadedLibraries = (cast(void**)arg)[1]; + .free(arg); + } + else + { + Thread obj = cast(Thread)arg; + } + assert( obj ); + + // loadedLibraries need to be inherited from parent thread + // before initilizing GC for TLS (rt_tlsgc_init) + version (Shared) + { + externDFunc!("rt.sections_elf_shared.inheritLoadedLibraries", + void function(void*) @nogc nothrow)(loadedLibraries); + } + + obj.initDataStorage(); + + atomicStore!(MemoryOrder.raw)(obj.m_isRunning, true); + + Thread.registerThis(obj); // can only receive signals from here on + + scope (exit) + { + // allow the GC to clean up any resources it allocated for this thread. + import core.internal.gc.proxy : gc_getProxy; + gc_getProxy().cleanupThread(obj); + + Thread.remove(obj); + atomicStore!(MemoryOrder.raw)(obj.m_isRunning, false); + obj.destroyDataStorage(); + } + Thread.add(&obj.m_main); + + static extern (C) void thread_cleanupHandler( void* arg ) nothrow @nogc + { + Thread obj = cast(Thread) arg; + assert( obj ); + + // NOTE: If the thread terminated abnormally, just set it as + // not running and let thread_suspendAll remove it from + // the thread list. This is safer and is consistent + // with the Windows thread code. + atomicStore!(MemoryOrder.raw)(obj.m_isRunning,false); + } + + // NOTE: Using void to skip the initialization here relies on + // knowledge of how pthread_cleanup is implemented. It may + // not be appropriate for all platforms. However, it does + // avoid the need to link the pthread module. If any + // implementation actually requires default initialization + // then pthread_cleanup should be restructured to maintain + // the current lack of a link dependency. + static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup)) + { + import core.sys.posix.pthread : pthread_cleanup; + + pthread_cleanup cleanup = void; + cleanup.push( &thread_cleanupHandler, cast(void*) obj ); + } + else static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup_push)) + { + import core.sys.posix.pthread : pthread_cleanup_push; + + pthread_cleanup_push(&thread_cleanupHandler, cast(void*) obj); + } + else + { + static assert( false, "Platform not supported." ); + } + + // NOTE: No GC allocations may occur until the stack pointers have + // been set and Thread.getThis returns a valid reference to + // this thread object (this latter condition is not strictly + // necessary on Windows but it should be followed for the + // sake of consistency). + + // TODO: Consider putting an auto exception object here (using + // alloca) forOutOfMemoryError plus something to track + // whether an exception is in-flight? + + void append( Throwable t ) + { + obj.filterCaughtThrowable(t); + if (t !is null) + obj.m_unhandled = Throwable.chainTogether(obj.m_unhandled, t); + } + try + { + rt_moduleTlsCtor(); + try + { + obj.run(); + } + catch ( Throwable t ) + { + append( t ); + } + rt_moduleTlsDtor(); + version (Shared) + { + externDFunc!("rt.sections_elf_shared.cleanupLoadedLibraries", + void function() @nogc nothrow)(); + } + } + catch ( Throwable t ) + { + append( t ); + } + + // NOTE: Normal cleanup is handled by scope(exit). + + static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup)) + { + cleanup.pop( 0 ); + } + else static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup_push)) + { + import core.sys.posix.pthread : pthread_cleanup_pop; + + pthread_cleanup_pop( 0 ); + } + + return null; + } + + + // + // Used to track the number of suspended threads + // + __gshared sem_t suspendCount; + + + extern (C) bool thread_preSuspend( void* sp ) nothrow { + // NOTE: Since registers are being pushed and popped from the + // stack, any other stack data used by this function should + // be gone before the stack cleanup code is called below. + Thread obj = Thread.getThis(); + if (obj is null) + { + return false; + } + + if ( !obj.m_lock ) + { + obj.m_curr.tstack = sp; + } + + return true; + } + + extern (C) bool thread_postSuspend() nothrow { + Thread obj = Thread.getThis(); + if (obj is null) + { + return false; + } + + if ( !obj.m_lock ) + { + obj.m_curr.tstack = obj.m_curr.bstack; + } + + return true; + } + + extern (C) void thread_suspendHandler( int sig ) nothrow + in + { + assert( sig == suspendSignalNumber ); + } + do { + void op(void* sp) nothrow + { + int cancel_state = thread_cancelDisable(); + scope(exit) thread_cancelRestore(cancel_state); + + bool supported = thread_preSuspend(getStackTop()); + assert(supported, "Tried to suspend a detached thread!"); + + scope(exit) + { + supported = thread_postSuspend(); + assert(supported, "Tried to suspend a detached thread!"); + } + + sigset_t sigres = void; + int status; + + status = sigfillset( &sigres ); + assert( status == 0 ); + + status = sigdelset( &sigres, resumeSignalNumber ); + assert( status == 0 ); + + status = sem_post( &suspendCount ); + assert( status == 0 ); + + sigsuspend( &sigres ); + } + callWithStackShell(&op); } - atomicFence(); // make sure load+store below happens after waiting for f - cast() r = cast() x; - }); + extern (C) void thread_resumeHandler( int sig ) nothrow + in + { + assert( sig == resumeSignalNumber ); + } + do + { + + } + } +} +else +{ + // NOTE: This is the only place threading versions are checked. If a new + // version is added, the module code will need to be searched for + // places where version-specific code may be required. This can be + // easily accomlished by searching for 'Windows' or 'Posix'. + static assert( false, "Unknown threading implementation." ); +} - thr.start(); // new thread will wait until f is set +// +// exposed by compiler runtime +// +extern (C) void rt_moduleTlsCtor(); +extern (C) void rt_moduleTlsDtor(); - cast() x = 42; - atomicFence(); // make sure x is set before setting f +// regression test for Issue 13416 +version (FreeBSD) unittest +{ + static void loop() + { + pthread_attr_t attr; + pthread_attr_init(&attr); + auto thr = pthread_self(); + foreach (i; 0 .. 50) + pthread_attr_get_np(thr, &attr); + pthread_attr_destroy(&attr); + } - cast() f = true; + auto thr = new Thread(&loop).start(); + foreach (i; 0 .. 50) + { + thread_suspendAll(); + thread_resumeAll(); + } + thr.join(); +} - atomicFence(); +version (DragonFlyBSD) unittest +{ + static void loop() + { + pthread_attr_t attr; + pthread_attr_init(&attr); + auto thr = pthread_self(); + foreach (i; 0 .. 50) + pthread_attr_get_np(thr, &attr); + pthread_attr_destroy(&attr); + } + auto thr = new Thread(&loop).start(); + foreach (i; 0 .. 50) + { + thread_suspendAll(); + thread_resumeAll(); + } thr.join(); +} + + +/////////////////////////////////////////////////////////////////////////////// +// lowlovel threading support +/////////////////////////////////////////////////////////////////////////////// + +private +{ + version (Windows): + // If the runtime is dynamically loaded as a DLL, there is a problem with + // threads still running when the DLL is supposed to be unloaded: + // + // - with the VC runtime starting with VS2015 (i.e. using the Universal CRT) + // a thread created with _beginthreadex increments the DLL reference count + // and decrements it when done, so that the DLL is no longer unloaded unless + // all the threads have terminated. With the DLL reference count held up + // by a thread that is only stopped by a signal from a static destructor or + // the termination of the runtime will cause the DLL to never be unloaded. + // + // - with the DigitalMars runtime and VC runtime up to VS2013, the thread + // continues to run, but crashes once the DLL is unloaded from memory as + // the code memory is no longer accessible. Stopping the threads is not possible + // from within the runtime termination as it is invoked from + // DllMain(DLL_PROCESS_DETACH) holding a lock that prevents threads from + // terminating. + // + // Solution: start a watchdog thread that keeps the DLL reference count above 0 and + // checks it periodically. If it is equal to 1 (plus the number of started threads), no + // external references to the DLL exist anymore, threads can be stopped + // and runtime termination and DLL unload can be invoked via FreeLibraryAndExitThread. + // Note: runtime termination is then performed by a different thread than at startup. + // + // Note: if the DLL is never unloaded, process termination kills all threads + // and signals their handles before unconditionally calling DllMain(DLL_PROCESS_DETACH). + + import core.sys.windows.dll : dll_getRefCount; + import core.sys.windows.winbase : FreeLibraryAndExitThread, GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, GetModuleHandleExW; + import core.sys.windows.windef : HMODULE; + + version (CRuntime_Microsoft) + extern(C) extern __gshared ubyte msvcUsesUCRT; // from rt/msvc.d + extern(C) extern __gshared void* __ImageBase; // symbol at the beginning of module, added by linker + enum HMODULE runtimeModule = &__ImageBase; + + /// set during termination of a DLL on Windows, i.e. while executing DllMain(DLL_PROCESS_DETACH) + public __gshared bool thread_DLLProcessDetaching; + + __gshared ThreadID ll_dllMonitorThread; + + int ll_countLowLevelThreadsWithDLLUnloadCallback(HMODULE hMod) nothrow + { + lowlevelLock.lock_nothrow(); + scope(exit) lowlevelLock.unlock_nothrow(); + + int cnt = 0; + foreach (i; 0 .. ll_nThreads) + if (ll_pThreads[i].cbDllUnload && ll_pThreads[i].hMod == hMod) + cnt++; + return cnt; + } + + bool ll_dllHasExternalReferences(HMODULE hMod) nothrow + { + int unloadCallbacks = ll_countLowLevelThreadsWithDLLUnloadCallback(hMod); + int internalReferences = hMod != runtimeModule ? unloadCallbacks + : (ll_dllMonitorThread ? 1 : 0) + (msvcUsesUCRT ? unloadCallbacks : 0); + int refcnt = dll_getRefCount(hMod); + return refcnt > internalReferences; + } + + void notifyUnloadLowLevelThreads(HMODULE hMod) nothrow + { + HMODULE toFree; + for (;;) + { + ThreadID tid; + void delegate() nothrow cbDllUnload; + { + lowlevelLock.lock_nothrow(); + scope(exit) lowlevelLock.unlock_nothrow(); + + foreach (i; 0 .. ll_nThreads) + if (ll_pThreads[i].cbDllUnload && ll_pThreads[i].hMod == hMod) + { + if (!toFree) + toFree = ll_getModuleHandle(hMod, true); // keep the module alive until the callback returns + cbDllUnload = ll_pThreads[i].cbDllUnload; + tid = ll_pThreads[i].tid; + break; + } + } + if (!cbDllUnload) + break; + cbDllUnload(); // must wait for thread termination + assert(!findLowLevelThread(tid)); + } + if (toFree) + FreeLibrary(toFree); + } + + private void monitorDLLRefCnt() nothrow + { + // this thread keeps the DLL alive until all external references are gone + // (including those from DLLs using druntime in a shared DLL) + while (ll_dllHasExternalReferences(runtimeModule)) + { + // find and unload module that only has internal references left + HMODULE hMod; + { + lowlevelLock.lock_nothrow(); + scope(exit) lowlevelLock.unlock_nothrow(); + + foreach (i; 0 .. ll_nThreads) + if (ll_pThreads[i].cbDllUnload && ll_pThreads[i].hMod != runtimeModule) + if (!ll_dllHasExternalReferences(ll_pThreads[i].hMod)) + { + hMod = ll_pThreads[i].hMod; + break; + } + } + if (hMod) + notifyUnloadLowLevelThreads(hMod); + else + Thread.sleep(100.msecs); + } + + notifyUnloadLowLevelThreads(runtimeModule); + + // the current thread will be terminated without cleanup within the thread + ll_removeThread(GetCurrentThreadId()); + + FreeLibraryAndExitThread(runtimeModule, 0); + } + + HMODULE ll_getModuleHandle(void* funcptr, bool addref = false) nothrow @nogc + { + HMODULE hmod; + DWORD refflag = addref ? 0 : GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT; + if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | refflag, + cast(const(wchar)*) funcptr, &hmod)) + return null; + return hmod; + } + + bool ll_startDLLUnloadThread() nothrow @nogc + { + if (ll_dllMonitorThread !is ThreadID.init) + return true; + + // if a thread is created from a DLL, the MS runtime (starting with VC2015) increments the DLL reference count + // to avoid the DLL being unloaded while the thread is still running. Mimick this behavior here for all + // runtimes not doing this + bool needRef = !msvcUsesUCRT; + if (needRef) + ll_getModuleHandle(runtimeModule, true); + + // the monitor thread must be a low-level thread so the runtime does not attach to it + ll_dllMonitorThread = createLowLevelThread(() { monitorDLLRefCnt(); }); + return ll_dllMonitorThread != ThreadID.init; + } +} + +/** + * Create a thread not under control of the runtime, i.e. TLS module constructors are + * not run and the GC does not suspend it during a collection. + * + * Params: + * dg = delegate to execute in the created thread. + * stacksize = size of the stack of the created thread. The default of 0 will select the + * platform-specific default size. + * cbDllUnload = Windows only: if running in a dynamically loaded DLL, this delegate will be called + * if the DLL is supposed to be unloaded, but the thread is still running. + * The thread must be terminated via `joinLowLevelThread` by the callback. + * + * Returns: the platform specific thread ID of the new thread. If an error occurs, `ThreadID.init` + * is returned. + */ +ThreadID createLowLevelThread(void delegate() nothrow dg, uint stacksize = 0, + void delegate() nothrow cbDllUnload = null) nothrow @nogc +{ + static struct Context + { + void delegate() nothrow dg; + version (Windows) + HMODULE cbMod; + } + auto context = cast(Context*)malloc(Context.sizeof); + scope(exit) free(context); + context.dg = dg; + + ThreadID tid; + version (Windows) + { + // the thread won't start until after the DLL is unloaded + if (thread_DLLProcessDetaching) + return ThreadID.init; + context.cbMod = cbDllUnload ? ll_getModuleHandle(cbDllUnload.funcptr) : null; + if (context.cbMod) + { + int refcnt = dll_getRefCount(context.cbMod); + if (refcnt < 0) + { + // not a dynamically loaded DLL, so never unloaded + cbDllUnload = null; + context.cbMod = null; + } + if (refcnt == 0) + return ThreadID.init; // createLowLevelThread called while DLL is unloading + } + + static extern (Windows) uint thread_lowlevelEntry(void* ctx) nothrow + { + auto context = *cast(Context*)ctx; + free(ctx); + + context.dg(); + + ll_removeThread(GetCurrentThreadId()); + if (context.cbMod && context.cbMod != runtimeModule) + FreeLibrary(context.cbMod); + return 0; + } + + // see Thread.start() for why thread is created in suspended state + HANDLE hThread = cast(HANDLE) _beginthreadex(null, stacksize, &thread_lowlevelEntry, + context, CREATE_SUSPENDED, &tid); + if (!hThread) + return ThreadID.init; + } + + lowlevelLock.lock_nothrow(); + scope(exit) lowlevelLock.unlock_nothrow(); + + ll_nThreads++; + ll_pThreads = cast(ll_ThreadData*)realloc(ll_pThreads, ll_ThreadData.sizeof * ll_nThreads); + ll_pThreads[ll_nThreads - 1] = ll_ThreadData.init; + + version (Windows) + { + ll_pThreads[ll_nThreads - 1].tid = tid; + // ignore callback if not a dynamically loaded DLL + if (cbDllUnload) + { + ll_pThreads[ll_nThreads - 1].cbDllUnload = cbDllUnload; + ll_pThreads[ll_nThreads - 1].hMod = context.cbMod; + if (context.cbMod != runtimeModule) + ll_getModuleHandle(context.cbMod, true); // increment ref count + } + + if (ResumeThread(hThread) == -1) + onThreadError("Error resuming thread"); + CloseHandle(hThread); + + if (cbDllUnload) + ll_startDLLUnloadThread(); + } + else version (Posix) + { + static extern (C) void* thread_lowlevelEntry(void* ctx) nothrow + { + auto context = *cast(Context*)ctx; + free(ctx); + + context.dg(); + ll_removeThread(pthread_self()); + return null; + } + + size_t stksz = adjustStackSize(stacksize); + + pthread_attr_t attr; + + int rc; + if ((rc = pthread_attr_init(&attr)) != 0) + return ThreadID.init; + if (stksz && (rc = pthread_attr_setstacksize(&attr, stksz)) != 0) + return ThreadID.init; + if ((rc = pthread_create(&tid, &attr, &thread_lowlevelEntry, context)) != 0) + return ThreadID.init; + rc = pthread_attr_destroy(&attr); + assert(rc == 0); + + ll_pThreads[ll_nThreads - 1].tid = tid; + } + else + static assert(0, "unsupported os"); + context = null; // free'd in thread + return tid; +} + +/** + * Wait for a thread created with `createLowLevelThread` to terminate. + * + * Note: In a Windows DLL, if this function is called via DllMain with + * argument DLL_PROCESS_DETACH, the thread is terminated forcefully + * without proper cleanup as a deadlock would happen otherwise. + * + * Params: + * tid = the thread ID returned by `createLowLevelThread`. + */ +void joinLowLevelThread(ThreadID tid) nothrow @nogc +{ + version (Windows) + { + HANDLE handle = OpenThreadHandle(tid); + if (!handle) + return; + + if (thread_DLLProcessDetaching) + { + // When being called from DllMain/DLL_DETACH_PROCESS, threads cannot stop + // due to the loader lock being held by the current thread. + // On the other hand, the thread must not continue to run as it will crash + // if the DLL is unloaded. The best guess is to terminate it immediately. + TerminateThread(handle, 1); + WaitForSingleObject(handle, 10); // give it some time to terminate, but don't wait indefinitely + } + else + WaitForSingleObject(handle, INFINITE); + CloseHandle(handle); + } + else version (Posix) + { + if (pthread_join(tid, null) != 0) + onThreadError("Unable to join thread"); + } + else + static assert(0, "unsupported os"); +} + +nothrow @nogc unittest +{ + struct TaskWithContect + { + shared int n = 0; + void run() nothrow + { + n.atomicOp!"+="(1); + } + } + TaskWithContect task; + + ThreadID[8] tids; + for (int i = 0; i < tids.length; i++) + { + tids[i] = createLowLevelThread(&task.run); + assert(tids[i] != ThreadID.init); + } + + for (int i = 0; i < tids.length; i++) + joinLowLevelThread(tids[i]); + + assert(task.n == tids.length); +} + +version (Posix) +private size_t adjustStackSize(size_t sz) nothrow @nogc +{ + if (sz == 0) + return 0; + + // stack size must be at least PTHREAD_STACK_MIN for most platforms. + if (PTHREAD_STACK_MIN > sz) + sz = PTHREAD_STACK_MIN; + + version (CRuntime_Glibc) + { + // On glibc, TLS uses the top of the stack, so add its size to the requested size + sz += externDFunc!("rt.sections_elf_shared.sizeOfTLS", + size_t function() @nogc nothrow)(); + } + + // stack size must be a multiple of pageSize + sz = ((sz + pageSize - 1) & ~(pageSize - 1)); - assert(cast() r == 42); + return sz; } From 52942f647e896b69c00f283fb790975fbc80047f Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Wed, 29 Apr 2026 12:39:22 +0300 Subject: [PATCH 04/32] *_impl.d added to COPY and SRC lists, public import added to osthread --- druntime/mak/COPY | 2 ++ druntime/mak/SRCS | 2 ++ druntime/src/core/thread/osthread.d | 7 +++++++ druntime/src/core/thread/posix_impl.d | 2 +- druntime/src/core/thread/windows_impl.d | 2 +- 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/druntime/mak/COPY b/druntime/mak/COPY index 5bd19b0b46c3..67a929a53122 100644 --- a/druntime/mak/COPY +++ b/druntime/mak/COPY @@ -571,6 +571,8 @@ COPY=\ $(IMPDIR)\core\thread\threadgroup.d \ $(IMPDIR)\core\thread\threadbase.d \ $(IMPDIR)\core\thread\osthread.d \ + $(IMPDIR)\core\thread\posix_impl.d \ + $(IMPDIR)\core\thread\windows_impl.d \ $(IMPDIR)\core\thread\package.d \ \ $(IMPDIR)\etc\valgrind\valgrind.d \ diff --git a/druntime/mak/SRCS b/druntime/mak/SRCS index a4bb81f74abb..2e59749dd842 100644 --- a/druntime/mak/SRCS +++ b/druntime/mak/SRCS @@ -563,6 +563,8 @@ SRCS=\ src\core\thread\threadgroup.d \ src\core\thread\threadbase.d \ src\core\thread\osthread.d \ + src\core\thread\posix_impl.d \ + src\core\thread\windows_impl.d \ src\core\thread\context.d \ src\core\thread\package.d \ \ diff --git a/druntime/src/core/thread/osthread.d b/druntime/src/core/thread/osthread.d index f42f6a9c7409..0c3daa294f0c 100644 --- a/druntime/src/core/thread/osthread.d +++ b/druntime/src/core/thread/osthread.d @@ -25,6 +25,13 @@ import core.time; // Platform Detection and Memory Allocation /////////////////////////////////////////////////////////////////////////////// +version (Posix) + public import core.thread.posix_impl; +else version (Windows) + public import core.thread.windows_impl; +else + static assert(0, "unsupported operating system"); + version (OSX) version = Darwin; else version (iOS) diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index f42f6a9c7409..3a27cd02564f 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -10,7 +10,7 @@ * Source: $(DRUNTIMESRC core/thread/osthread.d) */ -module core.thread.osthread; +module core.thread.posix_impl; import core.atomic; import core.exception : onOutOfMemoryError; diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index f42f6a9c7409..4a9255af3333 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -10,7 +10,7 @@ * Source: $(DRUNTIMESRC core/thread/osthread.d) */ -module core.thread.osthread; +module core.thread.windows_impl; import core.atomic; import core.exception : onOutOfMemoryError; From ee0689528199160d7971f2a1045d5b9042f7dd7d Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:11:12 +0300 Subject: [PATCH 05/32] posix thread: class Thread removed from osthread, some unused code removed --- druntime/src/core/thread/osthread.d | 684 ------- druntime/src/core/thread/posix_impl.d | 2430 +------------------------ 2 files changed, 6 insertions(+), 3108 deletions(-) diff --git a/druntime/src/core/thread/osthread.d b/druntime/src/core/thread/osthread.d index 0c3daa294f0c..af781a66a4c6 100644 --- a/druntime/src/core/thread/osthread.d +++ b/druntime/src/core/thread/osthread.d @@ -431,690 +431,6 @@ class Thread : ThreadBase } } -version (CoreDdoc) {} else -class Thread : ThreadBase -{ - version (Windows) - { - private HANDLE m_hndl; - } - - version (Posix) - { - private shared bool m_isRunning; - } - - version (Darwin) - { - private mach_port_t m_tmach; - } - - version (Solaris) - { - private __gshared bool m_isRTClass; - } - - version (Windows) - { - alias TLSKey = uint; - } - else version (Posix) - { - alias TLSKey = pthread_key_t; - } - else - static assert(0, "unsupported os"); - - this( void function() fn, size_t sz = 0 ) @safe pure nothrow @nogc - { - super(fn, sz); - } - - this( void delegate() dg, size_t sz = 0 ) @safe pure nothrow @nogc - { - super(dg, sz); - } - - package this( size_t sz = 0 ) @safe pure nothrow @nogc - { - super(sz); - } - - ~this() nothrow @nogc - { - if (super.destructBeforeDtor()) - return; - - version (Windows) - { - m_addr = m_addr.init; - CloseHandle( m_hndl ); - m_hndl = m_hndl.init; - } - else version (Posix) - { - if (m_addr != m_addr.init) - pthread_detach( m_addr ); - m_addr = m_addr.init; - version (Darwin) - { - m_tmach = m_tmach.init; - } - } - else - static assert(0, "unsupported OS"); - } - - private final void run() - { - super.run(); - } - - static Thread getThis() @safe nothrow @nogc - { - return ThreadBase.getThis().toThread; - } - - version (Windows) - { - version (X86) - { - uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax - } - else version (X86_64) - { - ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax - // r8,r9,r10,r11,r12,r13,r14,r15 - } - else - { - static assert(false, "Architecture not supported." ); - } - } - else version (Darwin) - { - version (X86) - { - uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax - } - else version (X86_64) - { - ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax - // r8,r9,r10,r11,r12,r13,r14,r15 - } - else version (AArch64) - { - ulong[33] m_reg; // x0-x31, pc - } - else version (ARM) - { - uint[16] m_reg; // r0-r15 - } - else version (PPC) - { - // Make the assumption that we only care about non-fp and non-vr regs. - // ??? : it seems plausible that a valid address can be copied into a VR. - uint[32] m_reg; // r0-31 - } - else version (PPC64) - { - // As above. - ulong[32] m_reg; // r0-31 - } - else - { - static assert(false, "Architecture not supported." ); - } - } - else version (Solaris) - { - version (X86) - { - uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax - } - else version (X86_64) - { - ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax - // r8,r9,r10,r11,r12,r13,r14,r15 - } - else version (SPARC) - { - int[33] m_reg; // g0-7, o0-7, l0-7, i0-7, pc - } - else version (SPARC64) - { - long[33] m_reg; // g0-7, o0-7, l0-7, i0-7, pc - } - else - { - static assert(false, "Architecture not supported." ); - } - } - - override final void[] savedRegisters() nothrow @nogc - { - version (Windows) - { - return m_reg; - } - else version (Darwin) - { - return m_reg; - } - else version (Solaris) - { - return m_reg; - } - else - { - return null; - } - } - - final Thread start() nothrow - in - { - assert( !next && !prev ); - } - do - { - auto wasThreaded = multiThreadedFlag; - multiThreadedFlag = true; - scope( failure ) - { - if ( !wasThreaded ) - multiThreadedFlag = false; - } - - version (Windows) - { - // NOTE: If a thread is just executing DllMain() - // while another thread is started here, it holds an OS internal - // lock that serializes DllMain with CreateThread. As the code - // might request a synchronization on slock (e.g. in thread_findByAddr()), - // we cannot hold that lock while creating the thread without - // creating a deadlock - // - // Solution: Create the thread in suspended state and then - // add and resume it with slock acquired - assert(m_sz <= uint.max, "m_sz must be less than or equal to uint.max"); - m_hndl = cast(HANDLE) _beginthreadex( null, cast(uint) m_sz, &thread_entryPoint, cast(void*) this, CREATE_SUSPENDED, &m_addr ); - if ( cast(size_t) m_hndl == 0 ) - onThreadError( "Error creating thread" ); - } - else version (Posix) - { - size_t stksz = adjustStackSize( m_sz ); - - pthread_attr_t attr; - - if ( pthread_attr_init( &attr ) ) - onThreadError( "Error initializing thread attributes" ); - if ( stksz && pthread_attr_setstacksize( &attr, stksz ) ) - onThreadError( "Error initializing thread stack size" ); - } - else - static assert(0, "unsupported OS"); - - slock.lock_nothrow(); - scope(exit) slock.unlock_nothrow(); - { - incrementAboutToStart(this); - - version (Windows) - { - if ( ResumeThread( m_hndl ) == -1 ) - onThreadError( "Error resuming thread" ); - } - else version (Posix) - { - // NOTE: This is also set to true by thread_entryPoint, but set it - // here as well so the calling thread will see the isRunning - // state immediately. - atomicStore!(MemoryOrder.raw)(m_isRunning, true); - scope( failure ) atomicStore!(MemoryOrder.raw)(m_isRunning, false); - - version (Shared) - { - auto libs = externDFunc!("rt.sections_elf_shared.pinLoadedLibraries", - void* function() @nogc nothrow)(); - - auto ps = cast(void**).malloc(2 * size_t.sizeof); - if (ps is null) onOutOfMemoryError(); - ps[0] = cast(void*)this; - ps[1] = cast(void*)libs; - if ( pthread_create( &m_addr, &attr, &thread_entryPoint, ps ) != 0 ) - { - externDFunc!("rt.sections_elf_shared.unpinLoadedLibraries", - void function(void*) @nogc nothrow)(libs); - .free(ps); - onThreadError( "Error creating thread" ); - } - } - else - { - if ( pthread_create( &m_addr, &attr, &thread_entryPoint, cast(void*) this ) != 0 ) - onThreadError( "Error creating thread" ); - } - if ( pthread_attr_destroy( &attr ) != 0 ) - onThreadError( "Error destroying thread attributes" ); - - version (Darwin) - { - m_tmach = pthread_mach_thread_np( m_addr ); - if ( m_tmach == m_tmach.init ) - onThreadError( "Error creating thread" ); - } - } - else - static assert(0, "unsupported OS"); - - return this; - } - } - - override final Throwable join( bool rethrow = true ) - { - version (Windows) - { - if ( m_addr != m_addr.init && WaitForSingleObject( m_hndl, INFINITE ) != WAIT_OBJECT_0 ) - throw new ThreadException( "Unable to join thread" ); - // NOTE: m_addr must be cleared before m_hndl is closed to avoid - // a race condition with isRunning. The operation is done - // with atomicStore to prevent compiler reordering. - atomicStore!(MemoryOrder.raw)(*cast(shared)&m_addr, m_addr.init); - CloseHandle( m_hndl ); - m_hndl = m_hndl.init; - } - else version (Posix) - { - if ( m_addr != m_addr.init && pthread_join( m_addr, null ) != 0 ) - throw new ThreadException( "Unable to join thread" ); - // NOTE: pthread_join acts as a substitute for pthread_detach, - // which is normally called by the dtor. Setting m_addr - // to zero ensures that pthread_detach will not be called - // on object destruction. - m_addr = m_addr.init; - } - else - static assert(0, "unsupported OS"); - - if ( m_unhandled ) - { - if ( rethrow ) - throw m_unhandled; - return m_unhandled; - } - return null; - } - - version (Windows) - { - @property static int PRIORITY_MIN() @nogc nothrow pure @safe - { - return THREAD_PRIORITY_IDLE; - } - - @property static const(int) PRIORITY_MAX() @nogc nothrow pure @safe - { - return THREAD_PRIORITY_TIME_CRITICAL; - } - - @property static int PRIORITY_DEFAULT() @nogc nothrow pure @safe - { - return THREAD_PRIORITY_NORMAL; - } - } - else version (Posix) - { - private struct Priority - { - int PRIORITY_MIN = int.min; - int PRIORITY_DEFAULT = int.min; - int PRIORITY_MAX = int.min; - } - - /* - Lazily loads one of the members stored in a hidden global variable of - type `Priority`. Upon the first access of either member, the entire - `Priority` structure is initialized. Multiple initializations from - different threads calling this function are tolerated. - - `which` must be one of `PRIORITY_MIN`, `PRIORITY_DEFAULT`, - `PRIORITY_MAX`. - */ - private static shared Priority cache; - private static int loadGlobal(string which)() - { - auto local = atomicLoad(mixin("cache." ~ which)); - if (local != local.min) return local; - // There will be benign races - auto loaded = loadPriorities; - static foreach (i, _; loaded.tupleof) - atomicStore(cache.tupleof[i], loaded.tupleof[i]); - return atomicLoad(mixin("cache." ~ which)); - } - - /* - Loads all priorities and returns them as a `Priority` structure. This - function is thread-neutral. - */ - private static Priority loadPriorities() @nogc nothrow @trusted - { - Priority result; - version (Solaris) - { - pcparms_t pcParms; - pcinfo_t pcInfo; - - pcParms.pc_cid = PC_CLNULL; - if (priocntl(idtype_t.P_PID, P_MYID, PC_GETPARMS, &pcParms) == -1) - assert( 0, "Unable to get scheduling class" ); - - pcInfo.pc_cid = pcParms.pc_cid; - // PC_GETCLINFO ignores the first two args, use dummy values - if (priocntl(idtype_t.P_PID, 0, PC_GETCLINFO, &pcInfo) == -1) - assert( 0, "Unable to get scheduling class info" ); - - pri_t* clparms = cast(pri_t*)&pcParms.pc_clparms; - pri_t* clinfo = cast(pri_t*)&pcInfo.pc_clinfo; - - result.PRIORITY_MAX = clparms[0]; - - if (pcInfo.pc_clname == "RT") - { - m_isRTClass = true; - - // For RT class, just assume it can't be changed - result.PRIORITY_MIN = clparms[0]; - result.PRIORITY_DEFAULT = clparms[0]; - } - else - { - m_isRTClass = false; - - // For all other scheduling classes, there are - // two key values -- uprilim and maxupri. - // maxupri is the maximum possible priority defined - // for the scheduling class, and valid priorities - // range are in [-maxupri, maxupri]. - // - // However, uprilim is an upper limit that the - // current thread can set for the current scheduling - // class, which can be less than maxupri. As such, - // use this value for priorityMax since this is - // the effective maximum. - - // maxupri - result.PRIORITY_MIN = -cast(int)(clinfo[0]); - // by definition - result.PRIORITY_DEFAULT = 0; - } - } - else - { - int policy; - sched_param param; - pthread_getschedparam( pthread_self(), &policy, ¶m ) == 0 - || assert(0, "Internal error in pthread_getschedparam"); - - result.PRIORITY_MIN = sched_get_priority_min( policy ); - result.PRIORITY_MIN != -1 - || assert(0, "Internal error in sched_get_priority_min"); - result.PRIORITY_DEFAULT = param.sched_priority; - result.PRIORITY_MAX = sched_get_priority_max( policy ); - result.PRIORITY_MAX != -1 || - assert(0, "Internal error in sched_get_priority_max"); - } - return result; - } - - @property static int PRIORITY_MIN() @nogc nothrow pure @trusted - { - return (cast(int function() @nogc nothrow pure @safe) - &loadGlobal!"PRIORITY_MIN")(); - } - - @property static const(int) PRIORITY_MAX() @nogc nothrow pure @trusted - { - return (cast(int function() @nogc nothrow pure @safe) - &loadGlobal!"PRIORITY_MAX")(); - } - - @property static int PRIORITY_DEFAULT() @nogc nothrow pure @trusted - { - return (cast(int function() @nogc nothrow pure @safe) - &loadGlobal!"PRIORITY_DEFAULT")(); - } - } - else - static assert(0, "unsupported OS"); - - - version (NetBSD) - { - //NetBSD does not support priority for default policy - // and it is not possible change policy without root access - int fakePriority = int.max; - } - - final @property int priority() - { - version (Windows) - { - return GetThreadPriority( m_hndl ); - } - else version (NetBSD) - { - return fakePriority==int.max? PRIORITY_DEFAULT : fakePriority; - } - else version (Posix) - { - int policy; - sched_param param; - - if (auto err = pthread_getschedparam(m_addr, &policy, ¶m)) - { - // ignore error if thread is not running => Bugzilla 8960 - if (!atomicLoad(m_isRunning)) return PRIORITY_DEFAULT; - throw new ThreadException("Unable to get thread priority"); - } - return param.sched_priority; - } - else - static assert(0, "unsupported os"); - } - - final @property void priority( int val ) - in - { - assert(val >= PRIORITY_MIN); - assert(val <= PRIORITY_MAX); - } - do - { - version (Windows) - { - if ( !SetThreadPriority( m_hndl, val ) ) - throw new ThreadException( "Unable to set thread priority" ); - } - else version (Solaris) - { - // the pthread_setschedprio(3c) and pthread_setschedparam functions - // are broken for the default (TS / time sharing) scheduling class. - // instead, we use priocntl(2) which gives us the desired behavior. - - // We hardcode the min and max priorities to the current value - // so this is a no-op for RT threads. - if (m_isRTClass) - return; - - pcparms_t pcparm; - - pcparm.pc_cid = PC_CLNULL; - if (priocntl(idtype_t.P_LWPID, P_MYID, PC_GETPARMS, &pcparm) == -1) - throw new ThreadException( "Unable to get scheduling class" ); - - pri_t* clparms = cast(pri_t*)&pcparm.pc_clparms; - - // clparms is filled in by the PC_GETPARMS call, only necessary - // to adjust the element that contains the thread priority - clparms[1] = cast(pri_t) val; - - if (priocntl(idtype_t.P_LWPID, P_MYID, PC_SETPARMS, &pcparm) == -1) - throw new ThreadException( "Unable to set scheduling class" ); - } - else version (NetBSD) - { - fakePriority = val; - } - else version (Posix) - { - static if (__traits(compiles, core.sys.posix.pthread.pthread_setschedprio)) - { - import core.sys.posix.pthread : pthread_setschedprio; - - if (auto err = pthread_setschedprio(m_addr, val)) - { - // ignore error if thread is not running => Bugzilla 8960 - if (!atomicLoad(m_isRunning)) return; - throw new ThreadException("Unable to set thread priority"); - } - } - else - { - // NOTE: pthread_setschedprio is not implemented on Darwin, FreeBSD, OpenBSD, - // or DragonFlyBSD, so use the more complicated get/set sequence below. - int policy; - sched_param param; - - if (auto err = pthread_getschedparam(m_addr, &policy, ¶m)) - { - // ignore error if thread is not running => Bugzilla 8960 - if (!atomicLoad(m_isRunning)) return; - throw new ThreadException("Unable to set thread priority"); - } - param.sched_priority = val; - if (auto err = pthread_setschedparam(m_addr, policy, ¶m)) - { - // ignore error if thread is not running => Bugzilla 8960 - if (!atomicLoad(m_isRunning)) return; - throw new ThreadException("Unable to set thread priority"); - } - } - } - else - static assert(0, "unsupported os"); - } - - - unittest - { - auto thr = Thread.getThis(); - immutable prio = thr.priority; - scope (exit) thr.priority = prio; - - assert(prio == PRIORITY_DEFAULT); - assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); - thr.priority = PRIORITY_MIN; - assert(thr.priority == PRIORITY_MIN); - thr.priority = PRIORITY_MAX; - assert(thr.priority == PRIORITY_MAX); - } - - unittest // Bugzilla 8960 - { - import core.sync.semaphore; - - auto thr = new Thread({}); - thr.start(); - Thread.sleep(1.msecs); // wait a little so the thread likely has finished - thr.priority = PRIORITY_MAX; // setting priority doesn't cause error - auto prio = thr.priority; // getting priority doesn't cause error - assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); - } - - override final @property bool isRunning() nothrow @nogc - { - if (!super.isRunning()) - return false; - - version (Windows) - { - uint ecode = 0; - GetExitCodeThread( m_hndl, &ecode ); - return ecode == STILL_ACTIVE; - } - else version (Posix) - { - return atomicLoad(m_isRunning); - } - else - static assert(0, "unsupported os"); - } - - static void sleep( Duration val ) @nogc nothrow @trusted - in - { - assert( !val.isNegative ); - } - do - { - version (Windows) - { - auto maxSleepMillis = dur!("msecs")( uint.max - 1 ); - - // avoid a non-zero time to be round down to 0 - if ( val > dur!"msecs"( 0 ) && val < dur!"msecs"( 1 ) ) - val = dur!"msecs"( 1 ); - - // NOTE: In instances where all other threads in the process have a - // lower priority than the current thread, the current thread - // will not yield with a sleep time of zero. However, unlike - // yield(), the user is not asking for a yield to occur but - // only for execution to suspend for the requested interval. - // Therefore, expected performance may not be met if a yield - // is forced upon the user. - while ( val > maxSleepMillis ) - { - Sleep( cast(uint) - maxSleepMillis.total!"msecs" ); - val -= maxSleepMillis; - } - Sleep( cast(uint) val.total!"msecs" ); - } - else version (Posix) - { - timespec tin = void; - timespec tout = void; - - val.split!("seconds", "nsecs")(tin.tv_sec, tin.tv_nsec); - if ( val.total!"seconds" > tin.tv_sec.max ) - tin.tv_sec = tin.tv_sec.max; - while ( true ) - { - if ( !nanosleep( &tin, &tout ) ) - return; - if ( errno != EINTR ) - assert(0, "Unable to sleep for the specified duration"); - tin = tout; - } - } - else - static assert(0, "unsupported os"); - } - - static void yield() @nogc nothrow - { - version (Windows) - SwitchToThread(); - else version (Posix) - sched_yield(); - else - static assert(0, "unsupported os"); - } -} - private Thread toThread(return scope ThreadBase t) @trusted nothrow @nogc pure { return cast(Thread) cast(void*) t; diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index 3a27cd02564f..a59e81b39dd7 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -25,6 +25,8 @@ import core.time; // Platform Detection and Memory Allocation /////////////////////////////////////////////////////////////////////////////// +version (Posix): + version (OSX) version = Darwin; else version (iOS) @@ -53,24 +55,7 @@ else version (D_InlineAsm_X86_64) } } -version (Windows) -{ - import core.stdc.stdint : uintptr_t; // for _beginthreadex decl below - import core.stdc.stdlib : free, malloc, realloc; - import core.sys.windows.basetsd /+: HANDLE+/; - import core.sys.windows.threadaux /+: getThreadStackBottom, impersonate_thread, OpenThreadHandle+/; - import core.sys.windows.winbase /+: CloseHandle, CREATE_SUSPENDED, DuplicateHandle, GetCurrentThread, - GetCurrentThreadId, GetCurrentProcess, GetExitCodeThread, GetSystemInfo, GetThreadContext, - GetThreadPriority, INFINITE, ResumeThread, SetThreadPriority, Sleep, STILL_ACTIVE, - SuspendThread, SwitchToThread, SYSTEM_INFO, THREAD_PRIORITY_IDLE, THREAD_PRIORITY_NORMAL, - THREAD_PRIORITY_TIME_CRITICAL, WAIT_OBJECT_0, WaitForSingleObject+/; - import core.sys.windows.windef /+: TRUE+/; - import core.sys.windows.winnt /+: CONTEXT, CONTEXT_CONTROL, CONTEXT_INTEGER+/; - - private extern (Windows) alias btex_fptr = uint function(void*); - private extern (C) uintptr_t _beginthreadex(void*, uint, btex_fptr, void*, uint, uint*) nothrow @nogc; -} -else version (Posix) +version (Posix) { static import core.sys.posix.pthread; static import core.sys.posix.signal; @@ -141,289 +126,6 @@ version (GNU) import gcc.builtins; } -/** - * Hook for whatever EH implementation is used to save/restore some data - * per stack. - * - * Params: - * newContext = The return value of the prior call to this function - * where the stack was last swapped out, or null when a fiber stack - * is switched in for the first time. - */ -private extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc; - -version (DigitalMars) -{ - version (Windows) - { - extern(D) void* swapContext(void* newContext) nothrow @nogc - { - return _d_eh_swapContext(newContext); - } - } - else - { - extern(C) void* _d_eh_swapContextDwarf(void* newContext) nothrow @nogc; - - extern(D) void* swapContext(void* newContext) nothrow @nogc - { - /* Detect at runtime which scheme is being used. - * Eventually, determine it statically. - */ - static int which = 0; - final switch (which) - { - case 0: - { - assert(newContext == null); - auto p = _d_eh_swapContext(newContext); - auto pdwarf = _d_eh_swapContextDwarf(newContext); - if (p) - { - which = 1; - return p; - } - else if (pdwarf) - { - which = 2; - return pdwarf; - } - return null; - } - case 1: - return _d_eh_swapContext(newContext); - case 2: - return _d_eh_swapContextDwarf(newContext); - } - } - } -} -else -{ - extern(D) void* swapContext(void* newContext) nothrow @nogc - { - return _d_eh_swapContext(newContext); - } -} - -/** - * This class encapsulates all threading functionality for the D - * programming language. As thread manipulation is a required facility - * for garbage collection, all user threads should derive from this - * class, and instances of this class should never be explicitly deleted. - * A new thread may be created using either derivation or composition, as - * in the following example. - */ -version (CoreDdoc) -class Thread : ThreadBase -{ - /** - * Initializes a thread object which is associated with a static - * D function. - * - * Params: - * fn = The thread function. - * sz = The stack size for this thread. - * - * In: - * fn must not be null. - */ - this( void function() fn, size_t sz = 0 ) @safe pure nothrow @nogc - { - } - - - /** - * Initializes a thread object which is associated with a dynamic - * D function. - * - * Params: - * dg = The thread function. - * sz = The stack size for this thread. - * - * In: - * dg must not be null. - */ - this( void delegate() dg, size_t sz = 0 ) @safe pure nothrow @nogc - { - } - - package this( size_t sz = 0 ) @safe pure nothrow @nogc - { - } - - /** - * Cleans up any remaining resources used by this object. - */ - ~this() nothrow @nogc - { - } - - // - // Thread entry point. Invokes the function or delegate passed on - // construction (if any). - // - private final void run() - { - } - - /** - * Provides a reference to the calling thread. - * - * Returns: - * The thread object representing the calling thread. The result of - * deleting this object is undefined. If the current thread is not - * attached to the runtime, a null reference is returned. - */ - static Thread getThis() @safe nothrow @nogc - { - return null; - } - - /// - override final void[] savedRegisters() nothrow @nogc - { - return null; - } - - /** - * Starts the thread and invokes the function or delegate passed upon - * construction. - * - * In: - * This routine may only be called once per thread instance. - * - * Throws: - * ThreadException if the thread fails to start. - */ - final Thread start() nothrow - { - return null; - } - - /** - * Waits for this thread to complete. If the thread terminated as the - * result of an unhandled exception, this exception will be rethrown. - * - * Params: - * rethrow = Rethrow any unhandled exception which may have caused this - * thread to terminate. - * - * Throws: - * ThreadException if the operation fails. - * Any exception not handled by the joined thread. - * - * Returns: - * Any exception not handled by this thread if rethrow = false, null - * otherwise. - */ - override final Throwable join( bool rethrow = true ) - { - return null; - } - - /** - * The minimum scheduling priority that may be set for a thread. On - * systems where multiple scheduling policies are defined, this value - * represents the minimum valid priority for the scheduling policy of - * the process. - */ - @property static int PRIORITY_MIN() @nogc nothrow pure @trusted - { - return 0; - } - - /** - * The maximum scheduling priority that may be set for a thread. On - * systems where multiple scheduling policies are defined, this value - * represents the maximum valid priority for the scheduling policy of - * the process. - */ - @property static const(int) PRIORITY_MAX() @nogc nothrow pure @trusted - { - return 0; - } - - /** - * The default scheduling priority that is set for a thread. On - * systems where multiple scheduling policies are defined, this value - * represents the default priority for the scheduling policy of - * the process. - */ - @property static int PRIORITY_DEFAULT() @nogc nothrow pure @trusted - { - return 0; - } - - /** - * Gets the scheduling priority for the associated thread. - * - * Note: Getting the priority of a thread that already terminated - * might return the default priority. - * - * Returns: - * The scheduling priority of this thread. - */ - final @property int priority() - { - return 0; - } - - /** - * Sets the scheduling priority for the associated thread. - * - * Note: Setting the priority of a thread that already terminated - * might have no effect. - * - * Params: - * val = The new scheduling priority of this thread. - */ - final @property void priority( int val ) - { - } - - /** - * Tests whether this thread is running. - * - * Returns: - * true if the thread is running, false if not. - */ - override final @property bool isRunning() nothrow @nogc - { - return false; - } - - /** - * Suspends the calling thread for at least the supplied period. This may - * result in multiple OS calls if period is greater than the maximum sleep - * duration supported by the operating system. - * - * Params: - * val = The minimum duration the calling thread should be suspended. - * - * In: - * period must be non-negative. - * - * Example: - * ------------------------------------------------------------------------ - * - * Thread.sleep( dur!("msecs")( 50 ) ); // sleep for 50 milliseconds - * Thread.sleep( dur!("seconds")( 5 ) ); // sleep for 5 seconds - * - * ------------------------------------------------------------------------ - */ - static void sleep( Duration val ) @nogc nothrow @trusted - { - } - - /** - * Forces a context switch to occur away from the calling thread. - */ - static void yield() @nogc nothrow - { - } -} - version (CoreDdoc) {} else class Thread : ThreadBase { @@ -434,7 +136,7 @@ class Thread : ThreadBase version (Posix) { - private shared bool m_isRunning; + package /*FIXME: private*/ shared bool m_isRunning; } version (Darwin) @@ -498,7 +200,7 @@ class Thread : ThreadBase static assert(0, "unsupported OS"); } - private final void run() + package /*FIXME: private*/ final void run() { super.run(); } @@ -1001,6 +703,7 @@ class Thread : ThreadBase } + //FIXME: move to osthread module: unittest { auto thr = Thread.getThis(); @@ -1107,2124 +810,3 @@ class Thread : ThreadBase static assert(0, "unsupported os"); } } - -private Thread toThread(return scope ThreadBase t) @trusted nothrow @nogc pure -{ - return cast(Thread) cast(void*) t; -} - -private extern(D) static void thread_yield() @nogc nothrow -{ - Thread.yield(); -} - -/// -unittest -{ - class DerivedThread : Thread - { - this() - { - super(&run); - } - - private: - void run() - { - // Derived thread running. - } - } - - void threadFunc() - { - // Composed thread running. - } - - // create and start instances of each type - auto derived = new DerivedThread().start(); - auto composed = new Thread(&threadFunc).start(); - new Thread({ - // Codes to run in the newly created thread. - }).start(); -} - -unittest -{ - int x = 0; - - new Thread( - { - x++; - }).start().join(); - assert( x == 1 ); -} - - -unittest -{ - enum MSG = "Test message."; - string caughtMsg; - - try - { - new Thread( - function() - { - throw new Exception( MSG ); - }).start().join(); - assert( false, "Expected rethrown exception." ); - } - catch ( Throwable t ) - { - assert( t.msg == MSG ); - } -} - - -unittest -{ - // use >pageSize to avoid stack overflow (e.g. in an syscall) - auto thr = new Thread(function{}, 4096 + 1).start(); - thr.join(); -} - - -unittest -{ - import core.memory : GC; - - auto t1 = new Thread({ - foreach (_; 0 .. 20) - ThreadBase.getAll; - }).start; - auto t2 = new Thread({ - foreach (_; 0 .. 20) - GC.collect; - }).start; - t1.join(); - t2.join(); -} - -unittest -{ - import core.sync.semaphore; - auto sem = new Semaphore(); - - auto t = new Thread( - { - sem.notify(); - Thread.sleep(100.msecs); - }).start(); - - sem.wait(); // thread cannot be detached while being started - thread_detachInstance(t); - foreach (t2; Thread) - assert(t !is t2); - t.join(); -} - -// https://issues.dlang.org/show_bug.cgi?id=22124 -unittest -{ - Thread thread = new Thread({}); - auto fun(Thread t, int x) - { - t.__ctor({x = 3;}); - return t; - } - static assert(!__traits(compiles, () @nogc => fun(thread, 3) )); -} - -@nogc @safe nothrow -unittest -{ - Thread.sleep(1.msecs); -} - -/////////////////////////////////////////////////////////////////////////////// -// GC Support Routines -/////////////////////////////////////////////////////////////////////////////// - -version (CoreDdoc) -{ - /** - * Instruct the thread module, when initialized, to use a different set of - * signals besides SIGRTMIN and SIGRTMIN + 1 for suspension and resumption of threads. - * This function should be called at most once, prior to thread_init(). - * This function is Posix-only. - */ - extern (C) void thread_setGCSignals(int suspendSignalNo, int resumeSignalNo) nothrow @nogc - { - } - - /** - * Get the GC signals set by the thread module. This function should be called either - * after thread_init() has finished, or after a call thread_setGCSignals(). - * This function is Posix-only. - */ - extern (C) void thread_getGCSignals(out int suspendSignalNo, out int resumeSignalNo) nothrow @nogc - { - } -} -else version (Posix) -{ - extern (C) void thread_setGCSignals(int suspendSignalNo, int resumeSignalNo) nothrow @nogc - in - { - assert(suspendSignalNo != 0); - assert(resumeSignalNo != 0); - } - out - { - assert(suspendSignalNumber != 0); - assert(resumeSignalNumber != 0); - } - do - { - suspendSignalNumber = suspendSignalNo; - resumeSignalNumber = resumeSignalNo; - } - - extern (C) void thread_getGCSignals(out int suspendSignalNo, out int resumeSignalNo) nothrow @nogc - in - { - assert(suspendSignalNumber != 0); - assert(resumeSignalNumber != 0); - } - out - { - assert(suspendSignalNo != 0); - assert(resumeSignalNo != 0); - } - do - { - suspendSignalNo = suspendSignalNumber; - resumeSignalNo = resumeSignalNumber; - } -} - -version (Posix) -{ - private __gshared int suspendSignalNumber; - private __gshared int resumeSignalNumber; -} - -version (CoreDdoc) {} else -private extern (D) ThreadBase attachThread(ThreadBase _thisThread) @nogc nothrow -{ - Thread thisThread = _thisThread.toThread(); - - StackContext* thisContext = &thisThread.m_main; - assert( thisContext == thisThread.m_curr ); - - version (Windows) - { - thisThread.m_addr = GetCurrentThreadId(); - thisThread.m_hndl = GetCurrentThreadHandle(); - thisContext.bstack = getStackBottom(); - thisContext.tstack = thisContext.bstack; - } - else version (Posix) - { - thisThread.m_addr = pthread_self(); - thisContext.bstack = getStackBottom(); - thisContext.tstack = thisContext.bstack; - - atomicStore!(MemoryOrder.raw)(thisThread.toThread.m_isRunning, true); - } - else - static assert(0, "unsupported os"); - thisThread.m_isDaemon = true; - thisThread.tlsRTdataInit(); - Thread.setThis( thisThread ); - - version (Darwin) - { - thisThread.m_tmach = pthread_mach_thread_np( thisThread.m_addr ); - assert( thisThread.m_tmach != thisThread.m_tmach.init ); - } - - Thread.add( thisThread, false ); - Thread.add( thisContext ); - if ( Thread.sm_main !is null ) - multiThreadedFlag = true; - return thisThread; -} - -/** - * Registers the calling thread for use with the D Runtime. If this routine - * is called for a thread which is already registered, no action is performed. - * - * NOTE: This routine does not run thread-local static constructors when called. - * If full functionality as a D thread is desired, the following function - * must be called after thread_attachThis: - * - * extern (C) void rt_moduleTlsCtor(); - * - * See_Also: - * $(REF thread_detachThis, core,thread,threadbase) - */ -extern(C) Thread thread_attachThis() -{ - return thread_attachThis_tpl!Thread(); -} - - -version (Windows) -{ - // NOTE: These calls are not safe on Posix systems that use signals to - // perform garbage collection. The suspendHandler uses getThis() - // to get the thread handle so getThis() must be a simple call. - // Mutexes can't safely be acquired inside signal handlers, and - // even if they could, the mutex needed (Thread.slock) is held by - // thread_suspendAll(). So in short, these routines will remain - // Windows-specific. If they are truly needed elsewhere, the - // suspendHandler will need a way to call a version of getThis() - // that only does the TLS lookup without the fancy fallback stuff. - - /// ditto - extern (C) Thread thread_attachByAddr( ThreadID addr ) - { - return thread_attachByAddrB( addr, getThreadStackBottom( addr ) ); - } - - - /// ditto - extern (C) Thread thread_attachByAddrB( ThreadID addr, void* bstack ) - { - GC.disable(); scope(exit) GC.enable(); - - if (auto t = thread_findByAddr(addr).toThread) - return t; - - Thread thisThread = new Thread(); - StackContext* thisContext = &thisThread.m_main; - assert( thisContext == thisThread.m_curr ); - - thisThread.m_addr = addr; - thisContext.bstack = bstack; - thisContext.tstack = thisContext.bstack; - - thisThread.m_isDaemon = true; - - if ( addr == GetCurrentThreadId() ) - { - thisThread.m_hndl = GetCurrentThreadHandle(); - thisThread.tlsRTdataInit(); - Thread.setThis( thisThread ); - } - else - { - thisThread.m_hndl = OpenThreadHandle( addr ); - impersonate_thread(addr, - { - thisThread.tlsRTdataInit(); - Thread.setThis( thisThread ); - }); - } - - Thread.add( thisThread, false ); - Thread.add( thisContext ); - if ( Thread.sm_main !is null ) - multiThreadedFlag = true; - return thisThread; - } -} - - -// Calls the given delegate, passing the current thread's stack pointer to it. -package extern(D) void callWithStackShell(scope callWithStackShellDg fn) nothrow -in (fn) -{ - // The purpose of the 'shell' is to ensure all the registers get - // put on the stack so they'll be scanned. We only need to push - // the callee-save registers. - void *sp = void; - version (GNU) - { - // The generic solution below using a call to __builtin_unwind_init () - // followed by an assignment to sp has two issues: - // 1) On some archs it stores a huge amount of FP and Vector state which - // is not the subject of the scan - and, indeed might produce false - // hits. - // 2) Even on archs like X86, where there are no callee-saved FPRs/VRs there - // tend to be 'holes' in the frame allocations (to deal with alignment) which - // also will contain random data which could produce false positives. - // This solution stores only the integer callee-saved registers. - version (X86) - { - void*[3] regs = void; - asm pure nothrow @nogc - { - "movl %%ebx, %0" : "=m" (regs[0]); - "movl %%esi, %0" : "=m" (regs[1]); - "movl %%edi, %0" : "=m" (regs[2]); - } - sp = cast(void*)®s[0]; - } - else version (X86_64) - { - void*[5] regs = void; - asm pure nothrow @nogc - { - "movq %%rbx, %0" : "=m" (regs[0]); - "movq %%r12, %0" : "=m" (regs[1]); - "movq %%r13, %0" : "=m" (regs[2]); - "movq %%r14, %0" : "=m" (regs[3]); - "movq %%r15, %0" : "=m" (regs[4]); - } - sp = cast(void*)®s[0]; - } - else version (PPC) - { - void*[19] regs = void; - version (Darwin) - enum regname = "r"; - else - enum regname = ""; - static foreach (i; 0 .. regs.length) - {{ - enum int j = 13 + i; // source register - asm pure nothrow @nogc - { - ("stw "~regname~j.stringof~", %0") : "=m" (regs[i]); - } - }} - sp = cast(void*)®s[0]; - } - else version (PPC64) - { - void*[19] regs = void; - version (Darwin) - enum regname = "r"; - else - enum regname = ""; - static foreach (i; 0 .. regs.length) - {{ - enum int j = 13 + i; // source register - asm pure nothrow @nogc - { - ("std "~regname~j.stringof~", %0") : "=m" (regs[i]); - } - }} - sp = cast(void*)®s[0]; - } - else version (AArch64) - { - // Callee-save registers, x19-x28 according to AAPCS64, section - // 5.1.1. Include x29 fp because it optionally can be a callee - // saved reg - size_t[11] regs = void; - // store the registers in pairs - asm pure nothrow @nogc - { - "stp x19, x20, %0" : "=m" (regs[ 0]), "=m" (regs[1]); - "stp x21, x22, %0" : "=m" (regs[ 2]), "=m" (regs[3]); - "stp x23, x24, %0" : "=m" (regs[ 4]), "=m" (regs[5]); - "stp x25, x26, %0" : "=m" (regs[ 6]), "=m" (regs[7]); - "stp x27, x28, %0" : "=m" (regs[ 8]), "=m" (regs[9]); - "str x29, %0" : "=m" (regs[10]); - "mov %0, sp" : "=r" (sp); - } - } - else version (ARM) - { - // Callee-save registers, according to AAPCS, section 5.1.1. - // arm and thumb2 instructions - size_t[8] regs = void; - asm pure nothrow @nogc - { - "stm %0, {r4-r11}" : : "r" (regs.ptr) : "memory"; - "mov %0, sp" : "=r" (sp); - } - } - else - { - __builtin_unwind_init(); - sp = &sp; - } - } - else version (AsmX86_Posix) - { - size_t[3] regs = void; - asm pure nothrow @nogc - { - mov [regs + 0 * 4], EBX; - mov [regs + 1 * 4], ESI; - mov [regs + 2 * 4], EDI; - - mov sp[EBP], ESP; - } - } - else version (AsmX86_Windows) - { - size_t[3] regs = void; - asm pure nothrow @nogc - { - mov [regs + 0 * 4], EBX; - mov [regs + 1 * 4], ESI; - mov [regs + 2 * 4], EDI; - - mov sp[EBP], ESP; - } - } - else version (AsmX86_64_Posix) - { - size_t[5] regs = void; - asm pure nothrow @nogc - { - mov [regs + 0 * 8], RBX; - mov [regs + 1 * 8], R12; - mov [regs + 2 * 8], R13; - mov [regs + 3 * 8], R14; - mov [regs + 4 * 8], R15; - - mov sp[RBP], RSP; - } - } - else version (AsmX86_64_Windows) - { - size_t[7] regs = void; - asm pure nothrow @nogc - { - mov [regs + 0 * 8], RBX; - mov [regs + 1 * 8], RSI; - mov [regs + 2 * 8], RDI; - mov [regs + 3 * 8], R12; - mov [regs + 4 * 8], R13; - mov [regs + 5 * 8], R14; - mov [regs + 6 * 8], R15; - - mov sp[RBP], RSP; - } - } - else version (AArch64) - { - // Callee-save registers, x19-x28 according to AAPCS64, section - // 5.1.1. Include x29 fp because it optionally can be a callee - // saved reg - size_t[11] regs = void; - // store the registers in pairs - asm pure nothrow @nogc - { - /* - stp x19, x20, regs[0]; - stp x21, x22, regs[2]; - stp x23, x24, regs[4]; - stp x25, x26, regs[6]; - stp x27, x28, regs[8]; - str x29, regs[10]; - mov [sp], sp; - */ - } - assert(0, "implement AArch64 inline assembler for callWithStackShell()"); // TODO AArch64 - } - else - { - static assert(false, "Architecture not supported."); - } - - fn(sp); -} - -/** - * Returns the process ID of the calling process, which is guaranteed to be - * unique on the system. This call is always successful. - * - * Example: - * --- - * writefln("Current process id: %s", getpid()); - * --- - */ -version (Posix) -{ - alias getpid = imported!"core.sys.posix.unistd".getpid; -} -else version (Windows) -{ - alias getpid = imported!"core.sys.windows.winbase".GetCurrentProcessId; -} -else - static assert(0, "unsupported os"); - -extern (C) @nogc nothrow -{ - version (CRuntime_Glibc) version = PThread_Getattr_NP; - version (CRuntime_Bionic) version = PThread_Getattr_NP; - version (CRuntime_Musl) version = PThread_Getattr_NP; - version (CRuntime_UClibc) version = PThread_Getattr_NP; - - version (FreeBSD) version = PThread_Attr_Get_NP; - version (NetBSD) version = PThread_Attr_Get_NP; - version (DragonFlyBSD) version = PThread_Attr_Get_NP; - - version (PThread_Getattr_NP) int pthread_getattr_np(pthread_t thread, pthread_attr_t* attr); - version (PThread_Attr_Get_NP) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr); - version (OpenBSD) int pthread_stackseg_np(pthread_t thread, stack_t* sinfo); -} - - -private extern(D) void* getStackTop() nothrow @nogc -{ - version (D_InlineAsm_X86) - asm pure nothrow @nogc { naked; mov EAX, ESP; ret; } - else version (D_InlineAsm_X86_64) - asm pure nothrow @nogc { naked; mov RAX, RSP; ret; } - else version (AArch64) - //asm pure nothrow @nogc { naked; mov x0, SP; ret; } // TODO AArch64 - { - return null; - } - else version (GNU) - return __builtin_frame_address(0); - else - static assert(false, "Architecture not supported."); -} - - -private extern(D) void* getStackBottom() nothrow @nogc -{ - version (Windows) - { - version (D_InlineAsm_X86) - asm pure nothrow @nogc { naked; mov EAX, FS:4; ret; } - else version (D_InlineAsm_X86_64) - asm pure nothrow @nogc - { naked; - mov RAX, 8; - mov RAX, GS:[RAX]; - ret; - } - else version (GNU_InlineAsm) - { - void *bottom; - - version (X86) - asm pure nothrow @nogc { "movl %%fs:4, %0;" : "=r" (bottom); } - else version (X86_64) - asm pure nothrow @nogc { "movq %%gs:8, %0;" : "=r" (bottom); } - else - static assert(false, "Architecture not supported."); - - return bottom; - } - else - static assert(false, "Architecture not supported."); - } - else version (Darwin) - { - import core.sys.darwin.pthread : pthread_get_stackaddr_np; - return pthread_get_stackaddr_np(pthread_self()); - } - else version (PThread_Getattr_NP) - { - pthread_attr_t attr; - void* addr; size_t size; - - pthread_attr_init(&attr); - pthread_getattr_np(pthread_self(), &attr); - pthread_attr_getstack(&attr, &addr, &size); - pthread_attr_destroy(&attr); - static if (isStackGrowingDown) - addr += size; - return addr; - } - else version (PThread_Attr_Get_NP) - { - pthread_attr_t attr; - void* addr; size_t size; - - pthread_attr_init(&attr); - pthread_attr_get_np(pthread_self(), &attr); - pthread_attr_getstack(&attr, &addr, &size); - pthread_attr_destroy(&attr); - static if (isStackGrowingDown) - addr += size; - return addr; - } - else version (OpenBSD) - { - stack_t stk; - - pthread_stackseg_np(pthread_self(), &stk); - return stk.ss_sp; - } - else version (Solaris) - { - stack_t stk; - - thr_stksegment(&stk); - return stk.ss_sp; - } - else - static assert(false, "Platform not supported."); -} - -/** - * Suspend the specified thread and load stack and register information for - * use by thread_scanAll. If the supplied thread is the calling thread, - * stack and register information will be loaded but the thread will not - * be suspended. If the suspend operation fails and the thread is not - * running then it will be removed from the global thread list, otherwise - * an exception will be thrown. - * - * Params: - * t = The thread to suspend. - * - * Throws: - * ThreadError if the suspend operation fails for a running thread. - * Returns: - * Whether the thread is now suspended (true) or terminated (false). - */ -private extern (D) bool suspend( Thread t ) nothrow @nogc -{ - if (!t.isRunning) - { - Thread.remove(t); - return false; - } - - version (Windows) - { - if ( t.m_addr != GetCurrentThreadId() && SuspendThread( t.m_hndl ) == 0xFFFFFFFF ) - { - if ( !t.isRunning ) - { - Thread.remove( t ); - return false; - } - onThreadError( "Unable to suspend thread" ); - } - - CONTEXT context = void; - context.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL; - - if ( !GetThreadContext( t.m_hndl, &context ) ) - onThreadError( "Unable to load thread context" ); - version (X86) - { - if ( !t.m_lock ) - t.m_curr.tstack = cast(void*) context.Esp; - // eax,ebx,ecx,edx,edi,esi,ebp,esp - t.m_reg[0] = context.Eax; - t.m_reg[1] = context.Ebx; - t.m_reg[2] = context.Ecx; - t.m_reg[3] = context.Edx; - t.m_reg[4] = context.Edi; - t.m_reg[5] = context.Esi; - t.m_reg[6] = context.Ebp; - t.m_reg[7] = context.Esp; - } - else version (X86_64) - { - if ( !t.m_lock ) - t.m_curr.tstack = cast(void*) context.Rsp; - // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp - t.m_reg[0] = context.Rax; - t.m_reg[1] = context.Rbx; - t.m_reg[2] = context.Rcx; - t.m_reg[3] = context.Rdx; - t.m_reg[4] = context.Rdi; - t.m_reg[5] = context.Rsi; - t.m_reg[6] = context.Rbp; - t.m_reg[7] = context.Rsp; - // r8,r9,r10,r11,r12,r13,r14,r15 - t.m_reg[8] = context.R8; - t.m_reg[9] = context.R9; - t.m_reg[10] = context.R10; - t.m_reg[11] = context.R11; - t.m_reg[12] = context.R12; - t.m_reg[13] = context.R13; - t.m_reg[14] = context.R14; - t.m_reg[15] = context.R15; - } - else - { - static assert(false, "Architecture not supported." ); - } - } - else version (Darwin) - { - if ( t.m_addr != pthread_self() && thread_suspend( t.m_tmach ) != KERN_SUCCESS ) - { - if ( !t.isRunning ) - { - Thread.remove( t ); - return false; - } - onThreadError( "Unable to suspend thread" ); - } - - version (X86) - { - x86_thread_state32_t state = void; - mach_msg_type_number_t count = x86_THREAD_STATE32_COUNT; - - if ( thread_get_state( t.m_tmach, x86_THREAD_STATE32, &state, &count ) != KERN_SUCCESS ) - onThreadError( "Unable to load thread state" ); - if ( !t.m_lock ) - t.m_curr.tstack = cast(void*) state.esp; - // eax,ebx,ecx,edx,edi,esi,ebp,esp - t.m_reg[0] = state.eax; - t.m_reg[1] = state.ebx; - t.m_reg[2] = state.ecx; - t.m_reg[3] = state.edx; - t.m_reg[4] = state.edi; - t.m_reg[5] = state.esi; - t.m_reg[6] = state.ebp; - t.m_reg[7] = state.esp; - } - else version (X86_64) - { - x86_thread_state64_t state = void; - mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; - - if ( thread_get_state( t.m_tmach, x86_THREAD_STATE64, &state, &count ) != KERN_SUCCESS ) - onThreadError( "Unable to load thread state" ); - if ( !t.m_lock ) - t.m_curr.tstack = cast(void*) state.rsp; - // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp - t.m_reg[0] = state.rax; - t.m_reg[1] = state.rbx; - t.m_reg[2] = state.rcx; - t.m_reg[3] = state.rdx; - t.m_reg[4] = state.rdi; - t.m_reg[5] = state.rsi; - t.m_reg[6] = state.rbp; - t.m_reg[7] = state.rsp; - // r8,r9,r10,r11,r12,r13,r14,r15 - t.m_reg[8] = state.r8; - t.m_reg[9] = state.r9; - t.m_reg[10] = state.r10; - t.m_reg[11] = state.r11; - t.m_reg[12] = state.r12; - t.m_reg[13] = state.r13; - t.m_reg[14] = state.r14; - t.m_reg[15] = state.r15; - } - else version (AArch64) - { - arm_thread_state64_t state = void; - mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT; - - if (thread_get_state(t.m_tmach, ARM_THREAD_STATE64, &state, &count) != KERN_SUCCESS) - onThreadError("Unable to load thread state"); - // TODO: ThreadException here recurses forever! Does it - //still using onThreadError? - //printf("state count %d (expect %d)\n", count ,ARM_THREAD_STATE64_COUNT); - if (!t.m_lock) - t.m_curr.tstack = cast(void*) state.sp; - - t.m_reg[0..29] = state.x; // x0-x28 - t.m_reg[29] = state.fp; // x29 - t.m_reg[30] = state.lr; // x30 - t.m_reg[31] = state.sp; // x31 - t.m_reg[32] = state.pc; - } - else version (ARM) - { - arm_thread_state32_t state = void; - mach_msg_type_number_t count = ARM_THREAD_STATE32_COUNT; - - // Thought this would be ARM_THREAD_STATE32, but that fails. - // Mystery - if (thread_get_state(t.m_tmach, ARM_THREAD_STATE, &state, &count) != KERN_SUCCESS) - onThreadError("Unable to load thread state"); - // TODO: in past, ThreadException here recurses forever! Does it - //still using onThreadError? - //printf("state count %d (expect %d)\n", count ,ARM_THREAD_STATE32_COUNT); - if (!t.m_lock) - t.m_curr.tstack = cast(void*) state.sp; - - t.m_reg[0..13] = state.r; // r0 - r13 - t.m_reg[13] = state.sp; - t.m_reg[14] = state.lr; - t.m_reg[15] = state.pc; - } - else version (PPC) - { - ppc_thread_state_t state = void; - mach_msg_type_number_t count = PPC_THREAD_STATE_COUNT; - - if (thread_get_state(t.m_tmach, PPC_THREAD_STATE, &state, &count) != KERN_SUCCESS) - onThreadError("Unable to load thread state"); - if (!t.m_lock) - t.m_curr.tstack = cast(void*) state.r[1]; - t.m_reg[] = state.r[]; - } - else version (PPC64) - { - ppc_thread_state64_t state = void; - mach_msg_type_number_t count = PPC_THREAD_STATE64_COUNT; - - if (thread_get_state(t.m_tmach, PPC_THREAD_STATE64, &state, &count) != KERN_SUCCESS) - onThreadError("Unable to load thread state"); - if (!t.m_lock) - t.m_curr.tstack = cast(void*) state.r[1]; - t.m_reg[] = state.r[]; - } - else - { - static assert(false, "Architecture not supported." ); - } - } - else version (Solaris) - { - if (t.m_addr != pthread_self()) - { - if (thr_suspend(t.m_addr) != 0) - { - if (!t.isRunning) - { - Thread.remove(t); - return false; - } - onThreadError("Unable to suspend thread"); - } - - static int getLwpStatus(ulong lwpid, out lwpstatus_t status) - { - import core.sys.posix.fcntl : open, O_RDONLY; - import core.sys.posix.unistd : pread, close; - import core.internal.string : unsignedToTempString; - - char[100] path = void; - auto pslice = path[0 .. $]; - immutable n = unsignedToTempString(lwpid); - immutable ndigits = n.length; - - // Construct path "/proc/self/lwp/%u/lwpstatus" - pslice[0 .. 15] = "/proc/self/lwp/"; - pslice = pslice[15 .. $]; - pslice[0 .. ndigits] = n[]; - pslice = pslice[ndigits .. $]; - pslice[0 .. 10] = "/lwpstatus"; - pslice[10] = '\0'; - - // Read in lwpstatus data - int fd = open(path.ptr, O_RDONLY, 0); - if (fd >= 0) - { - while (pread(fd, &status, status.sizeof, 0) == status.sizeof) - { - // Should only attempt to read the thread state once it - // has been stopped by thr_suspend - if (status.pr_flags & PR_STOPPED) - { - close(fd); - return 0; - } - // Give it a chance to stop - thread_yield(); - } - close(fd); - } - return -1; - } - - lwpstatus_t status = void; - if (getLwpStatus(t.m_addr, status) != 0) - onThreadError("Unable to load thread state"); - - version (X86) - { - import core.sys.solaris.sys.regset; // REG_xxx - - if (!t.m_lock) - t.m_curr.tstack = cast(void*) status.pr_reg[REG_ESP]; - // eax,ebx,ecx,edx,edi,esi,ebp,esp - t.m_reg[0] = status.pr_reg[REG_EAX]; - t.m_reg[1] = status.pr_reg[REG_EBX]; - t.m_reg[2] = status.pr_reg[REG_ECX]; - t.m_reg[3] = status.pr_reg[REG_EDX]; - t.m_reg[4] = status.pr_reg[REG_EDI]; - t.m_reg[5] = status.pr_reg[REG_ESI]; - t.m_reg[6] = status.pr_reg[REG_EBP]; - t.m_reg[7] = status.pr_reg[REG_ESP]; - } - else version (X86_64) - { - import core.sys.solaris.sys.regset; // REG_xxx - - if (!t.m_lock) - t.m_curr.tstack = cast(void*) status.pr_reg[REG_RSP]; - // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp - t.m_reg[0] = status.pr_reg[REG_RAX]; - t.m_reg[1] = status.pr_reg[REG_RBX]; - t.m_reg[2] = status.pr_reg[REG_RCX]; - t.m_reg[3] = status.pr_reg[REG_RDX]; - t.m_reg[4] = status.pr_reg[REG_RDI]; - t.m_reg[5] = status.pr_reg[REG_RSI]; - t.m_reg[6] = status.pr_reg[REG_RBP]; - t.m_reg[7] = status.pr_reg[REG_RSP]; - // r8,r9,r10,r11,r12,r13,r14,r15 - t.m_reg[8] = status.pr_reg[REG_R8]; - t.m_reg[9] = status.pr_reg[REG_R9]; - t.m_reg[10] = status.pr_reg[REG_R10]; - t.m_reg[11] = status.pr_reg[REG_R11]; - t.m_reg[12] = status.pr_reg[REG_R12]; - t.m_reg[13] = status.pr_reg[REG_R13]; - t.m_reg[14] = status.pr_reg[REG_R14]; - t.m_reg[15] = status.pr_reg[REG_R15]; - } - else version (SPARC) - { - import core.sys.solaris.sys.procfs : R_SP, R_PC; - - if (!t.m_lock) - t.m_curr.tstack = cast(void*) status.pr_reg[R_SP]; - // g0..g7, o0..o7, l0..l7, i0..i7 - t.m_reg[0 .. 32] = status.pr_reg[0 .. 32]; - // pc - t.m_reg[32] = status.pr_reg[R_PC]; - } - else version (SPARC64) - { - import core.sys.solaris.sys.procfs : R_SP, R_PC; - - if (!t.m_lock) - { - // SPARC V9 has a stack bias of 2047 bytes which must be added to get - // the actual data of the stack frame. - auto tstack = status.pr_reg[R_SP] + 2047; - assert(tstack % 16 == 0); - t.m_curr.tstack = cast(void*) tstack; - } - // g0..g7, o0..o7, l0..l7, i0..i7 - t.m_reg[0 .. 32] = status.pr_reg[0 .. 32]; - // pc - t.m_reg[32] = status.pr_reg[R_PC]; - } - else - { - static assert(false, "Architecture not supported."); - } - } - else if (!t.m_lock) - { - t.m_curr.tstack = getStackTop(); - } - } - else version (Posix) - { - if ( t.m_addr != pthread_self() ) - { - if ( pthread_kill( t.m_addr, suspendSignalNumber ) != 0 ) - { - if ( !t.isRunning ) - { - Thread.remove( t ); - return false; - } - onThreadError( "Unable to suspend thread" ); - } - } - else if ( !t.m_lock ) - { - t.m_curr.tstack = getStackTop(); - } - } - else - static assert(0, "unsupported os"); - return true; -} - -/** - * Runs the necessary operations required before stopping the world. - */ -extern (C) void thread_preStopTheWorld() nothrow { - Thread.slock.lock_nothrow(); -} - -/** - * Suspend all threads but the calling thread for "stop the world" garbage - * collection runs. This function may be called multiple times, and must - * be followed by a matching number of calls to thread_resumeAll before - * processing is resumed. - * - * Throws: - * ThreadError if the suspend operation fails for a running thread. - */ -extern (C) void thread_suspendAll() nothrow -{ - // NOTE: We've got an odd chicken & egg problem here, because while the GC - // is required to call thread_init before calling any other thread - // routines, thread_init may allocate memory which could in turn - // trigger a collection. Thus, thread_suspendAll, thread_scanAll, - // and thread_resumeAll must be callable before thread_init - // completes, with the assumption that no other GC memory has yet - // been allocated by the system, and thus there is no risk of losing - // data if the global thread list is empty. The check of - // Thread.sm_tbeg below is done to ensure thread_init has completed, - // and therefore that calling Thread.getThis will not result in an - // error. For the short time when Thread.sm_tbeg is null, there is - // no reason not to simply call the multithreaded code below, with - // the expectation that the foreach loop will never be entered. - if ( !multiThreadedFlag && Thread.sm_tbeg ) - { - if ( ++suspendDepth == 1 ) - suspend( Thread.getThis() ); - - return; - } - - thread_preStopTheWorld(); - { - if ( ++suspendDepth > 1 ) - return; - - size_t cnt; - bool suspendedSelf; - Thread t = ThreadBase.sm_tbeg.toThread; - while (t) - { - auto tn = t.next.toThread; - if (suspend(t)) - { - if (t is ThreadBase.getThis()) - suspendedSelf = true; - ++cnt; - } - t = tn; - } - - version (Darwin) - {} - else version (Solaris) - {} - else version (Posix) - { - // Subtract own thread if we called suspend() on ourselves. - // For example, suspendedSelf would be false if the current - // thread ran thread_detachThis(). - assert(cnt >= 1); - if (suspendedSelf) - --cnt; - // wait for semaphore notifications - for (; cnt; --cnt) - { - while (sem_wait(&suspendCount) != 0) - { - if (errno != EINTR) - onThreadError("Unable to wait for semaphore"); - errno = 0; - } - } - } - else version (Windows) - { - } - else - static assert(0, "unsupported os"); - } -} - -/** - * Resume the specified thread and unload stack and register information. - * If the supplied thread is the calling thread, stack and register - * information will be unloaded but the thread will not be resumed. If - * the resume operation fails and the thread is not running then it will - * be removed from the global thread list, otherwise an exception will be - * thrown. - * - * Params: - * t = The thread to resume. - * - * Throws: - * ThreadError if the resume fails for a running thread. - */ -private extern (D) void resume(ThreadBase _t) nothrow @nogc -{ - Thread t = _t.toThread; - - version (Windows) - { - if ( t.m_addr != GetCurrentThreadId() && ResumeThread( t.m_hndl ) == 0xFFFFFFFF ) - { - if ( !t.isRunning ) - { - Thread.remove( t ); - return; - } - onThreadError( "Unable to resume thread" ); - } - - if ( !t.m_lock ) - t.m_curr.tstack = t.m_curr.bstack; - t.m_reg[0 .. $] = 0; - } - else version (Darwin) - { - if ( t.m_addr != pthread_self() && thread_resume( t.m_tmach ) != KERN_SUCCESS ) - { - if ( !t.isRunning ) - { - Thread.remove( t ); - return; - } - onThreadError( "Unable to resume thread" ); - } - - if ( !t.m_lock ) - t.m_curr.tstack = t.m_curr.bstack; - t.m_reg[0 .. $] = 0; - } - else version (Solaris) - { - if (t.m_addr != pthread_self() && thr_continue(t.m_addr) != 0) - { - if (!t.isRunning) - { - Thread.remove(t); - return; - } - onThreadError("Unable to resume thread"); - } - - if (!t.m_lock) - t.m_curr.tstack = t.m_curr.bstack; - t.m_reg[0 .. $] = 0; - } - else version (Posix) - { - if ( t.m_addr != pthread_self() ) - { - if ( pthread_kill( t.m_addr, resumeSignalNumber ) != 0 ) - { - if ( !t.isRunning ) - { - Thread.remove( t ); - return; - } - onThreadError( "Unable to resume thread" ); - } - } - else if ( !t.m_lock ) - { - t.m_curr.tstack = t.m_curr.bstack; - } - } - else - static assert(false, "Platform not supported."); -} - - -/** - * Initializes the thread module. This function must be called by the - * garbage collector on startup and before any other thread routines - * are called. - */ -version (CoreDdoc) - extern (C) void thread_init() @nogc nothrow {} -else -extern (C) void thread_init() @nogc nothrow -{ - // NOTE: If thread_init itself performs any allocations then the thread - // routines reserved for garbage collector use may be called while - // thread_init is being processed. However, since no memory should - // exist to be scanned at this point, it is sufficient for these - // functions to detect the condition and return immediately. - - initLowlevelThreads(); - Thread.initLocks(); - - version (Windows) - { - } - else version (Darwin) - { - // thread id different in forked child process - static extern(C) void initChildAfterFork() - { - auto thisThread = Thread.getThis(); - if (!thisThread) - { - // It is possible that runtime was not properly initialized in the current process or thread - - // it may happen after `fork` call when using a dynamically loaded shared library written in D from a multithreaded non-D program. - // In such case getThis will return null. - return; - } - thisThread.m_addr = pthread_self(); - assert( thisThread.m_addr != thisThread.m_addr.init ); - thisThread.m_tmach = pthread_mach_thread_np( thisThread.m_addr ); - assert( thisThread.m_tmach != thisThread.m_tmach.init ); - } - pthread_atfork(null, null, &initChildAfterFork); - } - else version (Solaris) - { - } - else version (Posix) - { - version (OpenBSD) - { - // OpenBSD does not support SIGRTMIN or SIGRTMAX - // Use SIGUSR1 for SIGRTMIN, SIGUSR2 for SIGRTMIN + 1 - // And use 32 for SIGRTMAX (32 is the max signal number on OpenBSD) - enum SIGRTMIN = SIGUSR1; - enum SIGRTMAX = 32; - } - else - { - import core.sys.posix.signal : SIGRTMAX, SIGRTMIN; - } - - if ( suspendSignalNumber == 0 ) - { - suspendSignalNumber = SIGRTMIN; - } - - if ( resumeSignalNumber == 0 ) - { - resumeSignalNumber = SIGRTMIN + 1; - assert(resumeSignalNumber <= SIGRTMAX); - } - int status; - sigaction_t suspend = void; - sigaction_t resume = void; - - // This is a quick way to zero-initialize the structs without using - // memset or creating a link dependency on their static initializer. - (cast(byte*) &suspend)[0 .. sigaction_t.sizeof] = 0; - (cast(byte*) &resume)[0 .. sigaction_t.sizeof] = 0; - - // NOTE: SA_RESTART indicates that system calls should restart if they - // are interrupted by a signal, but this is not available on all - // Posix systems, even those that support multithreading. - static if (__traits(compiles, core.sys.posix.signal.SA_RESTART)) - { - import core.sys.posix.signal : SA_RESTART; - - suspend.sa_flags = SA_RESTART; - } - - suspend.sa_handler = &thread_suspendHandler; - // NOTE: We want to ignore all signals while in this handler, so fill - // sa_mask to indicate this. - status = sigfillset( &suspend.sa_mask ); - assert( status == 0 ); - - // NOTE: Since resumeSignalNumber should only be issued for threads within the - // suspend handler, we don't want this signal to trigger a - // restart. - resume.sa_flags = 0; - resume.sa_handler = &thread_resumeHandler; - // NOTE: We want to ignore all signals while in this handler, so fill - // sa_mask to indicate this. - status = sigfillset( &resume.sa_mask ); - assert( status == 0 ); - - status = sigaction( suspendSignalNumber, &suspend, null ); - assert( status == 0 ); - - status = sigaction( resumeSignalNumber, &resume, null ); - assert( status == 0 ); - - status = sem_init( &suspendCount, 0, 0 ); - assert( status == 0 ); - } - else - static assert(0, "unsupported os"); - _mainThreadStore[] = cast(void[]) __traits(initSymbol, Thread)[]; - Thread.sm_main = attachThread((cast(Thread)_mainThreadStore.ptr).__ctor()); -} - -private alias MainThreadStore = void[__traits(classInstanceSize, Thread)]; -package __gshared align(__traits(classInstanceAlignment, Thread)) MainThreadStore _mainThreadStore; - -/** - * Terminates the thread module. No other thread routine may be called - * afterwards. - */ -extern (C) void thread_term() @nogc nothrow -{ - thread_term_tpl!(Thread)(_mainThreadStore); -} - - -/////////////////////////////////////////////////////////////////////////////// -// Thread Entry Point and Signal Handlers -/////////////////////////////////////////////////////////////////////////////// - - -version (Windows) -{ - private - { - // - // Entry point for Windows threads - // - extern (Windows) uint thread_entryPoint( void* arg ) nothrow - { - Thread obj = cast(Thread) arg; - assert( obj ); - - obj.initDataStorage(); - - Thread.registerThis(obj); - - scope (exit) - { - // allow the GC to clean up any resources it allocated for this thread. - import core.internal.gc.proxy : gc_getProxy; - gc_getProxy().cleanupThread(obj); - - Thread.remove(obj); - obj.destroyDataStorage(); - } - Thread.add(&obj.m_main); - - // NOTE: No GC allocations may occur until the stack pointers have - // been set and Thread.getThis returns a valid reference to - // this thread object (this latter condition is not strictly - // necessary on Windows but it should be followed for the - // sake of consistency). - - // TODO: Consider putting an auto exception object here (using - // alloca) forOutOfMemoryError plus something to track - // whether an exception is in-flight? - - void append( Throwable t ) - { - obj.filterCaughtThrowable(t); - if (t !is null) - obj.m_unhandled = Throwable.chainTogether(obj.m_unhandled, t); - } - - version (D_InlineAsm_X86) - { - asm nothrow @nogc { fninit; } - } - - try - { - rt_moduleTlsCtor(); - try - { - obj.run(); - } - catch ( Throwable t ) - { - append( t ); - } - rt_moduleTlsDtor(); - } - catch ( Throwable t ) - { - append( t ); - } - return 0; - } - - - HANDLE GetCurrentThreadHandle() nothrow @nogc - { - const uint DUPLICATE_SAME_ACCESS = 0x00000002; - - HANDLE curr = GetCurrentThread(), - proc = GetCurrentProcess(), - hndl; - - DuplicateHandle( proc, curr, proc, &hndl, 0, TRUE, DUPLICATE_SAME_ACCESS ); - return hndl; - } - } -} -else version (Posix) -{ - // NOTE: A thread's cancelability state, determined by pthread_setcancelstate, - // can be enabled (the default for new threads) or disabled. - // If a thread has disabled cancelation, then a cancelation request remains - // queued until the thread enables cancelation. If a thread has enabled - // cancelation, then its cancelability type determines when cancelation occurs. - // - // Call these routines when entering/leaving critical sections of the code that - // are not cancellation points. - - extern (C) int thread_cancelDisable() nothrow - { - static if (__traits(compiles, core.sys.posix.pthread.PTHREAD_CANCEL_DISABLE)) - { - import core.sys.posix.pthread : pthread_setcancelstate, PTHREAD_CANCEL_DISABLE; - int oldstate; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); - return oldstate; - } - else - { - return 0; // No thread cancellation on platform - } - } - - extern (C) void thread_cancelRestore(int oldstate) nothrow - { - static if (__traits(compiles, core.sys.posix.pthread.PTHREAD_CANCEL_DISABLE)) - { - import core.sys.posix.pthread : pthread_setcancelstate; - pthread_setcancelstate(oldstate, null); - } - } - - private - { - // - // Entry point for POSIX threads - // - version (CoreDdoc) {} else - extern (C) void* thread_entryPoint( void* arg ) nothrow - { - version (Shared) - { - Thread obj = cast(Thread)(cast(void**)arg)[0]; - auto loadedLibraries = (cast(void**)arg)[1]; - .free(arg); - } - else - { - Thread obj = cast(Thread)arg; - } - assert( obj ); - - // loadedLibraries need to be inherited from parent thread - // before initilizing GC for TLS (rt_tlsgc_init) - version (Shared) - { - externDFunc!("rt.sections_elf_shared.inheritLoadedLibraries", - void function(void*) @nogc nothrow)(loadedLibraries); - } - - obj.initDataStorage(); - - atomicStore!(MemoryOrder.raw)(obj.m_isRunning, true); - - Thread.registerThis(obj); // can only receive signals from here on - - scope (exit) - { - // allow the GC to clean up any resources it allocated for this thread. - import core.internal.gc.proxy : gc_getProxy; - gc_getProxy().cleanupThread(obj); - - Thread.remove(obj); - atomicStore!(MemoryOrder.raw)(obj.m_isRunning, false); - obj.destroyDataStorage(); - } - Thread.add(&obj.m_main); - - static extern (C) void thread_cleanupHandler( void* arg ) nothrow @nogc - { - Thread obj = cast(Thread) arg; - assert( obj ); - - // NOTE: If the thread terminated abnormally, just set it as - // not running and let thread_suspendAll remove it from - // the thread list. This is safer and is consistent - // with the Windows thread code. - atomicStore!(MemoryOrder.raw)(obj.m_isRunning,false); - } - - // NOTE: Using void to skip the initialization here relies on - // knowledge of how pthread_cleanup is implemented. It may - // not be appropriate for all platforms. However, it does - // avoid the need to link the pthread module. If any - // implementation actually requires default initialization - // then pthread_cleanup should be restructured to maintain - // the current lack of a link dependency. - static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup)) - { - import core.sys.posix.pthread : pthread_cleanup; - - pthread_cleanup cleanup = void; - cleanup.push( &thread_cleanupHandler, cast(void*) obj ); - } - else static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup_push)) - { - import core.sys.posix.pthread : pthread_cleanup_push; - - pthread_cleanup_push(&thread_cleanupHandler, cast(void*) obj); - } - else - { - static assert( false, "Platform not supported." ); - } - - // NOTE: No GC allocations may occur until the stack pointers have - // been set and Thread.getThis returns a valid reference to - // this thread object (this latter condition is not strictly - // necessary on Windows but it should be followed for the - // sake of consistency). - - // TODO: Consider putting an auto exception object here (using - // alloca) forOutOfMemoryError plus something to track - // whether an exception is in-flight? - - void append( Throwable t ) - { - obj.filterCaughtThrowable(t); - if (t !is null) - obj.m_unhandled = Throwable.chainTogether(obj.m_unhandled, t); - } - try - { - rt_moduleTlsCtor(); - try - { - obj.run(); - } - catch ( Throwable t ) - { - append( t ); - } - rt_moduleTlsDtor(); - version (Shared) - { - externDFunc!("rt.sections_elf_shared.cleanupLoadedLibraries", - void function() @nogc nothrow)(); - } - } - catch ( Throwable t ) - { - append( t ); - } - - // NOTE: Normal cleanup is handled by scope(exit). - - static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup)) - { - cleanup.pop( 0 ); - } - else static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup_push)) - { - import core.sys.posix.pthread : pthread_cleanup_pop; - - pthread_cleanup_pop( 0 ); - } - - return null; - } - - - // - // Used to track the number of suspended threads - // - __gshared sem_t suspendCount; - - - extern (C) bool thread_preSuspend( void* sp ) nothrow { - // NOTE: Since registers are being pushed and popped from the - // stack, any other stack data used by this function should - // be gone before the stack cleanup code is called below. - Thread obj = Thread.getThis(); - if (obj is null) - { - return false; - } - - if ( !obj.m_lock ) - { - obj.m_curr.tstack = sp; - } - - return true; - } - - extern (C) bool thread_postSuspend() nothrow { - Thread obj = Thread.getThis(); - if (obj is null) - { - return false; - } - - if ( !obj.m_lock ) - { - obj.m_curr.tstack = obj.m_curr.bstack; - } - - return true; - } - - extern (C) void thread_suspendHandler( int sig ) nothrow - in - { - assert( sig == suspendSignalNumber ); - } - do - { - void op(void* sp) nothrow - { - int cancel_state = thread_cancelDisable(); - scope(exit) thread_cancelRestore(cancel_state); - - bool supported = thread_preSuspend(getStackTop()); - assert(supported, "Tried to suspend a detached thread!"); - - scope(exit) - { - supported = thread_postSuspend(); - assert(supported, "Tried to suspend a detached thread!"); - } - - sigset_t sigres = void; - int status; - - status = sigfillset( &sigres ); - assert( status == 0 ); - - status = sigdelset( &sigres, resumeSignalNumber ); - assert( status == 0 ); - - status = sem_post( &suspendCount ); - assert( status == 0 ); - - sigsuspend( &sigres ); - } - callWithStackShell(&op); - } - - - extern (C) void thread_resumeHandler( int sig ) nothrow - in - { - assert( sig == resumeSignalNumber ); - } - do - { - - } - } -} -else -{ - // NOTE: This is the only place threading versions are checked. If a new - // version is added, the module code will need to be searched for - // places where version-specific code may be required. This can be - // easily accomlished by searching for 'Windows' or 'Posix'. - static assert( false, "Unknown threading implementation." ); -} - -// -// exposed by compiler runtime -// -extern (C) void rt_moduleTlsCtor(); -extern (C) void rt_moduleTlsDtor(); - - -// regression test for Issue 13416 -version (FreeBSD) unittest -{ - static void loop() - { - pthread_attr_t attr; - pthread_attr_init(&attr); - auto thr = pthread_self(); - foreach (i; 0 .. 50) - pthread_attr_get_np(thr, &attr); - pthread_attr_destroy(&attr); - } - - auto thr = new Thread(&loop).start(); - foreach (i; 0 .. 50) - { - thread_suspendAll(); - thread_resumeAll(); - } - thr.join(); -} - -version (DragonFlyBSD) unittest -{ - static void loop() - { - pthread_attr_t attr; - pthread_attr_init(&attr); - auto thr = pthread_self(); - foreach (i; 0 .. 50) - pthread_attr_get_np(thr, &attr); - pthread_attr_destroy(&attr); - } - - auto thr = new Thread(&loop).start(); - foreach (i; 0 .. 50) - { - thread_suspendAll(); - thread_resumeAll(); - } - thr.join(); -} - - -/////////////////////////////////////////////////////////////////////////////// -// lowlovel threading support -/////////////////////////////////////////////////////////////////////////////// - -private -{ - version (Windows): - // If the runtime is dynamically loaded as a DLL, there is a problem with - // threads still running when the DLL is supposed to be unloaded: - // - // - with the VC runtime starting with VS2015 (i.e. using the Universal CRT) - // a thread created with _beginthreadex increments the DLL reference count - // and decrements it when done, so that the DLL is no longer unloaded unless - // all the threads have terminated. With the DLL reference count held up - // by a thread that is only stopped by a signal from a static destructor or - // the termination of the runtime will cause the DLL to never be unloaded. - // - // - with the DigitalMars runtime and VC runtime up to VS2013, the thread - // continues to run, but crashes once the DLL is unloaded from memory as - // the code memory is no longer accessible. Stopping the threads is not possible - // from within the runtime termination as it is invoked from - // DllMain(DLL_PROCESS_DETACH) holding a lock that prevents threads from - // terminating. - // - // Solution: start a watchdog thread that keeps the DLL reference count above 0 and - // checks it periodically. If it is equal to 1 (plus the number of started threads), no - // external references to the DLL exist anymore, threads can be stopped - // and runtime termination and DLL unload can be invoked via FreeLibraryAndExitThread. - // Note: runtime termination is then performed by a different thread than at startup. - // - // Note: if the DLL is never unloaded, process termination kills all threads - // and signals their handles before unconditionally calling DllMain(DLL_PROCESS_DETACH). - - import core.sys.windows.dll : dll_getRefCount; - import core.sys.windows.winbase : FreeLibraryAndExitThread, GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, GetModuleHandleExW; - import core.sys.windows.windef : HMODULE; - - version (CRuntime_Microsoft) - extern(C) extern __gshared ubyte msvcUsesUCRT; // from rt/msvc.d - extern(C) extern __gshared void* __ImageBase; // symbol at the beginning of module, added by linker - enum HMODULE runtimeModule = &__ImageBase; - - /// set during termination of a DLL on Windows, i.e. while executing DllMain(DLL_PROCESS_DETACH) - public __gshared bool thread_DLLProcessDetaching; - - __gshared ThreadID ll_dllMonitorThread; - - int ll_countLowLevelThreadsWithDLLUnloadCallback(HMODULE hMod) nothrow - { - lowlevelLock.lock_nothrow(); - scope(exit) lowlevelLock.unlock_nothrow(); - - int cnt = 0; - foreach (i; 0 .. ll_nThreads) - if (ll_pThreads[i].cbDllUnload && ll_pThreads[i].hMod == hMod) - cnt++; - return cnt; - } - - bool ll_dllHasExternalReferences(HMODULE hMod) nothrow - { - int unloadCallbacks = ll_countLowLevelThreadsWithDLLUnloadCallback(hMod); - int internalReferences = hMod != runtimeModule ? unloadCallbacks - : (ll_dllMonitorThread ? 1 : 0) + (msvcUsesUCRT ? unloadCallbacks : 0); - int refcnt = dll_getRefCount(hMod); - return refcnt > internalReferences; - } - - void notifyUnloadLowLevelThreads(HMODULE hMod) nothrow - { - HMODULE toFree; - for (;;) - { - ThreadID tid; - void delegate() nothrow cbDllUnload; - { - lowlevelLock.lock_nothrow(); - scope(exit) lowlevelLock.unlock_nothrow(); - - foreach (i; 0 .. ll_nThreads) - if (ll_pThreads[i].cbDllUnload && ll_pThreads[i].hMod == hMod) - { - if (!toFree) - toFree = ll_getModuleHandle(hMod, true); // keep the module alive until the callback returns - cbDllUnload = ll_pThreads[i].cbDllUnload; - tid = ll_pThreads[i].tid; - break; - } - } - if (!cbDllUnload) - break; - cbDllUnload(); // must wait for thread termination - assert(!findLowLevelThread(tid)); - } - if (toFree) - FreeLibrary(toFree); - } - - private void monitorDLLRefCnt() nothrow - { - // this thread keeps the DLL alive until all external references are gone - // (including those from DLLs using druntime in a shared DLL) - while (ll_dllHasExternalReferences(runtimeModule)) - { - // find and unload module that only has internal references left - HMODULE hMod; - { - lowlevelLock.lock_nothrow(); - scope(exit) lowlevelLock.unlock_nothrow(); - - foreach (i; 0 .. ll_nThreads) - if (ll_pThreads[i].cbDllUnload && ll_pThreads[i].hMod != runtimeModule) - if (!ll_dllHasExternalReferences(ll_pThreads[i].hMod)) - { - hMod = ll_pThreads[i].hMod; - break; - } - } - if (hMod) - notifyUnloadLowLevelThreads(hMod); - else - Thread.sleep(100.msecs); - } - - notifyUnloadLowLevelThreads(runtimeModule); - - // the current thread will be terminated without cleanup within the thread - ll_removeThread(GetCurrentThreadId()); - - FreeLibraryAndExitThread(runtimeModule, 0); - } - - HMODULE ll_getModuleHandle(void* funcptr, bool addref = false) nothrow @nogc - { - HMODULE hmod; - DWORD refflag = addref ? 0 : GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT; - if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | refflag, - cast(const(wchar)*) funcptr, &hmod)) - return null; - return hmod; - } - - bool ll_startDLLUnloadThread() nothrow @nogc - { - if (ll_dllMonitorThread !is ThreadID.init) - return true; - - // if a thread is created from a DLL, the MS runtime (starting with VC2015) increments the DLL reference count - // to avoid the DLL being unloaded while the thread is still running. Mimick this behavior here for all - // runtimes not doing this - bool needRef = !msvcUsesUCRT; - if (needRef) - ll_getModuleHandle(runtimeModule, true); - - // the monitor thread must be a low-level thread so the runtime does not attach to it - ll_dllMonitorThread = createLowLevelThread(() { monitorDLLRefCnt(); }); - return ll_dllMonitorThread != ThreadID.init; - } -} - -/** - * Create a thread not under control of the runtime, i.e. TLS module constructors are - * not run and the GC does not suspend it during a collection. - * - * Params: - * dg = delegate to execute in the created thread. - * stacksize = size of the stack of the created thread. The default of 0 will select the - * platform-specific default size. - * cbDllUnload = Windows only: if running in a dynamically loaded DLL, this delegate will be called - * if the DLL is supposed to be unloaded, but the thread is still running. - * The thread must be terminated via `joinLowLevelThread` by the callback. - * - * Returns: the platform specific thread ID of the new thread. If an error occurs, `ThreadID.init` - * is returned. - */ -ThreadID createLowLevelThread(void delegate() nothrow dg, uint stacksize = 0, - void delegate() nothrow cbDllUnload = null) nothrow @nogc -{ - static struct Context - { - void delegate() nothrow dg; - version (Windows) - HMODULE cbMod; - } - auto context = cast(Context*)malloc(Context.sizeof); - scope(exit) free(context); - context.dg = dg; - - ThreadID tid; - version (Windows) - { - // the thread won't start until after the DLL is unloaded - if (thread_DLLProcessDetaching) - return ThreadID.init; - context.cbMod = cbDllUnload ? ll_getModuleHandle(cbDllUnload.funcptr) : null; - if (context.cbMod) - { - int refcnt = dll_getRefCount(context.cbMod); - if (refcnt < 0) - { - // not a dynamically loaded DLL, so never unloaded - cbDllUnload = null; - context.cbMod = null; - } - if (refcnt == 0) - return ThreadID.init; // createLowLevelThread called while DLL is unloading - } - - static extern (Windows) uint thread_lowlevelEntry(void* ctx) nothrow - { - auto context = *cast(Context*)ctx; - free(ctx); - - context.dg(); - - ll_removeThread(GetCurrentThreadId()); - if (context.cbMod && context.cbMod != runtimeModule) - FreeLibrary(context.cbMod); - return 0; - } - - // see Thread.start() for why thread is created in suspended state - HANDLE hThread = cast(HANDLE) _beginthreadex(null, stacksize, &thread_lowlevelEntry, - context, CREATE_SUSPENDED, &tid); - if (!hThread) - return ThreadID.init; - } - - lowlevelLock.lock_nothrow(); - scope(exit) lowlevelLock.unlock_nothrow(); - - ll_nThreads++; - ll_pThreads = cast(ll_ThreadData*)realloc(ll_pThreads, ll_ThreadData.sizeof * ll_nThreads); - ll_pThreads[ll_nThreads - 1] = ll_ThreadData.init; - - version (Windows) - { - ll_pThreads[ll_nThreads - 1].tid = tid; - // ignore callback if not a dynamically loaded DLL - if (cbDllUnload) - { - ll_pThreads[ll_nThreads - 1].cbDllUnload = cbDllUnload; - ll_pThreads[ll_nThreads - 1].hMod = context.cbMod; - if (context.cbMod != runtimeModule) - ll_getModuleHandle(context.cbMod, true); // increment ref count - } - - if (ResumeThread(hThread) == -1) - onThreadError("Error resuming thread"); - CloseHandle(hThread); - - if (cbDllUnload) - ll_startDLLUnloadThread(); - } - else version (Posix) - { - static extern (C) void* thread_lowlevelEntry(void* ctx) nothrow - { - auto context = *cast(Context*)ctx; - free(ctx); - - context.dg(); - ll_removeThread(pthread_self()); - return null; - } - - size_t stksz = adjustStackSize(stacksize); - - pthread_attr_t attr; - - int rc; - if ((rc = pthread_attr_init(&attr)) != 0) - return ThreadID.init; - if (stksz && (rc = pthread_attr_setstacksize(&attr, stksz)) != 0) - return ThreadID.init; - if ((rc = pthread_create(&tid, &attr, &thread_lowlevelEntry, context)) != 0) - return ThreadID.init; - rc = pthread_attr_destroy(&attr); - assert(rc == 0); - - ll_pThreads[ll_nThreads - 1].tid = tid; - } - else - static assert(0, "unsupported os"); - context = null; // free'd in thread - return tid; -} - -/** - * Wait for a thread created with `createLowLevelThread` to terminate. - * - * Note: In a Windows DLL, if this function is called via DllMain with - * argument DLL_PROCESS_DETACH, the thread is terminated forcefully - * without proper cleanup as a deadlock would happen otherwise. - * - * Params: - * tid = the thread ID returned by `createLowLevelThread`. - */ -void joinLowLevelThread(ThreadID tid) nothrow @nogc -{ - version (Windows) - { - HANDLE handle = OpenThreadHandle(tid); - if (!handle) - return; - - if (thread_DLLProcessDetaching) - { - // When being called from DllMain/DLL_DETACH_PROCESS, threads cannot stop - // due to the loader lock being held by the current thread. - // On the other hand, the thread must not continue to run as it will crash - // if the DLL is unloaded. The best guess is to terminate it immediately. - TerminateThread(handle, 1); - WaitForSingleObject(handle, 10); // give it some time to terminate, but don't wait indefinitely - } - else - WaitForSingleObject(handle, INFINITE); - CloseHandle(handle); - } - else version (Posix) - { - if (pthread_join(tid, null) != 0) - onThreadError("Unable to join thread"); - } - else - static assert(0, "unsupported os"); -} - -nothrow @nogc unittest -{ - struct TaskWithContect - { - shared int n = 0; - void run() nothrow - { - n.atomicOp!"+="(1); - } - } - TaskWithContect task; - - ThreadID[8] tids; - for (int i = 0; i < tids.length; i++) - { - tids[i] = createLowLevelThread(&task.run); - assert(tids[i] != ThreadID.init); - } - - for (int i = 0; i < tids.length; i++) - joinLowLevelThread(tids[i]); - - assert(task.n == tids.length); -} - -version (Posix) -private size_t adjustStackSize(size_t sz) nothrow @nogc -{ - if (sz == 0) - return 0; - - // stack size must be at least PTHREAD_STACK_MIN for most platforms. - if (PTHREAD_STACK_MIN > sz) - sz = PTHREAD_STACK_MIN; - - version (CRuntime_Glibc) - { - // On glibc, TLS uses the top of the stack, so add its size to the requested size - sz += externDFunc!("rt.sections_elf_shared.sizeOfTLS", - size_t function() @nogc nothrow)(); - } - - // stack size must be a multiple of pageSize - sz = ((sz + pageSize - 1) & ~(pageSize - 1)); - - return sz; -} From 96be4666462f821bf7d7488f86ab556a414621ee Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:14:41 +0300 Subject: [PATCH 06/32] Thread class quick and dirty moved to posix_impl --- druntime/src/core/thread/osthread.d | 6 +++--- druntime/src/core/thread/posix_impl.d | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/druntime/src/core/thread/osthread.d b/druntime/src/core/thread/osthread.d index af781a66a4c6..c15cfa4d4827 100644 --- a/druntime/src/core/thread/osthread.d +++ b/druntime/src/core/thread/osthread.d @@ -431,7 +431,7 @@ class Thread : ThreadBase } } -private Thread toThread(return scope ThreadBase t) @trusted nothrow @nogc pure +package /*FIXME: private*/ Thread toThread(return scope ThreadBase t) @trusted nothrow @nogc pure { return cast(Thread) cast(void*) t; } @@ -1888,7 +1888,7 @@ else version (Posix) } } - private + package /*FIXME: private*/ { // // Entry point for POSIX threads @@ -2530,7 +2530,7 @@ nothrow @nogc unittest } version (Posix) -private size_t adjustStackSize(size_t sz) nothrow @nogc +package /*FIXME: private*/ size_t adjustStackSize(size_t sz) nothrow @nogc { if (sz == 0) return 0; diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index a59e81b39dd7..4fc67fd4babc 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -17,6 +17,7 @@ import core.exception : onOutOfMemoryError; import core.internal.traits : externDFunc; import core.memory : GC, pageSize; import core.thread.context; +import core.thread.osthread; import core.thread.threadbase; import core.thread.types; import core.time; From 591f664aa7307322b7d39facbff5cd0d3402a035 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:18:22 +0300 Subject: [PATCH 07/32] Source comment filename changed --- druntime/src/core/thread/posix_impl.d | 2 +- druntime/src/core/thread/windows_impl.d | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index 4fc67fd4babc..67403f8e36fe 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -7,7 +7,7 @@ * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Sean Kelly, Walter Bright, Alex Rønne Petersen, Martin Nowak - * Source: $(DRUNTIMESRC core/thread/osthread.d) + * Source: $(DRUNTIMESRC core/thread/posix_impl.d) */ module core.thread.posix_impl; diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index 4a9255af3333..b36f1e6d9599 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -7,7 +7,7 @@ * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Sean Kelly, Walter Bright, Alex Rønne Petersen, Martin Nowak - * Source: $(DRUNTIMESRC core/thread/osthread.d) + * Source: $(DRUNTIMESRC core/thread/windows_impl.d) */ module core.thread.windows_impl; From 831debb31dc9b40a4afb602dc483da8de862ec91 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:21:38 +0300 Subject: [PATCH 08/32] static assert comment about threading changed --- druntime/src/core/thread/osthread.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druntime/src/core/thread/osthread.d b/druntime/src/core/thread/osthread.d index c15cfa4d4827..a0440b11c653 100644 --- a/druntime/src/core/thread/osthread.d +++ b/druntime/src/core/thread/osthread.d @@ -30,7 +30,7 @@ version (Posix) else version (Windows) public import core.thread.windows_impl; else - static assert(0, "unsupported operating system"); + static assert(false, "Unknown threading implementation."); version (OSX) version = Darwin; From 353bbdd57f8c32ba577f8fb1e97366886539cb95 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:25:53 +0300 Subject: [PATCH 09/32] posix_impl: unused InlineAsm versions removed --- druntime/src/core/thread/posix_impl.d | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index 67403f8e36fe..fb4cb3dbfc43 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -37,25 +37,6 @@ else version (TVOS) else version (WatchOS) version = Darwin; -version (D_InlineAsm_X86) -{ - version (Windows) - version = AsmX86_Windows; - else version (Posix) - version = AsmX86_Posix; -} -else version (D_InlineAsm_X86_64) -{ - version (Windows) - { - version = AsmX86_64_Windows; - } - else version (Posix) - { - version = AsmX86_64_Posix; - } -} - version (Posix) { static import core.sys.posix.pthread; From 6038e33f6ce1918967f0ee9804fd246653015597 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:29:09 +0300 Subject: [PATCH 10/32] unused imports removed --- druntime/src/core/thread/osthread.d | 1 - druntime/src/core/thread/posix_impl.d | 3 --- 2 files changed, 4 deletions(-) diff --git a/druntime/src/core/thread/osthread.d b/druntime/src/core/thread/osthread.d index a0440b11c653..98aab4cfd22a 100644 --- a/druntime/src/core/thread/osthread.d +++ b/druntime/src/core/thread/osthread.d @@ -13,7 +13,6 @@ module core.thread.osthread; import core.atomic; -import core.exception : onOutOfMemoryError; import core.internal.traits : externDFunc; import core.memory : GC, pageSize; import core.thread.context; diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index fb4cb3dbfc43..ffe4d66c50d6 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -15,11 +15,8 @@ module core.thread.posix_impl; import core.atomic; import core.exception : onOutOfMemoryError; import core.internal.traits : externDFunc; -import core.memory : GC, pageSize; -import core.thread.context; import core.thread.osthread; import core.thread.threadbase; -import core.thread.types; import core.time; /////////////////////////////////////////////////////////////////////////////// From e249b2829aeabae2ceb50033a67ec7e8e27c7b9b Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:29:24 +0300 Subject: [PATCH 11/32] old style comment removed --- druntime/src/core/thread/posix_impl.d | 4 ---- 1 file changed, 4 deletions(-) diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index ffe4d66c50d6..07b30d5178c3 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -19,10 +19,6 @@ import core.thread.osthread; import core.thread.threadbase; import core.time; -/////////////////////////////////////////////////////////////////////////////// -// Platform Detection and Memory Allocation -/////////////////////////////////////////////////////////////////////////////// - version (Posix): version (OSX) From c71c52fa316f79106d05b97df110b0c1b4578fe4 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:42:15 +0300 Subject: [PATCH 12/32] posix_impl: some version Windows branches removed --- druntime/src/core/thread/posix_impl.d | 149 +++----------------------- 1 file changed, 14 insertions(+), 135 deletions(-) diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index 07b30d5178c3..31281c8f52a2 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -30,7 +30,7 @@ else version (TVOS) else version (WatchOS) version = Darwin; -version (Posix) +version (all) { static import core.sys.posix.pthread; static import core.sys.posix.signal; @@ -93,8 +93,6 @@ version (Posix) // Use POSIX threads for suspend/resume } } -else - static assert(0, "unsupported operating system"); version (GNU) { @@ -104,15 +102,7 @@ version (GNU) version (CoreDdoc) {} else class Thread : ThreadBase { - version (Windows) - { - private HANDLE m_hndl; - } - - version (Posix) - { - package /*FIXME: private*/ shared bool m_isRunning; - } + package /*FIXME: private*/ shared bool m_isRunning; version (Darwin) { @@ -124,16 +114,7 @@ class Thread : ThreadBase private __gshared bool m_isRTClass; } - version (Windows) - { - alias TLSKey = uint; - } - else version (Posix) - { - alias TLSKey = pthread_key_t; - } - else - static assert(0, "unsupported os"); + alias TLSKey = pthread_key_t; this( void function() fn, size_t sz = 0 ) @safe pure nothrow @nogc { @@ -155,13 +136,7 @@ class Thread : ThreadBase if (super.destructBeforeDtor()) return; - version (Windows) - { - m_addr = m_addr.init; - CloseHandle( m_hndl ); - m_hndl = m_hndl.init; - } - else version (Posix) + version (all) { if (m_addr != m_addr.init) pthread_detach( m_addr ); @@ -171,8 +146,6 @@ class Thread : ThreadBase m_tmach = m_tmach.init; } } - else - static assert(0, "unsupported OS"); } package /*FIXME: private*/ final void run() @@ -185,23 +158,7 @@ class Thread : ThreadBase return ThreadBase.getThis().toThread; } - version (Windows) - { - version (X86) - { - uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax - } - else version (X86_64) - { - ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax - // r8,r9,r10,r11,r12,r13,r14,r15 - } - else - { - static assert(false, "Architecture not supported." ); - } - } - else version (Darwin) + version (Darwin) { version (X86) { @@ -263,11 +220,7 @@ class Thread : ThreadBase override final void[] savedRegisters() nothrow @nogc { - version (Windows) - { - return m_reg; - } - else version (Darwin) + version (Darwin) { return m_reg; } @@ -418,24 +371,7 @@ class Thread : ThreadBase return null; } - version (Windows) - { - @property static int PRIORITY_MIN() @nogc nothrow pure @safe - { - return THREAD_PRIORITY_IDLE; - } - - @property static const(int) PRIORITY_MAX() @nogc nothrow pure @safe - { - return THREAD_PRIORITY_TIME_CRITICAL; - } - - @property static int PRIORITY_DEFAULT() @nogc nothrow pure @safe - { - return THREAD_PRIORITY_NORMAL; - } - } - else version (Posix) + version (all) { private struct Priority { @@ -557,9 +493,6 @@ class Thread : ThreadBase &loadGlobal!"PRIORITY_DEFAULT")(); } } - else - static assert(0, "unsupported OS"); - version (NetBSD) { @@ -570,15 +503,11 @@ class Thread : ThreadBase final @property int priority() { - version (Windows) - { - return GetThreadPriority( m_hndl ); - } - else version (NetBSD) + version (NetBSD) { return fakePriority==int.max? PRIORITY_DEFAULT : fakePriority; } - else version (Posix) + else { int policy; sched_param param; @@ -591,8 +520,6 @@ class Thread : ThreadBase } return param.sched_priority; } - else - static assert(0, "unsupported os"); } final @property void priority( int val ) @@ -603,12 +530,7 @@ class Thread : ThreadBase } do { - version (Windows) - { - if ( !SetThreadPriority( m_hndl, val ) ) - throw new ThreadException( "Unable to set thread priority" ); - } - else version (Solaris) + version (Solaris) { // the pthread_setschedprio(3c) and pthread_setschedparam functions // are broken for the default (TS / time sharing) scheduling class. @@ -638,7 +560,7 @@ class Thread : ThreadBase { fakePriority = val; } - else version (Posix) + else { static if (__traits(compiles, core.sys.posix.pthread.pthread_setschedprio)) { @@ -673,8 +595,6 @@ class Thread : ThreadBase } } } - else - static assert(0, "unsupported os"); } @@ -710,18 +630,7 @@ class Thread : ThreadBase if (!super.isRunning()) return false; - version (Windows) - { - uint ecode = 0; - GetExitCodeThread( m_hndl, &ecode ); - return ecode == STILL_ACTIVE; - } - else version (Posix) - { - return atomicLoad(m_isRunning); - } - else - static assert(0, "unsupported os"); + return atomicLoad(m_isRunning); } static void sleep( Duration val ) @nogc nothrow @trusted @@ -731,30 +640,7 @@ class Thread : ThreadBase } do { - version (Windows) - { - auto maxSleepMillis = dur!("msecs")( uint.max - 1 ); - - // avoid a non-zero time to be round down to 0 - if ( val > dur!"msecs"( 0 ) && val < dur!"msecs"( 1 ) ) - val = dur!"msecs"( 1 ); - - // NOTE: In instances where all other threads in the process have a - // lower priority than the current thread, the current thread - // will not yield with a sleep time of zero. However, unlike - // yield(), the user is not asking for a yield to occur but - // only for execution to suspend for the requested interval. - // Therefore, expected performance may not be met if a yield - // is forced upon the user. - while ( val > maxSleepMillis ) - { - Sleep( cast(uint) - maxSleepMillis.total!"msecs" ); - val -= maxSleepMillis; - } - Sleep( cast(uint) val.total!"msecs" ); - } - else version (Posix) + version (all) { timespec tin = void; timespec tout = void; @@ -771,17 +657,10 @@ class Thread : ThreadBase tin = tout; } } - else - static assert(0, "unsupported os"); } static void yield() @nogc nothrow { - version (Windows) - SwitchToThread(); - else version (Posix) - sched_yield(); - else - static assert(0, "unsupported os"); + sched_yield(); } } From f4e30d8cbadea5f09950125442f99455731f5ed3 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:47:29 +0300 Subject: [PATCH 13/32] posix_impl: Windows version branch remove from .join() method --- druntime/src/core/thread/posix_impl.d | 40 ++++++--------------------- druntime/src/core/thread/threadbase.d | 13 ++++++++- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index 31281c8f52a2..2ce1aa77a5ab 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -338,37 +338,15 @@ class Thread : ThreadBase override final Throwable join( bool rethrow = true ) { - version (Windows) - { - if ( m_addr != m_addr.init && WaitForSingleObject( m_hndl, INFINITE ) != WAIT_OBJECT_0 ) - throw new ThreadException( "Unable to join thread" ); - // NOTE: m_addr must be cleared before m_hndl is closed to avoid - // a race condition with isRunning. The operation is done - // with atomicStore to prevent compiler reordering. - atomicStore!(MemoryOrder.raw)(*cast(shared)&m_addr, m_addr.init); - CloseHandle( m_hndl ); - m_hndl = m_hndl.init; - } - else version (Posix) - { - if ( m_addr != m_addr.init && pthread_join( m_addr, null ) != 0 ) - throw new ThreadException( "Unable to join thread" ); - // NOTE: pthread_join acts as a substitute for pthread_detach, - // which is normally called by the dtor. Setting m_addr - // to zero ensures that pthread_detach will not be called - // on object destruction. - m_addr = m_addr.init; - } - else - static assert(0, "unsupported OS"); - - if ( m_unhandled ) - { - if ( rethrow ) - throw m_unhandled; - return m_unhandled; - } - return null; + if ( m_addr != m_addr.init && pthread_join( m_addr, null ) != 0 ) + throw new ThreadException( "Unable to join thread" ); + // NOTE: pthread_join acts as a substitute for pthread_detach, + // which is normally called by the dtor. Setting m_addr + // to zero ensures that pthread_detach will not be called + // on object destruction. + m_addr = m_addr.init; + + return super.join(rethrow); } version (all) diff --git a/druntime/src/core/thread/threadbase.d b/druntime/src/core/thread/threadbase.d index 350d738f8b8a..66a5504b837e 100644 --- a/druntime/src/core/thread/threadbase.d +++ b/druntime/src/core/thread/threadbase.d @@ -182,7 +182,18 @@ class ThreadBase * Any exception not handled by this thread if rethrow = false, null * otherwise. */ - abstract Throwable join(bool rethrow = true); + abstract Throwable join(bool rethrow = true) + { + if ( m_unhandled ) + { + if ( rethrow ) + throw m_unhandled; + + return m_unhandled; + } + + return null; + } /** * Filter any exceptions that escaped the thread entry point. From c9b03a68716968d8b28b80b912d222ffd34ac4bd Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:49:16 +0300 Subject: [PATCH 14/32] TODO comment added --- druntime/src/core/thread/threadbase.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druntime/src/core/thread/threadbase.d b/druntime/src/core/thread/threadbase.d index 66a5504b837e..5aad75360569 100644 --- a/druntime/src/core/thread/threadbase.d +++ b/druntime/src/core/thread/threadbase.d @@ -488,7 +488,7 @@ package: string m_name; size_t m_sz; bool m_isDaemon; - Throwable m_unhandled; + Throwable m_unhandled; //TODO: private /////////////////////////////////////////////////////////////////////////// // Storage of Active Thread From 261e834a3a02a7738140dc6bcbfa7b4272d94a97 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:53:43 +0300 Subject: [PATCH 15/32] posix_impl: all Windows version branches removed --- druntime/src/core/thread/posix_impl.d | 29 ++------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index 2ce1aa77a5ab..ff85dc407d91 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -249,23 +249,7 @@ class Thread : ThreadBase multiThreadedFlag = false; } - version (Windows) - { - // NOTE: If a thread is just executing DllMain() - // while another thread is started here, it holds an OS internal - // lock that serializes DllMain with CreateThread. As the code - // might request a synchronization on slock (e.g. in thread_findByAddr()), - // we cannot hold that lock while creating the thread without - // creating a deadlock - // - // Solution: Create the thread in suspended state and then - // add and resume it with slock acquired - assert(m_sz <= uint.max, "m_sz must be less than or equal to uint.max"); - m_hndl = cast(HANDLE) _beginthreadex( null, cast(uint) m_sz, &thread_entryPoint, cast(void*) this, CREATE_SUSPENDED, &m_addr ); - if ( cast(size_t) m_hndl == 0 ) - onThreadError( "Error creating thread" ); - } - else version (Posix) + version (all) { size_t stksz = adjustStackSize( m_sz ); @@ -276,20 +260,13 @@ class Thread : ThreadBase if ( stksz && pthread_attr_setstacksize( &attr, stksz ) ) onThreadError( "Error initializing thread stack size" ); } - else - static assert(0, "unsupported OS"); slock.lock_nothrow(); scope(exit) slock.unlock_nothrow(); { incrementAboutToStart(this); - version (Windows) - { - if ( ResumeThread( m_hndl ) == -1 ) - onThreadError( "Error resuming thread" ); - } - else version (Posix) + version (all) { // NOTE: This is also set to true by thread_entryPoint, but set it // here as well so the calling thread will see the isRunning @@ -329,8 +306,6 @@ class Thread : ThreadBase onThreadError( "Error creating thread" ); } } - else - static assert(0, "unsupported OS"); return this; } From d07dfddb1a0938613d18b016461a2e88bf4ed51d Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:55:49 +0300 Subject: [PATCH 16/32] windows_impl: start deal with Windows --- druntime/src/core/thread/windows_impl.d | 106 ++---------------------- 1 file changed, 5 insertions(+), 101 deletions(-) diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index b36f1e6d9599..cf9a80bc3c31 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -15,45 +15,13 @@ module core.thread.windows_impl; import core.atomic; import core.exception : onOutOfMemoryError; import core.internal.traits : externDFunc; -import core.memory : GC, pageSize; -import core.thread.context; +import core.thread.osthread; import core.thread.threadbase; -import core.thread.types; import core.time; -/////////////////////////////////////////////////////////////////////////////// -// Platform Detection and Memory Allocation -/////////////////////////////////////////////////////////////////////////////// - -version (OSX) - version = Darwin; -else version (iOS) - version = Darwin; -else version (TVOS) - version = Darwin; -else version (WatchOS) - version = Darwin; +version (Windows): -version (D_InlineAsm_X86) -{ - version (Windows) - version = AsmX86_Windows; - else version (Posix) - version = AsmX86_Posix; -} -else version (D_InlineAsm_X86_64) -{ - version (Windows) - { - version = AsmX86_64_Windows; - } - else version (Posix) - { - version = AsmX86_64_Posix; - } -} - -version (Windows) +version (all) { import core.stdc.stdint : uintptr_t; // for _beginthreadex decl below import core.stdc.stdlib : free, malloc, realloc; @@ -70,75 +38,11 @@ version (Windows) private extern (Windows) alias btex_fptr = uint function(void*); private extern (C) uintptr_t _beginthreadex(void*, uint, btex_fptr, void*, uint, uint*) nothrow @nogc; } -else version (Posix) -{ - static import core.sys.posix.pthread; - static import core.sys.posix.signal; - import core.stdc.errno : EINTR, errno; - import core.sys.posix.pthread : pthread_atfork, pthread_attr_destroy, pthread_attr_getstack, pthread_attr_init, - pthread_attr_setstacksize, pthread_create, pthread_detach, pthread_getschedparam, pthread_join, pthread_self, - pthread_setschedparam, sched_get_priority_max, sched_get_priority_min, sched_param, sched_yield; - import core.sys.posix.semaphore : sem_init, sem_post, sem_t, sem_wait; - import core.sys.posix.signal : pthread_kill, sigaction, sigaction_t, sigdelset, sigfillset, sigset_t, sigsuspend, - SIGUSR1, stack_t; - import core.sys.posix.stdlib : free, malloc, realloc; - import core.sys.posix.sys.types : pthread_attr_t, pthread_key_t, pthread_t; - import core.sys.posix.time : nanosleep, timespec; - - version (Darwin) - { - // Use macOS threads for suspend/resume - import core.sys.darwin.mach.kern_return : KERN_SUCCESS; - import core.sys.darwin.mach.port : mach_port_t; - import core.sys.darwin.mach.thread_act : mach_msg_type_number_t, - thread_get_state, thread_resume, thread_suspend; - import core.sys.darwin.pthread : pthread_mach_thread_np; - version (X86) - { - import core.sys.darwin.mach.thread_act : - x86_THREAD_STATE32, x86_THREAD_STATE32_COUNT, x86_thread_state32_t; - } - else version (X86_64) - { - import core.sys.darwin.mach.thread_act : - x86_THREAD_STATE64, x86_THREAD_STATE64_COUNT, x86_thread_state64_t; - } - else version (AArch64) - { - import core.sys.darwin.mach.thread_act : - ARM_THREAD_STATE64, ARM_THREAD_STATE64_COUNT, arm_thread_state64_t; - } - else version (PPC) - { - import core.sys.darwin.mach.thread_act : - PPC_THREAD_STATE, PPC_THREAD_STATE_COUNT, ppc_thread_state_t; - } - else version (PPC64) - { - import core.sys.darwin.mach.thread_act : - PPC_THREAD_STATE64, PPC_THREAD_STATE64_COUNT, ppc_thread_state64_t; - } - } - else version (Solaris) - { - // Use Solaris threads for suspend/resume - import core.sys.posix.sys.wait : idtype_t; - import core.sys.solaris.sys.priocntl : PC_CLNULL, PC_GETCLINFO, PC_GETPARMS, PC_SETPARMS, pcinfo_t, pcparms_t, priocntl; - import core.sys.solaris.sys.types : P_MYID, pri_t; - import core.sys.solaris.thread : thr_stksegment, thr_suspend, thr_continue; - import core.sys.solaris.sys.procfs : PR_STOPPED, lwpstatus_t; - } - else - { - // Use POSIX threads for suspend/resume - } -} -else - static assert(0, "unsupported operating system"); version (GNU) { - import gcc.builtins; + //FIXME: remove or not? + //~ import gcc.builtins; } /** From ce06b84c2877890d213eec4d8cf2b87427d835c6 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:57:10 +0300 Subject: [PATCH 17/32] windows_impl: Thread version CoreDdoc removed --- druntime/src/core/thread/windows_impl.d | 293 +----------------------- 1 file changed, 4 insertions(+), 289 deletions(-) diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index cf9a80bc3c31..2838a8cb0edd 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -58,48 +58,9 @@ private extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc; version (DigitalMars) { - version (Windows) - { - extern(D) void* swapContext(void* newContext) nothrow @nogc - { - return _d_eh_swapContext(newContext); - } - } - else + extern(D) void* swapContext(void* newContext) nothrow @nogc { - extern(C) void* _d_eh_swapContextDwarf(void* newContext) nothrow @nogc; - - extern(D) void* swapContext(void* newContext) nothrow @nogc - { - /* Detect at runtime which scheme is being used. - * Eventually, determine it statically. - */ - static int which = 0; - final switch (which) - { - case 0: - { - assert(newContext == null); - auto p = _d_eh_swapContext(newContext); - auto pdwarf = _d_eh_swapContextDwarf(newContext); - if (p) - { - which = 1; - return p; - } - else if (pdwarf) - { - which = 2; - return pdwarf; - } - return null; - } - case 1: - return _d_eh_swapContext(newContext); - case 2: - return _d_eh_swapContextDwarf(newContext); - } - } + return _d_eh_swapContext(newContext); } } else @@ -110,257 +71,11 @@ else } } -/** - * This class encapsulates all threading functionality for the D - * programming language. As thread manipulation is a required facility - * for garbage collection, all user threads should derive from this - * class, and instances of this class should never be explicitly deleted. - * A new thread may be created using either derivation or composition, as - * in the following example. - */ -version (CoreDdoc) -class Thread : ThreadBase -{ - /** - * Initializes a thread object which is associated with a static - * D function. - * - * Params: - * fn = The thread function. - * sz = The stack size for this thread. - * - * In: - * fn must not be null. - */ - this( void function() fn, size_t sz = 0 ) @safe pure nothrow @nogc - { - } - - - /** - * Initializes a thread object which is associated with a dynamic - * D function. - * - * Params: - * dg = The thread function. - * sz = The stack size for this thread. - * - * In: - * dg must not be null. - */ - this( void delegate() dg, size_t sz = 0 ) @safe pure nothrow @nogc - { - } - - package this( size_t sz = 0 ) @safe pure nothrow @nogc - { - } - - /** - * Cleans up any remaining resources used by this object. - */ - ~this() nothrow @nogc - { - } - - // - // Thread entry point. Invokes the function or delegate passed on - // construction (if any). - // - private final void run() - { - } - - /** - * Provides a reference to the calling thread. - * - * Returns: - * The thread object representing the calling thread. The result of - * deleting this object is undefined. If the current thread is not - * attached to the runtime, a null reference is returned. - */ - static Thread getThis() @safe nothrow @nogc - { - return null; - } - - /// - override final void[] savedRegisters() nothrow @nogc - { - return null; - } - - /** - * Starts the thread and invokes the function or delegate passed upon - * construction. - * - * In: - * This routine may only be called once per thread instance. - * - * Throws: - * ThreadException if the thread fails to start. - */ - final Thread start() nothrow - { - return null; - } - - /** - * Waits for this thread to complete. If the thread terminated as the - * result of an unhandled exception, this exception will be rethrown. - * - * Params: - * rethrow = Rethrow any unhandled exception which may have caused this - * thread to terminate. - * - * Throws: - * ThreadException if the operation fails. - * Any exception not handled by the joined thread. - * - * Returns: - * Any exception not handled by this thread if rethrow = false, null - * otherwise. - */ - override final Throwable join( bool rethrow = true ) - { - return null; - } - - /** - * The minimum scheduling priority that may be set for a thread. On - * systems where multiple scheduling policies are defined, this value - * represents the minimum valid priority for the scheduling policy of - * the process. - */ - @property static int PRIORITY_MIN() @nogc nothrow pure @trusted - { - return 0; - } - - /** - * The maximum scheduling priority that may be set for a thread. On - * systems where multiple scheduling policies are defined, this value - * represents the maximum valid priority for the scheduling policy of - * the process. - */ - @property static const(int) PRIORITY_MAX() @nogc nothrow pure @trusted - { - return 0; - } - - /** - * The default scheduling priority that is set for a thread. On - * systems where multiple scheduling policies are defined, this value - * represents the default priority for the scheduling policy of - * the process. - */ - @property static int PRIORITY_DEFAULT() @nogc nothrow pure @trusted - { - return 0; - } - - /** - * Gets the scheduling priority for the associated thread. - * - * Note: Getting the priority of a thread that already terminated - * might return the default priority. - * - * Returns: - * The scheduling priority of this thread. - */ - final @property int priority() - { - return 0; - } - - /** - * Sets the scheduling priority for the associated thread. - * - * Note: Setting the priority of a thread that already terminated - * might have no effect. - * - * Params: - * val = The new scheduling priority of this thread. - */ - final @property void priority( int val ) - { - } - - /** - * Tests whether this thread is running. - * - * Returns: - * true if the thread is running, false if not. - */ - override final @property bool isRunning() nothrow @nogc - { - return false; - } - - /** - * Suspends the calling thread for at least the supplied period. This may - * result in multiple OS calls if period is greater than the maximum sleep - * duration supported by the operating system. - * - * Params: - * val = The minimum duration the calling thread should be suspended. - * - * In: - * period must be non-negative. - * - * Example: - * ------------------------------------------------------------------------ - * - * Thread.sleep( dur!("msecs")( 50 ) ); // sleep for 50 milliseconds - * Thread.sleep( dur!("seconds")( 5 ) ); // sleep for 5 seconds - * - * ------------------------------------------------------------------------ - */ - static void sleep( Duration val ) @nogc nothrow @trusted - { - } - - /** - * Forces a context switch to occur away from the calling thread. - */ - static void yield() @nogc nothrow - { - } -} - version (CoreDdoc) {} else class Thread : ThreadBase { - version (Windows) - { - private HANDLE m_hndl; - } - - version (Posix) - { - private shared bool m_isRunning; - } - - version (Darwin) - { - private mach_port_t m_tmach; - } - - version (Solaris) - { - private __gshared bool m_isRTClass; - } - - version (Windows) - { - alias TLSKey = uint; - } - else version (Posix) - { - alias TLSKey = pthread_key_t; - } - else - static assert(0, "unsupported os"); + private HANDLE m_hndl; + alias TLSKey = uint; this( void function() fn, size_t sz = 0 ) @safe pure nothrow @nogc { From 857784af9fcdf64820593baab8c070e7f791e175 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:58:53 +0300 Subject: [PATCH 18/32] windows_impl: some simple Windows version branches removed --- druntime/src/core/thread/windows_impl.d | 99 ++----------------------- 1 file changed, 5 insertions(+), 94 deletions(-) diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index 2838a8cb0edd..b6bcbbbdcdeb 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -97,24 +97,9 @@ class Thread : ThreadBase if (super.destructBeforeDtor()) return; - version (Windows) - { - m_addr = m_addr.init; - CloseHandle( m_hndl ); - m_hndl = m_hndl.init; - } - else version (Posix) - { - if (m_addr != m_addr.init) - pthread_detach( m_addr ); - m_addr = m_addr.init; - version (Darwin) - { - m_tmach = m_tmach.init; - } - } - else - static assert(0, "unsupported OS"); + m_addr = m_addr.init; + CloseHandle( m_hndl ); + m_hndl = m_hndl.init; } private final void run() @@ -127,58 +112,7 @@ class Thread : ThreadBase return ThreadBase.getThis().toThread; } - version (Windows) - { - version (X86) - { - uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax - } - else version (X86_64) - { - ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax - // r8,r9,r10,r11,r12,r13,r14,r15 - } - else - { - static assert(false, "Architecture not supported." ); - } - } - else version (Darwin) - { - version (X86) - { - uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax - } - else version (X86_64) - { - ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax - // r8,r9,r10,r11,r12,r13,r14,r15 - } - else version (AArch64) - { - ulong[33] m_reg; // x0-x31, pc - } - else version (ARM) - { - uint[16] m_reg; // r0-r15 - } - else version (PPC) - { - // Make the assumption that we only care about non-fp and non-vr regs. - // ??? : it seems plausible that a valid address can be copied into a VR. - uint[32] m_reg; // r0-31 - } - else version (PPC64) - { - // As above. - ulong[32] m_reg; // r0-31 - } - else - { - static assert(false, "Architecture not supported." ); - } - } - else version (Solaris) + version (all) { version (X86) { @@ -189,14 +123,6 @@ class Thread : ThreadBase ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax // r8,r9,r10,r11,r12,r13,r14,r15 } - else version (SPARC) - { - int[33] m_reg; // g0-7, o0-7, l0-7, i0-7, pc - } - else version (SPARC64) - { - long[33] m_reg; // g0-7, o0-7, l0-7, i0-7, pc - } else { static assert(false, "Architecture not supported." ); @@ -205,22 +131,7 @@ class Thread : ThreadBase override final void[] savedRegisters() nothrow @nogc { - version (Windows) - { - return m_reg; - } - else version (Darwin) - { - return m_reg; - } - else version (Solaris) - { - return m_reg; - } - else - { - return null; - } + return m_reg; } final Thread start() nothrow From 17888bfc1119502df2822320c1267a9167738d12 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 12:59:55 +0300 Subject: [PATCH 19/32] windows_impl: some simple Windows version branches removed - 2 --- druntime/src/core/thread/windows_impl.d | 64 ++----------------------- 1 file changed, 3 insertions(+), 61 deletions(-) diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index b6bcbbbdcdeb..a72e2544de19 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -149,7 +149,7 @@ class Thread : ThreadBase multiThreadedFlag = false; } - version (Windows) + version (all) { // NOTE: If a thread is just executing DllMain() // while another thread is started here, it holds an OS internal @@ -165,72 +165,14 @@ class Thread : ThreadBase if ( cast(size_t) m_hndl == 0 ) onThreadError( "Error creating thread" ); } - else version (Posix) - { - size_t stksz = adjustStackSize( m_sz ); - - pthread_attr_t attr; - - if ( pthread_attr_init( &attr ) ) - onThreadError( "Error initializing thread attributes" ); - if ( stksz && pthread_attr_setstacksize( &attr, stksz ) ) - onThreadError( "Error initializing thread stack size" ); - } - else - static assert(0, "unsupported OS"); slock.lock_nothrow(); scope(exit) slock.unlock_nothrow(); { incrementAboutToStart(this); - version (Windows) - { - if ( ResumeThread( m_hndl ) == -1 ) - onThreadError( "Error resuming thread" ); - } - else version (Posix) - { - // NOTE: This is also set to true by thread_entryPoint, but set it - // here as well so the calling thread will see the isRunning - // state immediately. - atomicStore!(MemoryOrder.raw)(m_isRunning, true); - scope( failure ) atomicStore!(MemoryOrder.raw)(m_isRunning, false); - - version (Shared) - { - auto libs = externDFunc!("rt.sections_elf_shared.pinLoadedLibraries", - void* function() @nogc nothrow)(); - - auto ps = cast(void**).malloc(2 * size_t.sizeof); - if (ps is null) onOutOfMemoryError(); - ps[0] = cast(void*)this; - ps[1] = cast(void*)libs; - if ( pthread_create( &m_addr, &attr, &thread_entryPoint, ps ) != 0 ) - { - externDFunc!("rt.sections_elf_shared.unpinLoadedLibraries", - void function(void*) @nogc nothrow)(libs); - .free(ps); - onThreadError( "Error creating thread" ); - } - } - else - { - if ( pthread_create( &m_addr, &attr, &thread_entryPoint, cast(void*) this ) != 0 ) - onThreadError( "Error creating thread" ); - } - if ( pthread_attr_destroy( &attr ) != 0 ) - onThreadError( "Error destroying thread attributes" ); - - version (Darwin) - { - m_tmach = pthread_mach_thread_np( m_addr ); - if ( m_tmach == m_tmach.init ) - onThreadError( "Error creating thread" ); - } - } - else - static assert(0, "unsupported OS"); + if ( ResumeThread( m_hndl ) == -1 ) + onThreadError( "Error resuming thread" ); return this; } From db50ad6f2d6293dd8f88cde0cb70f6ea231d82ff Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 13:01:03 +0300 Subject: [PATCH 20/32] windows_impl: some simple Windows version branches removed - 3 --- druntime/src/core/thread/windows_impl.d | 173 ++---------------------- 1 file changed, 10 insertions(+), 163 deletions(-) diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index a72e2544de19..0102640d705f 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -180,40 +180,19 @@ class Thread : ThreadBase override final Throwable join( bool rethrow = true ) { - version (Windows) - { - if ( m_addr != m_addr.init && WaitForSingleObject( m_hndl, INFINITE ) != WAIT_OBJECT_0 ) - throw new ThreadException( "Unable to join thread" ); - // NOTE: m_addr must be cleared before m_hndl is closed to avoid - // a race condition with isRunning. The operation is done - // with atomicStore to prevent compiler reordering. - atomicStore!(MemoryOrder.raw)(*cast(shared)&m_addr, m_addr.init); - CloseHandle( m_hndl ); - m_hndl = m_hndl.init; - } - else version (Posix) - { - if ( m_addr != m_addr.init && pthread_join( m_addr, null ) != 0 ) - throw new ThreadException( "Unable to join thread" ); - // NOTE: pthread_join acts as a substitute for pthread_detach, - // which is normally called by the dtor. Setting m_addr - // to zero ensures that pthread_detach will not be called - // on object destruction. - m_addr = m_addr.init; - } - else - static assert(0, "unsupported OS"); + if ( m_addr != m_addr.init && WaitForSingleObject( m_hndl, INFINITE ) != WAIT_OBJECT_0 ) + throw new ThreadException( "Unable to join thread" ); + // NOTE: m_addr must be cleared before m_hndl is closed to avoid + // a race condition with isRunning. The operation is done + // with atomicStore to prevent compiler reordering. + atomicStore!(MemoryOrder.raw)(*cast(shared)&m_addr, m_addr.init); + CloseHandle( m_hndl ); + m_hndl = m_hndl.init; - if ( m_unhandled ) - { - if ( rethrow ) - throw m_unhandled; - return m_unhandled; - } - return null; + return super.join(rethrow); } - version (Windows) + version (all) { @property static int PRIORITY_MIN() @nogc nothrow pure @safe { @@ -230,138 +209,6 @@ class Thread : ThreadBase return THREAD_PRIORITY_NORMAL; } } - else version (Posix) - { - private struct Priority - { - int PRIORITY_MIN = int.min; - int PRIORITY_DEFAULT = int.min; - int PRIORITY_MAX = int.min; - } - - /* - Lazily loads one of the members stored in a hidden global variable of - type `Priority`. Upon the first access of either member, the entire - `Priority` structure is initialized. Multiple initializations from - different threads calling this function are tolerated. - - `which` must be one of `PRIORITY_MIN`, `PRIORITY_DEFAULT`, - `PRIORITY_MAX`. - */ - private static shared Priority cache; - private static int loadGlobal(string which)() - { - auto local = atomicLoad(mixin("cache." ~ which)); - if (local != local.min) return local; - // There will be benign races - auto loaded = loadPriorities; - static foreach (i, _; loaded.tupleof) - atomicStore(cache.tupleof[i], loaded.tupleof[i]); - return atomicLoad(mixin("cache." ~ which)); - } - - /* - Loads all priorities and returns them as a `Priority` structure. This - function is thread-neutral. - */ - private static Priority loadPriorities() @nogc nothrow @trusted - { - Priority result; - version (Solaris) - { - pcparms_t pcParms; - pcinfo_t pcInfo; - - pcParms.pc_cid = PC_CLNULL; - if (priocntl(idtype_t.P_PID, P_MYID, PC_GETPARMS, &pcParms) == -1) - assert( 0, "Unable to get scheduling class" ); - - pcInfo.pc_cid = pcParms.pc_cid; - // PC_GETCLINFO ignores the first two args, use dummy values - if (priocntl(idtype_t.P_PID, 0, PC_GETCLINFO, &pcInfo) == -1) - assert( 0, "Unable to get scheduling class info" ); - - pri_t* clparms = cast(pri_t*)&pcParms.pc_clparms; - pri_t* clinfo = cast(pri_t*)&pcInfo.pc_clinfo; - - result.PRIORITY_MAX = clparms[0]; - - if (pcInfo.pc_clname == "RT") - { - m_isRTClass = true; - - // For RT class, just assume it can't be changed - result.PRIORITY_MIN = clparms[0]; - result.PRIORITY_DEFAULT = clparms[0]; - } - else - { - m_isRTClass = false; - - // For all other scheduling classes, there are - // two key values -- uprilim and maxupri. - // maxupri is the maximum possible priority defined - // for the scheduling class, and valid priorities - // range are in [-maxupri, maxupri]. - // - // However, uprilim is an upper limit that the - // current thread can set for the current scheduling - // class, which can be less than maxupri. As such, - // use this value for priorityMax since this is - // the effective maximum. - - // maxupri - result.PRIORITY_MIN = -cast(int)(clinfo[0]); - // by definition - result.PRIORITY_DEFAULT = 0; - } - } - else - { - int policy; - sched_param param; - pthread_getschedparam( pthread_self(), &policy, ¶m ) == 0 - || assert(0, "Internal error in pthread_getschedparam"); - - result.PRIORITY_MIN = sched_get_priority_min( policy ); - result.PRIORITY_MIN != -1 - || assert(0, "Internal error in sched_get_priority_min"); - result.PRIORITY_DEFAULT = param.sched_priority; - result.PRIORITY_MAX = sched_get_priority_max( policy ); - result.PRIORITY_MAX != -1 || - assert(0, "Internal error in sched_get_priority_max"); - } - return result; - } - - @property static int PRIORITY_MIN() @nogc nothrow pure @trusted - { - return (cast(int function() @nogc nothrow pure @safe) - &loadGlobal!"PRIORITY_MIN")(); - } - - @property static const(int) PRIORITY_MAX() @nogc nothrow pure @trusted - { - return (cast(int function() @nogc nothrow pure @safe) - &loadGlobal!"PRIORITY_MAX")(); - } - - @property static int PRIORITY_DEFAULT() @nogc nothrow pure @trusted - { - return (cast(int function() @nogc nothrow pure @safe) - &loadGlobal!"PRIORITY_DEFAULT")(); - } - } - else - static assert(0, "unsupported OS"); - - - version (NetBSD) - { - //NetBSD does not support priority for default policy - // and it is not possible change policy without root access - int fakePriority = int.max; - } final @property int priority() { From 0720cd209feceb016900314fd950c40e34582398 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 13:01:44 +0300 Subject: [PATCH 21/32] windows_impl: some simple Windows version branches removed - 4 --- druntime/src/core/thread/windows_impl.d | 98 +------------------------ 1 file changed, 3 insertions(+), 95 deletions(-) diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index 0102640d705f..1eafb34bc408 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -212,29 +212,7 @@ class Thread : ThreadBase final @property int priority() { - version (Windows) - { - return GetThreadPriority( m_hndl ); - } - else version (NetBSD) - { - return fakePriority==int.max? PRIORITY_DEFAULT : fakePriority; - } - else version (Posix) - { - int policy; - sched_param param; - - if (auto err = pthread_getschedparam(m_addr, &policy, ¶m)) - { - // ignore error if thread is not running => Bugzilla 8960 - if (!atomicLoad(m_isRunning)) return PRIORITY_DEFAULT; - throw new ThreadException("Unable to get thread priority"); - } - return param.sched_priority; - } - else - static assert(0, "unsupported os"); + return GetThreadPriority( m_hndl ); } final @property void priority( int val ) @@ -245,78 +223,8 @@ class Thread : ThreadBase } do { - version (Windows) - { - if ( !SetThreadPriority( m_hndl, val ) ) - throw new ThreadException( "Unable to set thread priority" ); - } - else version (Solaris) - { - // the pthread_setschedprio(3c) and pthread_setschedparam functions - // are broken for the default (TS / time sharing) scheduling class. - // instead, we use priocntl(2) which gives us the desired behavior. - - // We hardcode the min and max priorities to the current value - // so this is a no-op for RT threads. - if (m_isRTClass) - return; - - pcparms_t pcparm; - - pcparm.pc_cid = PC_CLNULL; - if (priocntl(idtype_t.P_LWPID, P_MYID, PC_GETPARMS, &pcparm) == -1) - throw new ThreadException( "Unable to get scheduling class" ); - - pri_t* clparms = cast(pri_t*)&pcparm.pc_clparms; - - // clparms is filled in by the PC_GETPARMS call, only necessary - // to adjust the element that contains the thread priority - clparms[1] = cast(pri_t) val; - - if (priocntl(idtype_t.P_LWPID, P_MYID, PC_SETPARMS, &pcparm) == -1) - throw new ThreadException( "Unable to set scheduling class" ); - } - else version (NetBSD) - { - fakePriority = val; - } - else version (Posix) - { - static if (__traits(compiles, core.sys.posix.pthread.pthread_setschedprio)) - { - import core.sys.posix.pthread : pthread_setschedprio; - - if (auto err = pthread_setschedprio(m_addr, val)) - { - // ignore error if thread is not running => Bugzilla 8960 - if (!atomicLoad(m_isRunning)) return; - throw new ThreadException("Unable to set thread priority"); - } - } - else - { - // NOTE: pthread_setschedprio is not implemented on Darwin, FreeBSD, OpenBSD, - // or DragonFlyBSD, so use the more complicated get/set sequence below. - int policy; - sched_param param; - - if (auto err = pthread_getschedparam(m_addr, &policy, ¶m)) - { - // ignore error if thread is not running => Bugzilla 8960 - if (!atomicLoad(m_isRunning)) return; - throw new ThreadException("Unable to set thread priority"); - } - param.sched_priority = val; - if (auto err = pthread_setschedparam(m_addr, policy, ¶m)) - { - // ignore error if thread is not running => Bugzilla 8960 - if (!atomicLoad(m_isRunning)) return; - throw new ThreadException("Unable to set thread priority"); - } - } - } - else - static assert(0, "unsupported os"); + if ( !SetThreadPriority( m_hndl, val ) ) + throw new ThreadException( "Unable to set thread priority" ); } From 27398d5e831aef78568914e6a803f34dc09e9776 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 13:02:13 +0300 Subject: [PATCH 22/32] windows_impl: unittests removed --- druntime/src/core/thread/windows_impl.d | 27 ------------------------- 1 file changed, 27 deletions(-) diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index 1eafb34bc408..5e53b62dd96c 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -227,33 +227,6 @@ class Thread : ThreadBase throw new ThreadException( "Unable to set thread priority" ); } - - unittest - { - auto thr = Thread.getThis(); - immutable prio = thr.priority; - scope (exit) thr.priority = prio; - - assert(prio == PRIORITY_DEFAULT); - assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); - thr.priority = PRIORITY_MIN; - assert(thr.priority == PRIORITY_MIN); - thr.priority = PRIORITY_MAX; - assert(thr.priority == PRIORITY_MAX); - } - - unittest // Bugzilla 8960 - { - import core.sync.semaphore; - - auto thr = new Thread({}); - thr.start(); - Thread.sleep(1.msecs); // wait a little so the thread likely has finished - thr.priority = PRIORITY_MAX; // setting priority doesn't cause error - auto prio = thr.priority; // getting priority doesn't cause error - assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); - } - override final @property bool isRunning() nothrow @nogc { if (!super.isRunning()) From 9b381814819ed877f5980f066e7b41169799e2f8 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 14:06:10 +0300 Subject: [PATCH 23/32] windows_impl: code after Thread class definition removed --- druntime/src/core/thread/windows_impl.d | 2121 ----------------------- 1 file changed, 2121 deletions(-) diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index 5e53b62dd96c..ba297ff7074f 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -307,2124 +307,3 @@ class Thread : ThreadBase static assert(0, "unsupported os"); } } - -private Thread toThread(return scope ThreadBase t) @trusted nothrow @nogc pure -{ - return cast(Thread) cast(void*) t; -} - -private extern(D) static void thread_yield() @nogc nothrow -{ - Thread.yield(); -} - -/// -unittest -{ - class DerivedThread : Thread - { - this() - { - super(&run); - } - - private: - void run() - { - // Derived thread running. - } - } - - void threadFunc() - { - // Composed thread running. - } - - // create and start instances of each type - auto derived = new DerivedThread().start(); - auto composed = new Thread(&threadFunc).start(); - new Thread({ - // Codes to run in the newly created thread. - }).start(); -} - -unittest -{ - int x = 0; - - new Thread( - { - x++; - }).start().join(); - assert( x == 1 ); -} - - -unittest -{ - enum MSG = "Test message."; - string caughtMsg; - - try - { - new Thread( - function() - { - throw new Exception( MSG ); - }).start().join(); - assert( false, "Expected rethrown exception." ); - } - catch ( Throwable t ) - { - assert( t.msg == MSG ); - } -} - - -unittest -{ - // use >pageSize to avoid stack overflow (e.g. in an syscall) - auto thr = new Thread(function{}, 4096 + 1).start(); - thr.join(); -} - - -unittest -{ - import core.memory : GC; - - auto t1 = new Thread({ - foreach (_; 0 .. 20) - ThreadBase.getAll; - }).start; - auto t2 = new Thread({ - foreach (_; 0 .. 20) - GC.collect; - }).start; - t1.join(); - t2.join(); -} - -unittest -{ - import core.sync.semaphore; - auto sem = new Semaphore(); - - auto t = new Thread( - { - sem.notify(); - Thread.sleep(100.msecs); - }).start(); - - sem.wait(); // thread cannot be detached while being started - thread_detachInstance(t); - foreach (t2; Thread) - assert(t !is t2); - t.join(); -} - -// https://issues.dlang.org/show_bug.cgi?id=22124 -unittest -{ - Thread thread = new Thread({}); - auto fun(Thread t, int x) - { - t.__ctor({x = 3;}); - return t; - } - static assert(!__traits(compiles, () @nogc => fun(thread, 3) )); -} - -@nogc @safe nothrow -unittest -{ - Thread.sleep(1.msecs); -} - -/////////////////////////////////////////////////////////////////////////////// -// GC Support Routines -/////////////////////////////////////////////////////////////////////////////// - -version (CoreDdoc) -{ - /** - * Instruct the thread module, when initialized, to use a different set of - * signals besides SIGRTMIN and SIGRTMIN + 1 for suspension and resumption of threads. - * This function should be called at most once, prior to thread_init(). - * This function is Posix-only. - */ - extern (C) void thread_setGCSignals(int suspendSignalNo, int resumeSignalNo) nothrow @nogc - { - } - - /** - * Get the GC signals set by the thread module. This function should be called either - * after thread_init() has finished, or after a call thread_setGCSignals(). - * This function is Posix-only. - */ - extern (C) void thread_getGCSignals(out int suspendSignalNo, out int resumeSignalNo) nothrow @nogc - { - } -} -else version (Posix) -{ - extern (C) void thread_setGCSignals(int suspendSignalNo, int resumeSignalNo) nothrow @nogc - in - { - assert(suspendSignalNo != 0); - assert(resumeSignalNo != 0); - } - out - { - assert(suspendSignalNumber != 0); - assert(resumeSignalNumber != 0); - } - do - { - suspendSignalNumber = suspendSignalNo; - resumeSignalNumber = resumeSignalNo; - } - - extern (C) void thread_getGCSignals(out int suspendSignalNo, out int resumeSignalNo) nothrow @nogc - in - { - assert(suspendSignalNumber != 0); - assert(resumeSignalNumber != 0); - } - out - { - assert(suspendSignalNo != 0); - assert(resumeSignalNo != 0); - } - do - { - suspendSignalNo = suspendSignalNumber; - resumeSignalNo = resumeSignalNumber; - } -} - -version (Posix) -{ - private __gshared int suspendSignalNumber; - private __gshared int resumeSignalNumber; -} - -version (CoreDdoc) {} else -private extern (D) ThreadBase attachThread(ThreadBase _thisThread) @nogc nothrow -{ - Thread thisThread = _thisThread.toThread(); - - StackContext* thisContext = &thisThread.m_main; - assert( thisContext == thisThread.m_curr ); - - version (Windows) - { - thisThread.m_addr = GetCurrentThreadId(); - thisThread.m_hndl = GetCurrentThreadHandle(); - thisContext.bstack = getStackBottom(); - thisContext.tstack = thisContext.bstack; - } - else version (Posix) - { - thisThread.m_addr = pthread_self(); - thisContext.bstack = getStackBottom(); - thisContext.tstack = thisContext.bstack; - - atomicStore!(MemoryOrder.raw)(thisThread.toThread.m_isRunning, true); - } - else - static assert(0, "unsupported os"); - thisThread.m_isDaemon = true; - thisThread.tlsRTdataInit(); - Thread.setThis( thisThread ); - - version (Darwin) - { - thisThread.m_tmach = pthread_mach_thread_np( thisThread.m_addr ); - assert( thisThread.m_tmach != thisThread.m_tmach.init ); - } - - Thread.add( thisThread, false ); - Thread.add( thisContext ); - if ( Thread.sm_main !is null ) - multiThreadedFlag = true; - return thisThread; -} - -/** - * Registers the calling thread for use with the D Runtime. If this routine - * is called for a thread which is already registered, no action is performed. - * - * NOTE: This routine does not run thread-local static constructors when called. - * If full functionality as a D thread is desired, the following function - * must be called after thread_attachThis: - * - * extern (C) void rt_moduleTlsCtor(); - * - * See_Also: - * $(REF thread_detachThis, core,thread,threadbase) - */ -extern(C) Thread thread_attachThis() -{ - return thread_attachThis_tpl!Thread(); -} - - -version (Windows) -{ - // NOTE: These calls are not safe on Posix systems that use signals to - // perform garbage collection. The suspendHandler uses getThis() - // to get the thread handle so getThis() must be a simple call. - // Mutexes can't safely be acquired inside signal handlers, and - // even if they could, the mutex needed (Thread.slock) is held by - // thread_suspendAll(). So in short, these routines will remain - // Windows-specific. If they are truly needed elsewhere, the - // suspendHandler will need a way to call a version of getThis() - // that only does the TLS lookup without the fancy fallback stuff. - - /// ditto - extern (C) Thread thread_attachByAddr( ThreadID addr ) - { - return thread_attachByAddrB( addr, getThreadStackBottom( addr ) ); - } - - - /// ditto - extern (C) Thread thread_attachByAddrB( ThreadID addr, void* bstack ) - { - GC.disable(); scope(exit) GC.enable(); - - if (auto t = thread_findByAddr(addr).toThread) - return t; - - Thread thisThread = new Thread(); - StackContext* thisContext = &thisThread.m_main; - assert( thisContext == thisThread.m_curr ); - - thisThread.m_addr = addr; - thisContext.bstack = bstack; - thisContext.tstack = thisContext.bstack; - - thisThread.m_isDaemon = true; - - if ( addr == GetCurrentThreadId() ) - { - thisThread.m_hndl = GetCurrentThreadHandle(); - thisThread.tlsRTdataInit(); - Thread.setThis( thisThread ); - } - else - { - thisThread.m_hndl = OpenThreadHandle( addr ); - impersonate_thread(addr, - { - thisThread.tlsRTdataInit(); - Thread.setThis( thisThread ); - }); - } - - Thread.add( thisThread, false ); - Thread.add( thisContext ); - if ( Thread.sm_main !is null ) - multiThreadedFlag = true; - return thisThread; - } -} - - -// Calls the given delegate, passing the current thread's stack pointer to it. -package extern(D) void callWithStackShell(scope callWithStackShellDg fn) nothrow -in (fn) -{ - // The purpose of the 'shell' is to ensure all the registers get - // put on the stack so they'll be scanned. We only need to push - // the callee-save registers. - void *sp = void; - version (GNU) - { - // The generic solution below using a call to __builtin_unwind_init () - // followed by an assignment to sp has two issues: - // 1) On some archs it stores a huge amount of FP and Vector state which - // is not the subject of the scan - and, indeed might produce false - // hits. - // 2) Even on archs like X86, where there are no callee-saved FPRs/VRs there - // tend to be 'holes' in the frame allocations (to deal with alignment) which - // also will contain random data which could produce false positives. - // This solution stores only the integer callee-saved registers. - version (X86) - { - void*[3] regs = void; - asm pure nothrow @nogc - { - "movl %%ebx, %0" : "=m" (regs[0]); - "movl %%esi, %0" : "=m" (regs[1]); - "movl %%edi, %0" : "=m" (regs[2]); - } - sp = cast(void*)®s[0]; - } - else version (X86_64) - { - void*[5] regs = void; - asm pure nothrow @nogc - { - "movq %%rbx, %0" : "=m" (regs[0]); - "movq %%r12, %0" : "=m" (regs[1]); - "movq %%r13, %0" : "=m" (regs[2]); - "movq %%r14, %0" : "=m" (regs[3]); - "movq %%r15, %0" : "=m" (regs[4]); - } - sp = cast(void*)®s[0]; - } - else version (PPC) - { - void*[19] regs = void; - version (Darwin) - enum regname = "r"; - else - enum regname = ""; - static foreach (i; 0 .. regs.length) - {{ - enum int j = 13 + i; // source register - asm pure nothrow @nogc - { - ("stw "~regname~j.stringof~", %0") : "=m" (regs[i]); - } - }} - sp = cast(void*)®s[0]; - } - else version (PPC64) - { - void*[19] regs = void; - version (Darwin) - enum regname = "r"; - else - enum regname = ""; - static foreach (i; 0 .. regs.length) - {{ - enum int j = 13 + i; // source register - asm pure nothrow @nogc - { - ("std "~regname~j.stringof~", %0") : "=m" (regs[i]); - } - }} - sp = cast(void*)®s[0]; - } - else version (AArch64) - { - // Callee-save registers, x19-x28 according to AAPCS64, section - // 5.1.1. Include x29 fp because it optionally can be a callee - // saved reg - size_t[11] regs = void; - // store the registers in pairs - asm pure nothrow @nogc - { - "stp x19, x20, %0" : "=m" (regs[ 0]), "=m" (regs[1]); - "stp x21, x22, %0" : "=m" (regs[ 2]), "=m" (regs[3]); - "stp x23, x24, %0" : "=m" (regs[ 4]), "=m" (regs[5]); - "stp x25, x26, %0" : "=m" (regs[ 6]), "=m" (regs[7]); - "stp x27, x28, %0" : "=m" (regs[ 8]), "=m" (regs[9]); - "str x29, %0" : "=m" (regs[10]); - "mov %0, sp" : "=r" (sp); - } - } - else version (ARM) - { - // Callee-save registers, according to AAPCS, section 5.1.1. - // arm and thumb2 instructions - size_t[8] regs = void; - asm pure nothrow @nogc - { - "stm %0, {r4-r11}" : : "r" (regs.ptr) : "memory"; - "mov %0, sp" : "=r" (sp); - } - } - else - { - __builtin_unwind_init(); - sp = &sp; - } - } - else version (AsmX86_Posix) - { - size_t[3] regs = void; - asm pure nothrow @nogc - { - mov [regs + 0 * 4], EBX; - mov [regs + 1 * 4], ESI; - mov [regs + 2 * 4], EDI; - - mov sp[EBP], ESP; - } - } - else version (AsmX86_Windows) - { - size_t[3] regs = void; - asm pure nothrow @nogc - { - mov [regs + 0 * 4], EBX; - mov [regs + 1 * 4], ESI; - mov [regs + 2 * 4], EDI; - - mov sp[EBP], ESP; - } - } - else version (AsmX86_64_Posix) - { - size_t[5] regs = void; - asm pure nothrow @nogc - { - mov [regs + 0 * 8], RBX; - mov [regs + 1 * 8], R12; - mov [regs + 2 * 8], R13; - mov [regs + 3 * 8], R14; - mov [regs + 4 * 8], R15; - - mov sp[RBP], RSP; - } - } - else version (AsmX86_64_Windows) - { - size_t[7] regs = void; - asm pure nothrow @nogc - { - mov [regs + 0 * 8], RBX; - mov [regs + 1 * 8], RSI; - mov [regs + 2 * 8], RDI; - mov [regs + 3 * 8], R12; - mov [regs + 4 * 8], R13; - mov [regs + 5 * 8], R14; - mov [regs + 6 * 8], R15; - - mov sp[RBP], RSP; - } - } - else version (AArch64) - { - // Callee-save registers, x19-x28 according to AAPCS64, section - // 5.1.1. Include x29 fp because it optionally can be a callee - // saved reg - size_t[11] regs = void; - // store the registers in pairs - asm pure nothrow @nogc - { - /* - stp x19, x20, regs[0]; - stp x21, x22, regs[2]; - stp x23, x24, regs[4]; - stp x25, x26, regs[6]; - stp x27, x28, regs[8]; - str x29, regs[10]; - mov [sp], sp; - */ - } - assert(0, "implement AArch64 inline assembler for callWithStackShell()"); // TODO AArch64 - } - else - { - static assert(false, "Architecture not supported."); - } - - fn(sp); -} - -/** - * Returns the process ID of the calling process, which is guaranteed to be - * unique on the system. This call is always successful. - * - * Example: - * --- - * writefln("Current process id: %s", getpid()); - * --- - */ -version (Posix) -{ - alias getpid = imported!"core.sys.posix.unistd".getpid; -} -else version (Windows) -{ - alias getpid = imported!"core.sys.windows.winbase".GetCurrentProcessId; -} -else - static assert(0, "unsupported os"); - -extern (C) @nogc nothrow -{ - version (CRuntime_Glibc) version = PThread_Getattr_NP; - version (CRuntime_Bionic) version = PThread_Getattr_NP; - version (CRuntime_Musl) version = PThread_Getattr_NP; - version (CRuntime_UClibc) version = PThread_Getattr_NP; - - version (FreeBSD) version = PThread_Attr_Get_NP; - version (NetBSD) version = PThread_Attr_Get_NP; - version (DragonFlyBSD) version = PThread_Attr_Get_NP; - - version (PThread_Getattr_NP) int pthread_getattr_np(pthread_t thread, pthread_attr_t* attr); - version (PThread_Attr_Get_NP) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr); - version (OpenBSD) int pthread_stackseg_np(pthread_t thread, stack_t* sinfo); -} - - -private extern(D) void* getStackTop() nothrow @nogc -{ - version (D_InlineAsm_X86) - asm pure nothrow @nogc { naked; mov EAX, ESP; ret; } - else version (D_InlineAsm_X86_64) - asm pure nothrow @nogc { naked; mov RAX, RSP; ret; } - else version (AArch64) - //asm pure nothrow @nogc { naked; mov x0, SP; ret; } // TODO AArch64 - { - return null; - } - else version (GNU) - return __builtin_frame_address(0); - else - static assert(false, "Architecture not supported."); -} - - -private extern(D) void* getStackBottom() nothrow @nogc -{ - version (Windows) - { - version (D_InlineAsm_X86) - asm pure nothrow @nogc { naked; mov EAX, FS:4; ret; } - else version (D_InlineAsm_X86_64) - asm pure nothrow @nogc - { naked; - mov RAX, 8; - mov RAX, GS:[RAX]; - ret; - } - else version (GNU_InlineAsm) - { - void *bottom; - - version (X86) - asm pure nothrow @nogc { "movl %%fs:4, %0;" : "=r" (bottom); } - else version (X86_64) - asm pure nothrow @nogc { "movq %%gs:8, %0;" : "=r" (bottom); } - else - static assert(false, "Architecture not supported."); - - return bottom; - } - else - static assert(false, "Architecture not supported."); - } - else version (Darwin) - { - import core.sys.darwin.pthread : pthread_get_stackaddr_np; - return pthread_get_stackaddr_np(pthread_self()); - } - else version (PThread_Getattr_NP) - { - pthread_attr_t attr; - void* addr; size_t size; - - pthread_attr_init(&attr); - pthread_getattr_np(pthread_self(), &attr); - pthread_attr_getstack(&attr, &addr, &size); - pthread_attr_destroy(&attr); - static if (isStackGrowingDown) - addr += size; - return addr; - } - else version (PThread_Attr_Get_NP) - { - pthread_attr_t attr; - void* addr; size_t size; - - pthread_attr_init(&attr); - pthread_attr_get_np(pthread_self(), &attr); - pthread_attr_getstack(&attr, &addr, &size); - pthread_attr_destroy(&attr); - static if (isStackGrowingDown) - addr += size; - return addr; - } - else version (OpenBSD) - { - stack_t stk; - - pthread_stackseg_np(pthread_self(), &stk); - return stk.ss_sp; - } - else version (Solaris) - { - stack_t stk; - - thr_stksegment(&stk); - return stk.ss_sp; - } - else - static assert(false, "Platform not supported."); -} - -/** - * Suspend the specified thread and load stack and register information for - * use by thread_scanAll. If the supplied thread is the calling thread, - * stack and register information will be loaded but the thread will not - * be suspended. If the suspend operation fails and the thread is not - * running then it will be removed from the global thread list, otherwise - * an exception will be thrown. - * - * Params: - * t = The thread to suspend. - * - * Throws: - * ThreadError if the suspend operation fails for a running thread. - * Returns: - * Whether the thread is now suspended (true) or terminated (false). - */ -private extern (D) bool suspend( Thread t ) nothrow @nogc -{ - if (!t.isRunning) - { - Thread.remove(t); - return false; - } - - version (Windows) - { - if ( t.m_addr != GetCurrentThreadId() && SuspendThread( t.m_hndl ) == 0xFFFFFFFF ) - { - if ( !t.isRunning ) - { - Thread.remove( t ); - return false; - } - onThreadError( "Unable to suspend thread" ); - } - - CONTEXT context = void; - context.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL; - - if ( !GetThreadContext( t.m_hndl, &context ) ) - onThreadError( "Unable to load thread context" ); - version (X86) - { - if ( !t.m_lock ) - t.m_curr.tstack = cast(void*) context.Esp; - // eax,ebx,ecx,edx,edi,esi,ebp,esp - t.m_reg[0] = context.Eax; - t.m_reg[1] = context.Ebx; - t.m_reg[2] = context.Ecx; - t.m_reg[3] = context.Edx; - t.m_reg[4] = context.Edi; - t.m_reg[5] = context.Esi; - t.m_reg[6] = context.Ebp; - t.m_reg[7] = context.Esp; - } - else version (X86_64) - { - if ( !t.m_lock ) - t.m_curr.tstack = cast(void*) context.Rsp; - // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp - t.m_reg[0] = context.Rax; - t.m_reg[1] = context.Rbx; - t.m_reg[2] = context.Rcx; - t.m_reg[3] = context.Rdx; - t.m_reg[4] = context.Rdi; - t.m_reg[5] = context.Rsi; - t.m_reg[6] = context.Rbp; - t.m_reg[7] = context.Rsp; - // r8,r9,r10,r11,r12,r13,r14,r15 - t.m_reg[8] = context.R8; - t.m_reg[9] = context.R9; - t.m_reg[10] = context.R10; - t.m_reg[11] = context.R11; - t.m_reg[12] = context.R12; - t.m_reg[13] = context.R13; - t.m_reg[14] = context.R14; - t.m_reg[15] = context.R15; - } - else - { - static assert(false, "Architecture not supported." ); - } - } - else version (Darwin) - { - if ( t.m_addr != pthread_self() && thread_suspend( t.m_tmach ) != KERN_SUCCESS ) - { - if ( !t.isRunning ) - { - Thread.remove( t ); - return false; - } - onThreadError( "Unable to suspend thread" ); - } - - version (X86) - { - x86_thread_state32_t state = void; - mach_msg_type_number_t count = x86_THREAD_STATE32_COUNT; - - if ( thread_get_state( t.m_tmach, x86_THREAD_STATE32, &state, &count ) != KERN_SUCCESS ) - onThreadError( "Unable to load thread state" ); - if ( !t.m_lock ) - t.m_curr.tstack = cast(void*) state.esp; - // eax,ebx,ecx,edx,edi,esi,ebp,esp - t.m_reg[0] = state.eax; - t.m_reg[1] = state.ebx; - t.m_reg[2] = state.ecx; - t.m_reg[3] = state.edx; - t.m_reg[4] = state.edi; - t.m_reg[5] = state.esi; - t.m_reg[6] = state.ebp; - t.m_reg[7] = state.esp; - } - else version (X86_64) - { - x86_thread_state64_t state = void; - mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; - - if ( thread_get_state( t.m_tmach, x86_THREAD_STATE64, &state, &count ) != KERN_SUCCESS ) - onThreadError( "Unable to load thread state" ); - if ( !t.m_lock ) - t.m_curr.tstack = cast(void*) state.rsp; - // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp - t.m_reg[0] = state.rax; - t.m_reg[1] = state.rbx; - t.m_reg[2] = state.rcx; - t.m_reg[3] = state.rdx; - t.m_reg[4] = state.rdi; - t.m_reg[5] = state.rsi; - t.m_reg[6] = state.rbp; - t.m_reg[7] = state.rsp; - // r8,r9,r10,r11,r12,r13,r14,r15 - t.m_reg[8] = state.r8; - t.m_reg[9] = state.r9; - t.m_reg[10] = state.r10; - t.m_reg[11] = state.r11; - t.m_reg[12] = state.r12; - t.m_reg[13] = state.r13; - t.m_reg[14] = state.r14; - t.m_reg[15] = state.r15; - } - else version (AArch64) - { - arm_thread_state64_t state = void; - mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT; - - if (thread_get_state(t.m_tmach, ARM_THREAD_STATE64, &state, &count) != KERN_SUCCESS) - onThreadError("Unable to load thread state"); - // TODO: ThreadException here recurses forever! Does it - //still using onThreadError? - //printf("state count %d (expect %d)\n", count ,ARM_THREAD_STATE64_COUNT); - if (!t.m_lock) - t.m_curr.tstack = cast(void*) state.sp; - - t.m_reg[0..29] = state.x; // x0-x28 - t.m_reg[29] = state.fp; // x29 - t.m_reg[30] = state.lr; // x30 - t.m_reg[31] = state.sp; // x31 - t.m_reg[32] = state.pc; - } - else version (ARM) - { - arm_thread_state32_t state = void; - mach_msg_type_number_t count = ARM_THREAD_STATE32_COUNT; - - // Thought this would be ARM_THREAD_STATE32, but that fails. - // Mystery - if (thread_get_state(t.m_tmach, ARM_THREAD_STATE, &state, &count) != KERN_SUCCESS) - onThreadError("Unable to load thread state"); - // TODO: in past, ThreadException here recurses forever! Does it - //still using onThreadError? - //printf("state count %d (expect %d)\n", count ,ARM_THREAD_STATE32_COUNT); - if (!t.m_lock) - t.m_curr.tstack = cast(void*) state.sp; - - t.m_reg[0..13] = state.r; // r0 - r13 - t.m_reg[13] = state.sp; - t.m_reg[14] = state.lr; - t.m_reg[15] = state.pc; - } - else version (PPC) - { - ppc_thread_state_t state = void; - mach_msg_type_number_t count = PPC_THREAD_STATE_COUNT; - - if (thread_get_state(t.m_tmach, PPC_THREAD_STATE, &state, &count) != KERN_SUCCESS) - onThreadError("Unable to load thread state"); - if (!t.m_lock) - t.m_curr.tstack = cast(void*) state.r[1]; - t.m_reg[] = state.r[]; - } - else version (PPC64) - { - ppc_thread_state64_t state = void; - mach_msg_type_number_t count = PPC_THREAD_STATE64_COUNT; - - if (thread_get_state(t.m_tmach, PPC_THREAD_STATE64, &state, &count) != KERN_SUCCESS) - onThreadError("Unable to load thread state"); - if (!t.m_lock) - t.m_curr.tstack = cast(void*) state.r[1]; - t.m_reg[] = state.r[]; - } - else - { - static assert(false, "Architecture not supported." ); - } - } - else version (Solaris) - { - if (t.m_addr != pthread_self()) - { - if (thr_suspend(t.m_addr) != 0) - { - if (!t.isRunning) - { - Thread.remove(t); - return false; - } - onThreadError("Unable to suspend thread"); - } - - static int getLwpStatus(ulong lwpid, out lwpstatus_t status) - { - import core.sys.posix.fcntl : open, O_RDONLY; - import core.sys.posix.unistd : pread, close; - import core.internal.string : unsignedToTempString; - - char[100] path = void; - auto pslice = path[0 .. $]; - immutable n = unsignedToTempString(lwpid); - immutable ndigits = n.length; - - // Construct path "/proc/self/lwp/%u/lwpstatus" - pslice[0 .. 15] = "/proc/self/lwp/"; - pslice = pslice[15 .. $]; - pslice[0 .. ndigits] = n[]; - pslice = pslice[ndigits .. $]; - pslice[0 .. 10] = "/lwpstatus"; - pslice[10] = '\0'; - - // Read in lwpstatus data - int fd = open(path.ptr, O_RDONLY, 0); - if (fd >= 0) - { - while (pread(fd, &status, status.sizeof, 0) == status.sizeof) - { - // Should only attempt to read the thread state once it - // has been stopped by thr_suspend - if (status.pr_flags & PR_STOPPED) - { - close(fd); - return 0; - } - // Give it a chance to stop - thread_yield(); - } - close(fd); - } - return -1; - } - - lwpstatus_t status = void; - if (getLwpStatus(t.m_addr, status) != 0) - onThreadError("Unable to load thread state"); - - version (X86) - { - import core.sys.solaris.sys.regset; // REG_xxx - - if (!t.m_lock) - t.m_curr.tstack = cast(void*) status.pr_reg[REG_ESP]; - // eax,ebx,ecx,edx,edi,esi,ebp,esp - t.m_reg[0] = status.pr_reg[REG_EAX]; - t.m_reg[1] = status.pr_reg[REG_EBX]; - t.m_reg[2] = status.pr_reg[REG_ECX]; - t.m_reg[3] = status.pr_reg[REG_EDX]; - t.m_reg[4] = status.pr_reg[REG_EDI]; - t.m_reg[5] = status.pr_reg[REG_ESI]; - t.m_reg[6] = status.pr_reg[REG_EBP]; - t.m_reg[7] = status.pr_reg[REG_ESP]; - } - else version (X86_64) - { - import core.sys.solaris.sys.regset; // REG_xxx - - if (!t.m_lock) - t.m_curr.tstack = cast(void*) status.pr_reg[REG_RSP]; - // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp - t.m_reg[0] = status.pr_reg[REG_RAX]; - t.m_reg[1] = status.pr_reg[REG_RBX]; - t.m_reg[2] = status.pr_reg[REG_RCX]; - t.m_reg[3] = status.pr_reg[REG_RDX]; - t.m_reg[4] = status.pr_reg[REG_RDI]; - t.m_reg[5] = status.pr_reg[REG_RSI]; - t.m_reg[6] = status.pr_reg[REG_RBP]; - t.m_reg[7] = status.pr_reg[REG_RSP]; - // r8,r9,r10,r11,r12,r13,r14,r15 - t.m_reg[8] = status.pr_reg[REG_R8]; - t.m_reg[9] = status.pr_reg[REG_R9]; - t.m_reg[10] = status.pr_reg[REG_R10]; - t.m_reg[11] = status.pr_reg[REG_R11]; - t.m_reg[12] = status.pr_reg[REG_R12]; - t.m_reg[13] = status.pr_reg[REG_R13]; - t.m_reg[14] = status.pr_reg[REG_R14]; - t.m_reg[15] = status.pr_reg[REG_R15]; - } - else version (SPARC) - { - import core.sys.solaris.sys.procfs : R_SP, R_PC; - - if (!t.m_lock) - t.m_curr.tstack = cast(void*) status.pr_reg[R_SP]; - // g0..g7, o0..o7, l0..l7, i0..i7 - t.m_reg[0 .. 32] = status.pr_reg[0 .. 32]; - // pc - t.m_reg[32] = status.pr_reg[R_PC]; - } - else version (SPARC64) - { - import core.sys.solaris.sys.procfs : R_SP, R_PC; - - if (!t.m_lock) - { - // SPARC V9 has a stack bias of 2047 bytes which must be added to get - // the actual data of the stack frame. - auto tstack = status.pr_reg[R_SP] + 2047; - assert(tstack % 16 == 0); - t.m_curr.tstack = cast(void*) tstack; - } - // g0..g7, o0..o7, l0..l7, i0..i7 - t.m_reg[0 .. 32] = status.pr_reg[0 .. 32]; - // pc - t.m_reg[32] = status.pr_reg[R_PC]; - } - else - { - static assert(false, "Architecture not supported."); - } - } - else if (!t.m_lock) - { - t.m_curr.tstack = getStackTop(); - } - } - else version (Posix) - { - if ( t.m_addr != pthread_self() ) - { - if ( pthread_kill( t.m_addr, suspendSignalNumber ) != 0 ) - { - if ( !t.isRunning ) - { - Thread.remove( t ); - return false; - } - onThreadError( "Unable to suspend thread" ); - } - } - else if ( !t.m_lock ) - { - t.m_curr.tstack = getStackTop(); - } - } - else - static assert(0, "unsupported os"); - return true; -} - -/** - * Runs the necessary operations required before stopping the world. - */ -extern (C) void thread_preStopTheWorld() nothrow { - Thread.slock.lock_nothrow(); -} - -/** - * Suspend all threads but the calling thread for "stop the world" garbage - * collection runs. This function may be called multiple times, and must - * be followed by a matching number of calls to thread_resumeAll before - * processing is resumed. - * - * Throws: - * ThreadError if the suspend operation fails for a running thread. - */ -extern (C) void thread_suspendAll() nothrow -{ - // NOTE: We've got an odd chicken & egg problem here, because while the GC - // is required to call thread_init before calling any other thread - // routines, thread_init may allocate memory which could in turn - // trigger a collection. Thus, thread_suspendAll, thread_scanAll, - // and thread_resumeAll must be callable before thread_init - // completes, with the assumption that no other GC memory has yet - // been allocated by the system, and thus there is no risk of losing - // data if the global thread list is empty. The check of - // Thread.sm_tbeg below is done to ensure thread_init has completed, - // and therefore that calling Thread.getThis will not result in an - // error. For the short time when Thread.sm_tbeg is null, there is - // no reason not to simply call the multithreaded code below, with - // the expectation that the foreach loop will never be entered. - if ( !multiThreadedFlag && Thread.sm_tbeg ) - { - if ( ++suspendDepth == 1 ) - suspend( Thread.getThis() ); - - return; - } - - thread_preStopTheWorld(); - { - if ( ++suspendDepth > 1 ) - return; - - size_t cnt; - bool suspendedSelf; - Thread t = ThreadBase.sm_tbeg.toThread; - while (t) - { - auto tn = t.next.toThread; - if (suspend(t)) - { - if (t is ThreadBase.getThis()) - suspendedSelf = true; - ++cnt; - } - t = tn; - } - - version (Darwin) - {} - else version (Solaris) - {} - else version (Posix) - { - // Subtract own thread if we called suspend() on ourselves. - // For example, suspendedSelf would be false if the current - // thread ran thread_detachThis(). - assert(cnt >= 1); - if (suspendedSelf) - --cnt; - // wait for semaphore notifications - for (; cnt; --cnt) - { - while (sem_wait(&suspendCount) != 0) - { - if (errno != EINTR) - onThreadError("Unable to wait for semaphore"); - errno = 0; - } - } - } - else version (Windows) - { - } - else - static assert(0, "unsupported os"); - } -} - -/** - * Resume the specified thread and unload stack and register information. - * If the supplied thread is the calling thread, stack and register - * information will be unloaded but the thread will not be resumed. If - * the resume operation fails and the thread is not running then it will - * be removed from the global thread list, otherwise an exception will be - * thrown. - * - * Params: - * t = The thread to resume. - * - * Throws: - * ThreadError if the resume fails for a running thread. - */ -private extern (D) void resume(ThreadBase _t) nothrow @nogc -{ - Thread t = _t.toThread; - - version (Windows) - { - if ( t.m_addr != GetCurrentThreadId() && ResumeThread( t.m_hndl ) == 0xFFFFFFFF ) - { - if ( !t.isRunning ) - { - Thread.remove( t ); - return; - } - onThreadError( "Unable to resume thread" ); - } - - if ( !t.m_lock ) - t.m_curr.tstack = t.m_curr.bstack; - t.m_reg[0 .. $] = 0; - } - else version (Darwin) - { - if ( t.m_addr != pthread_self() && thread_resume( t.m_tmach ) != KERN_SUCCESS ) - { - if ( !t.isRunning ) - { - Thread.remove( t ); - return; - } - onThreadError( "Unable to resume thread" ); - } - - if ( !t.m_lock ) - t.m_curr.tstack = t.m_curr.bstack; - t.m_reg[0 .. $] = 0; - } - else version (Solaris) - { - if (t.m_addr != pthread_self() && thr_continue(t.m_addr) != 0) - { - if (!t.isRunning) - { - Thread.remove(t); - return; - } - onThreadError("Unable to resume thread"); - } - - if (!t.m_lock) - t.m_curr.tstack = t.m_curr.bstack; - t.m_reg[0 .. $] = 0; - } - else version (Posix) - { - if ( t.m_addr != pthread_self() ) - { - if ( pthread_kill( t.m_addr, resumeSignalNumber ) != 0 ) - { - if ( !t.isRunning ) - { - Thread.remove( t ); - return; - } - onThreadError( "Unable to resume thread" ); - } - } - else if ( !t.m_lock ) - { - t.m_curr.tstack = t.m_curr.bstack; - } - } - else - static assert(false, "Platform not supported."); -} - - -/** - * Initializes the thread module. This function must be called by the - * garbage collector on startup and before any other thread routines - * are called. - */ -version (CoreDdoc) - extern (C) void thread_init() @nogc nothrow {} -else -extern (C) void thread_init() @nogc nothrow -{ - // NOTE: If thread_init itself performs any allocations then the thread - // routines reserved for garbage collector use may be called while - // thread_init is being processed. However, since no memory should - // exist to be scanned at this point, it is sufficient for these - // functions to detect the condition and return immediately. - - initLowlevelThreads(); - Thread.initLocks(); - - version (Windows) - { - } - else version (Darwin) - { - // thread id different in forked child process - static extern(C) void initChildAfterFork() - { - auto thisThread = Thread.getThis(); - if (!thisThread) - { - // It is possible that runtime was not properly initialized in the current process or thread - - // it may happen after `fork` call when using a dynamically loaded shared library written in D from a multithreaded non-D program. - // In such case getThis will return null. - return; - } - thisThread.m_addr = pthread_self(); - assert( thisThread.m_addr != thisThread.m_addr.init ); - thisThread.m_tmach = pthread_mach_thread_np( thisThread.m_addr ); - assert( thisThread.m_tmach != thisThread.m_tmach.init ); - } - pthread_atfork(null, null, &initChildAfterFork); - } - else version (Solaris) - { - } - else version (Posix) - { - version (OpenBSD) - { - // OpenBSD does not support SIGRTMIN or SIGRTMAX - // Use SIGUSR1 for SIGRTMIN, SIGUSR2 for SIGRTMIN + 1 - // And use 32 for SIGRTMAX (32 is the max signal number on OpenBSD) - enum SIGRTMIN = SIGUSR1; - enum SIGRTMAX = 32; - } - else - { - import core.sys.posix.signal : SIGRTMAX, SIGRTMIN; - } - - if ( suspendSignalNumber == 0 ) - { - suspendSignalNumber = SIGRTMIN; - } - - if ( resumeSignalNumber == 0 ) - { - resumeSignalNumber = SIGRTMIN + 1; - assert(resumeSignalNumber <= SIGRTMAX); - } - int status; - sigaction_t suspend = void; - sigaction_t resume = void; - - // This is a quick way to zero-initialize the structs without using - // memset or creating a link dependency on their static initializer. - (cast(byte*) &suspend)[0 .. sigaction_t.sizeof] = 0; - (cast(byte*) &resume)[0 .. sigaction_t.sizeof] = 0; - - // NOTE: SA_RESTART indicates that system calls should restart if they - // are interrupted by a signal, but this is not available on all - // Posix systems, even those that support multithreading. - static if (__traits(compiles, core.sys.posix.signal.SA_RESTART)) - { - import core.sys.posix.signal : SA_RESTART; - - suspend.sa_flags = SA_RESTART; - } - - suspend.sa_handler = &thread_suspendHandler; - // NOTE: We want to ignore all signals while in this handler, so fill - // sa_mask to indicate this. - status = sigfillset( &suspend.sa_mask ); - assert( status == 0 ); - - // NOTE: Since resumeSignalNumber should only be issued for threads within the - // suspend handler, we don't want this signal to trigger a - // restart. - resume.sa_flags = 0; - resume.sa_handler = &thread_resumeHandler; - // NOTE: We want to ignore all signals while in this handler, so fill - // sa_mask to indicate this. - status = sigfillset( &resume.sa_mask ); - assert( status == 0 ); - - status = sigaction( suspendSignalNumber, &suspend, null ); - assert( status == 0 ); - - status = sigaction( resumeSignalNumber, &resume, null ); - assert( status == 0 ); - - status = sem_init( &suspendCount, 0, 0 ); - assert( status == 0 ); - } - else - static assert(0, "unsupported os"); - _mainThreadStore[] = cast(void[]) __traits(initSymbol, Thread)[]; - Thread.sm_main = attachThread((cast(Thread)_mainThreadStore.ptr).__ctor()); -} - -private alias MainThreadStore = void[__traits(classInstanceSize, Thread)]; -package __gshared align(__traits(classInstanceAlignment, Thread)) MainThreadStore _mainThreadStore; - -/** - * Terminates the thread module. No other thread routine may be called - * afterwards. - */ -extern (C) void thread_term() @nogc nothrow -{ - thread_term_tpl!(Thread)(_mainThreadStore); -} - - -/////////////////////////////////////////////////////////////////////////////// -// Thread Entry Point and Signal Handlers -/////////////////////////////////////////////////////////////////////////////// - - -version (Windows) -{ - private - { - // - // Entry point for Windows threads - // - extern (Windows) uint thread_entryPoint( void* arg ) nothrow - { - Thread obj = cast(Thread) arg; - assert( obj ); - - obj.initDataStorage(); - - Thread.registerThis(obj); - - scope (exit) - { - // allow the GC to clean up any resources it allocated for this thread. - import core.internal.gc.proxy : gc_getProxy; - gc_getProxy().cleanupThread(obj); - - Thread.remove(obj); - obj.destroyDataStorage(); - } - Thread.add(&obj.m_main); - - // NOTE: No GC allocations may occur until the stack pointers have - // been set and Thread.getThis returns a valid reference to - // this thread object (this latter condition is not strictly - // necessary on Windows but it should be followed for the - // sake of consistency). - - // TODO: Consider putting an auto exception object here (using - // alloca) forOutOfMemoryError plus something to track - // whether an exception is in-flight? - - void append( Throwable t ) - { - obj.filterCaughtThrowable(t); - if (t !is null) - obj.m_unhandled = Throwable.chainTogether(obj.m_unhandled, t); - } - - version (D_InlineAsm_X86) - { - asm nothrow @nogc { fninit; } - } - - try - { - rt_moduleTlsCtor(); - try - { - obj.run(); - } - catch ( Throwable t ) - { - append( t ); - } - rt_moduleTlsDtor(); - } - catch ( Throwable t ) - { - append( t ); - } - return 0; - } - - - HANDLE GetCurrentThreadHandle() nothrow @nogc - { - const uint DUPLICATE_SAME_ACCESS = 0x00000002; - - HANDLE curr = GetCurrentThread(), - proc = GetCurrentProcess(), - hndl; - - DuplicateHandle( proc, curr, proc, &hndl, 0, TRUE, DUPLICATE_SAME_ACCESS ); - return hndl; - } - } -} -else version (Posix) -{ - // NOTE: A thread's cancelability state, determined by pthread_setcancelstate, - // can be enabled (the default for new threads) or disabled. - // If a thread has disabled cancelation, then a cancelation request remains - // queued until the thread enables cancelation. If a thread has enabled - // cancelation, then its cancelability type determines when cancelation occurs. - // - // Call these routines when entering/leaving critical sections of the code that - // are not cancellation points. - - extern (C) int thread_cancelDisable() nothrow - { - static if (__traits(compiles, core.sys.posix.pthread.PTHREAD_CANCEL_DISABLE)) - { - import core.sys.posix.pthread : pthread_setcancelstate, PTHREAD_CANCEL_DISABLE; - int oldstate; - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); - return oldstate; - } - else - { - return 0; // No thread cancellation on platform - } - } - - extern (C) void thread_cancelRestore(int oldstate) nothrow - { - static if (__traits(compiles, core.sys.posix.pthread.PTHREAD_CANCEL_DISABLE)) - { - import core.sys.posix.pthread : pthread_setcancelstate; - pthread_setcancelstate(oldstate, null); - } - } - - private - { - // - // Entry point for POSIX threads - // - version (CoreDdoc) {} else - extern (C) void* thread_entryPoint( void* arg ) nothrow - { - version (Shared) - { - Thread obj = cast(Thread)(cast(void**)arg)[0]; - auto loadedLibraries = (cast(void**)arg)[1]; - .free(arg); - } - else - { - Thread obj = cast(Thread)arg; - } - assert( obj ); - - // loadedLibraries need to be inherited from parent thread - // before initilizing GC for TLS (rt_tlsgc_init) - version (Shared) - { - externDFunc!("rt.sections_elf_shared.inheritLoadedLibraries", - void function(void*) @nogc nothrow)(loadedLibraries); - } - - obj.initDataStorage(); - - atomicStore!(MemoryOrder.raw)(obj.m_isRunning, true); - - Thread.registerThis(obj); // can only receive signals from here on - - scope (exit) - { - // allow the GC to clean up any resources it allocated for this thread. - import core.internal.gc.proxy : gc_getProxy; - gc_getProxy().cleanupThread(obj); - - Thread.remove(obj); - atomicStore!(MemoryOrder.raw)(obj.m_isRunning, false); - obj.destroyDataStorage(); - } - Thread.add(&obj.m_main); - - static extern (C) void thread_cleanupHandler( void* arg ) nothrow @nogc - { - Thread obj = cast(Thread) arg; - assert( obj ); - - // NOTE: If the thread terminated abnormally, just set it as - // not running and let thread_suspendAll remove it from - // the thread list. This is safer and is consistent - // with the Windows thread code. - atomicStore!(MemoryOrder.raw)(obj.m_isRunning,false); - } - - // NOTE: Using void to skip the initialization here relies on - // knowledge of how pthread_cleanup is implemented. It may - // not be appropriate for all platforms. However, it does - // avoid the need to link the pthread module. If any - // implementation actually requires default initialization - // then pthread_cleanup should be restructured to maintain - // the current lack of a link dependency. - static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup)) - { - import core.sys.posix.pthread : pthread_cleanup; - - pthread_cleanup cleanup = void; - cleanup.push( &thread_cleanupHandler, cast(void*) obj ); - } - else static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup_push)) - { - import core.sys.posix.pthread : pthread_cleanup_push; - - pthread_cleanup_push(&thread_cleanupHandler, cast(void*) obj); - } - else - { - static assert( false, "Platform not supported." ); - } - - // NOTE: No GC allocations may occur until the stack pointers have - // been set and Thread.getThis returns a valid reference to - // this thread object (this latter condition is not strictly - // necessary on Windows but it should be followed for the - // sake of consistency). - - // TODO: Consider putting an auto exception object here (using - // alloca) forOutOfMemoryError plus something to track - // whether an exception is in-flight? - - void append( Throwable t ) - { - obj.filterCaughtThrowable(t); - if (t !is null) - obj.m_unhandled = Throwable.chainTogether(obj.m_unhandled, t); - } - try - { - rt_moduleTlsCtor(); - try - { - obj.run(); - } - catch ( Throwable t ) - { - append( t ); - } - rt_moduleTlsDtor(); - version (Shared) - { - externDFunc!("rt.sections_elf_shared.cleanupLoadedLibraries", - void function() @nogc nothrow)(); - } - } - catch ( Throwable t ) - { - append( t ); - } - - // NOTE: Normal cleanup is handled by scope(exit). - - static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup)) - { - cleanup.pop( 0 ); - } - else static if (__traits(compiles, core.sys.posix.pthread.pthread_cleanup_push)) - { - import core.sys.posix.pthread : pthread_cleanup_pop; - - pthread_cleanup_pop( 0 ); - } - - return null; - } - - - // - // Used to track the number of suspended threads - // - __gshared sem_t suspendCount; - - - extern (C) bool thread_preSuspend( void* sp ) nothrow { - // NOTE: Since registers are being pushed and popped from the - // stack, any other stack data used by this function should - // be gone before the stack cleanup code is called below. - Thread obj = Thread.getThis(); - if (obj is null) - { - return false; - } - - if ( !obj.m_lock ) - { - obj.m_curr.tstack = sp; - } - - return true; - } - - extern (C) bool thread_postSuspend() nothrow { - Thread obj = Thread.getThis(); - if (obj is null) - { - return false; - } - - if ( !obj.m_lock ) - { - obj.m_curr.tstack = obj.m_curr.bstack; - } - - return true; - } - - extern (C) void thread_suspendHandler( int sig ) nothrow - in - { - assert( sig == suspendSignalNumber ); - } - do - { - void op(void* sp) nothrow - { - int cancel_state = thread_cancelDisable(); - scope(exit) thread_cancelRestore(cancel_state); - - bool supported = thread_preSuspend(getStackTop()); - assert(supported, "Tried to suspend a detached thread!"); - - scope(exit) - { - supported = thread_postSuspend(); - assert(supported, "Tried to suspend a detached thread!"); - } - - sigset_t sigres = void; - int status; - - status = sigfillset( &sigres ); - assert( status == 0 ); - - status = sigdelset( &sigres, resumeSignalNumber ); - assert( status == 0 ); - - status = sem_post( &suspendCount ); - assert( status == 0 ); - - sigsuspend( &sigres ); - } - callWithStackShell(&op); - } - - - extern (C) void thread_resumeHandler( int sig ) nothrow - in - { - assert( sig == resumeSignalNumber ); - } - do - { - - } - } -} -else -{ - // NOTE: This is the only place threading versions are checked. If a new - // version is added, the module code will need to be searched for - // places where version-specific code may be required. This can be - // easily accomlished by searching for 'Windows' or 'Posix'. - static assert( false, "Unknown threading implementation." ); -} - -// -// exposed by compiler runtime -// -extern (C) void rt_moduleTlsCtor(); -extern (C) void rt_moduleTlsDtor(); - - -// regression test for Issue 13416 -version (FreeBSD) unittest -{ - static void loop() - { - pthread_attr_t attr; - pthread_attr_init(&attr); - auto thr = pthread_self(); - foreach (i; 0 .. 50) - pthread_attr_get_np(thr, &attr); - pthread_attr_destroy(&attr); - } - - auto thr = new Thread(&loop).start(); - foreach (i; 0 .. 50) - { - thread_suspendAll(); - thread_resumeAll(); - } - thr.join(); -} - -version (DragonFlyBSD) unittest -{ - static void loop() - { - pthread_attr_t attr; - pthread_attr_init(&attr); - auto thr = pthread_self(); - foreach (i; 0 .. 50) - pthread_attr_get_np(thr, &attr); - pthread_attr_destroy(&attr); - } - - auto thr = new Thread(&loop).start(); - foreach (i; 0 .. 50) - { - thread_suspendAll(); - thread_resumeAll(); - } - thr.join(); -} - - -/////////////////////////////////////////////////////////////////////////////// -// lowlovel threading support -/////////////////////////////////////////////////////////////////////////////// - -private -{ - version (Windows): - // If the runtime is dynamically loaded as a DLL, there is a problem with - // threads still running when the DLL is supposed to be unloaded: - // - // - with the VC runtime starting with VS2015 (i.e. using the Universal CRT) - // a thread created with _beginthreadex increments the DLL reference count - // and decrements it when done, so that the DLL is no longer unloaded unless - // all the threads have terminated. With the DLL reference count held up - // by a thread that is only stopped by a signal from a static destructor or - // the termination of the runtime will cause the DLL to never be unloaded. - // - // - with the DigitalMars runtime and VC runtime up to VS2013, the thread - // continues to run, but crashes once the DLL is unloaded from memory as - // the code memory is no longer accessible. Stopping the threads is not possible - // from within the runtime termination as it is invoked from - // DllMain(DLL_PROCESS_DETACH) holding a lock that prevents threads from - // terminating. - // - // Solution: start a watchdog thread that keeps the DLL reference count above 0 and - // checks it periodically. If it is equal to 1 (plus the number of started threads), no - // external references to the DLL exist anymore, threads can be stopped - // and runtime termination and DLL unload can be invoked via FreeLibraryAndExitThread. - // Note: runtime termination is then performed by a different thread than at startup. - // - // Note: if the DLL is never unloaded, process termination kills all threads - // and signals their handles before unconditionally calling DllMain(DLL_PROCESS_DETACH). - - import core.sys.windows.dll : dll_getRefCount; - import core.sys.windows.winbase : FreeLibraryAndExitThread, GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, GetModuleHandleExW; - import core.sys.windows.windef : HMODULE; - - version (CRuntime_Microsoft) - extern(C) extern __gshared ubyte msvcUsesUCRT; // from rt/msvc.d - extern(C) extern __gshared void* __ImageBase; // symbol at the beginning of module, added by linker - enum HMODULE runtimeModule = &__ImageBase; - - /// set during termination of a DLL on Windows, i.e. while executing DllMain(DLL_PROCESS_DETACH) - public __gshared bool thread_DLLProcessDetaching; - - __gshared ThreadID ll_dllMonitorThread; - - int ll_countLowLevelThreadsWithDLLUnloadCallback(HMODULE hMod) nothrow - { - lowlevelLock.lock_nothrow(); - scope(exit) lowlevelLock.unlock_nothrow(); - - int cnt = 0; - foreach (i; 0 .. ll_nThreads) - if (ll_pThreads[i].cbDllUnload && ll_pThreads[i].hMod == hMod) - cnt++; - return cnt; - } - - bool ll_dllHasExternalReferences(HMODULE hMod) nothrow - { - int unloadCallbacks = ll_countLowLevelThreadsWithDLLUnloadCallback(hMod); - int internalReferences = hMod != runtimeModule ? unloadCallbacks - : (ll_dllMonitorThread ? 1 : 0) + (msvcUsesUCRT ? unloadCallbacks : 0); - int refcnt = dll_getRefCount(hMod); - return refcnt > internalReferences; - } - - void notifyUnloadLowLevelThreads(HMODULE hMod) nothrow - { - HMODULE toFree; - for (;;) - { - ThreadID tid; - void delegate() nothrow cbDllUnload; - { - lowlevelLock.lock_nothrow(); - scope(exit) lowlevelLock.unlock_nothrow(); - - foreach (i; 0 .. ll_nThreads) - if (ll_pThreads[i].cbDllUnload && ll_pThreads[i].hMod == hMod) - { - if (!toFree) - toFree = ll_getModuleHandle(hMod, true); // keep the module alive until the callback returns - cbDllUnload = ll_pThreads[i].cbDllUnload; - tid = ll_pThreads[i].tid; - break; - } - } - if (!cbDllUnload) - break; - cbDllUnload(); // must wait for thread termination - assert(!findLowLevelThread(tid)); - } - if (toFree) - FreeLibrary(toFree); - } - - private void monitorDLLRefCnt() nothrow - { - // this thread keeps the DLL alive until all external references are gone - // (including those from DLLs using druntime in a shared DLL) - while (ll_dllHasExternalReferences(runtimeModule)) - { - // find and unload module that only has internal references left - HMODULE hMod; - { - lowlevelLock.lock_nothrow(); - scope(exit) lowlevelLock.unlock_nothrow(); - - foreach (i; 0 .. ll_nThreads) - if (ll_pThreads[i].cbDllUnload && ll_pThreads[i].hMod != runtimeModule) - if (!ll_dllHasExternalReferences(ll_pThreads[i].hMod)) - { - hMod = ll_pThreads[i].hMod; - break; - } - } - if (hMod) - notifyUnloadLowLevelThreads(hMod); - else - Thread.sleep(100.msecs); - } - - notifyUnloadLowLevelThreads(runtimeModule); - - // the current thread will be terminated without cleanup within the thread - ll_removeThread(GetCurrentThreadId()); - - FreeLibraryAndExitThread(runtimeModule, 0); - } - - HMODULE ll_getModuleHandle(void* funcptr, bool addref = false) nothrow @nogc - { - HMODULE hmod; - DWORD refflag = addref ? 0 : GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT; - if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | refflag, - cast(const(wchar)*) funcptr, &hmod)) - return null; - return hmod; - } - - bool ll_startDLLUnloadThread() nothrow @nogc - { - if (ll_dllMonitorThread !is ThreadID.init) - return true; - - // if a thread is created from a DLL, the MS runtime (starting with VC2015) increments the DLL reference count - // to avoid the DLL being unloaded while the thread is still running. Mimick this behavior here for all - // runtimes not doing this - bool needRef = !msvcUsesUCRT; - if (needRef) - ll_getModuleHandle(runtimeModule, true); - - // the monitor thread must be a low-level thread so the runtime does not attach to it - ll_dllMonitorThread = createLowLevelThread(() { monitorDLLRefCnt(); }); - return ll_dllMonitorThread != ThreadID.init; - } -} - -/** - * Create a thread not under control of the runtime, i.e. TLS module constructors are - * not run and the GC does not suspend it during a collection. - * - * Params: - * dg = delegate to execute in the created thread. - * stacksize = size of the stack of the created thread. The default of 0 will select the - * platform-specific default size. - * cbDllUnload = Windows only: if running in a dynamically loaded DLL, this delegate will be called - * if the DLL is supposed to be unloaded, but the thread is still running. - * The thread must be terminated via `joinLowLevelThread` by the callback. - * - * Returns: the platform specific thread ID of the new thread. If an error occurs, `ThreadID.init` - * is returned. - */ -ThreadID createLowLevelThread(void delegate() nothrow dg, uint stacksize = 0, - void delegate() nothrow cbDllUnload = null) nothrow @nogc -{ - static struct Context - { - void delegate() nothrow dg; - version (Windows) - HMODULE cbMod; - } - auto context = cast(Context*)malloc(Context.sizeof); - scope(exit) free(context); - context.dg = dg; - - ThreadID tid; - version (Windows) - { - // the thread won't start until after the DLL is unloaded - if (thread_DLLProcessDetaching) - return ThreadID.init; - context.cbMod = cbDllUnload ? ll_getModuleHandle(cbDllUnload.funcptr) : null; - if (context.cbMod) - { - int refcnt = dll_getRefCount(context.cbMod); - if (refcnt < 0) - { - // not a dynamically loaded DLL, so never unloaded - cbDllUnload = null; - context.cbMod = null; - } - if (refcnt == 0) - return ThreadID.init; // createLowLevelThread called while DLL is unloading - } - - static extern (Windows) uint thread_lowlevelEntry(void* ctx) nothrow - { - auto context = *cast(Context*)ctx; - free(ctx); - - context.dg(); - - ll_removeThread(GetCurrentThreadId()); - if (context.cbMod && context.cbMod != runtimeModule) - FreeLibrary(context.cbMod); - return 0; - } - - // see Thread.start() for why thread is created in suspended state - HANDLE hThread = cast(HANDLE) _beginthreadex(null, stacksize, &thread_lowlevelEntry, - context, CREATE_SUSPENDED, &tid); - if (!hThread) - return ThreadID.init; - } - - lowlevelLock.lock_nothrow(); - scope(exit) lowlevelLock.unlock_nothrow(); - - ll_nThreads++; - ll_pThreads = cast(ll_ThreadData*)realloc(ll_pThreads, ll_ThreadData.sizeof * ll_nThreads); - ll_pThreads[ll_nThreads - 1] = ll_ThreadData.init; - - version (Windows) - { - ll_pThreads[ll_nThreads - 1].tid = tid; - // ignore callback if not a dynamically loaded DLL - if (cbDllUnload) - { - ll_pThreads[ll_nThreads - 1].cbDllUnload = cbDllUnload; - ll_pThreads[ll_nThreads - 1].hMod = context.cbMod; - if (context.cbMod != runtimeModule) - ll_getModuleHandle(context.cbMod, true); // increment ref count - } - - if (ResumeThread(hThread) == -1) - onThreadError("Error resuming thread"); - CloseHandle(hThread); - - if (cbDllUnload) - ll_startDLLUnloadThread(); - } - else version (Posix) - { - static extern (C) void* thread_lowlevelEntry(void* ctx) nothrow - { - auto context = *cast(Context*)ctx; - free(ctx); - - context.dg(); - ll_removeThread(pthread_self()); - return null; - } - - size_t stksz = adjustStackSize(stacksize); - - pthread_attr_t attr; - - int rc; - if ((rc = pthread_attr_init(&attr)) != 0) - return ThreadID.init; - if (stksz && (rc = pthread_attr_setstacksize(&attr, stksz)) != 0) - return ThreadID.init; - if ((rc = pthread_create(&tid, &attr, &thread_lowlevelEntry, context)) != 0) - return ThreadID.init; - rc = pthread_attr_destroy(&attr); - assert(rc == 0); - - ll_pThreads[ll_nThreads - 1].tid = tid; - } - else - static assert(0, "unsupported os"); - context = null; // free'd in thread - return tid; -} - -/** - * Wait for a thread created with `createLowLevelThread` to terminate. - * - * Note: In a Windows DLL, if this function is called via DllMain with - * argument DLL_PROCESS_DETACH, the thread is terminated forcefully - * without proper cleanup as a deadlock would happen otherwise. - * - * Params: - * tid = the thread ID returned by `createLowLevelThread`. - */ -void joinLowLevelThread(ThreadID tid) nothrow @nogc -{ - version (Windows) - { - HANDLE handle = OpenThreadHandle(tid); - if (!handle) - return; - - if (thread_DLLProcessDetaching) - { - // When being called from DllMain/DLL_DETACH_PROCESS, threads cannot stop - // due to the loader lock being held by the current thread. - // On the other hand, the thread must not continue to run as it will crash - // if the DLL is unloaded. The best guess is to terminate it immediately. - TerminateThread(handle, 1); - WaitForSingleObject(handle, 10); // give it some time to terminate, but don't wait indefinitely - } - else - WaitForSingleObject(handle, INFINITE); - CloseHandle(handle); - } - else version (Posix) - { - if (pthread_join(tid, null) != 0) - onThreadError("Unable to join thread"); - } - else - static assert(0, "unsupported os"); -} - -nothrow @nogc unittest -{ - struct TaskWithContect - { - shared int n = 0; - void run() nothrow - { - n.atomicOp!"+="(1); - } - } - TaskWithContect task; - - ThreadID[8] tids; - for (int i = 0; i < tids.length; i++) - { - tids[i] = createLowLevelThread(&task.run); - assert(tids[i] != ThreadID.init); - } - - for (int i = 0; i < tids.length; i++) - joinLowLevelThread(tids[i]); - - assert(task.n == tids.length); -} - -version (Posix) -private size_t adjustStackSize(size_t sz) nothrow @nogc -{ - if (sz == 0) - return 0; - - // stack size must be at least PTHREAD_STACK_MIN for most platforms. - if (PTHREAD_STACK_MIN > sz) - sz = PTHREAD_STACK_MIN; - - version (CRuntime_Glibc) - { - // On glibc, TLS uses the top of the stack, so add its size to the requested size - sz += externDFunc!("rt.sections_elf_shared.sizeOfTLS", - size_t function() @nogc nothrow)(); - } - - // stack size must be a multiple of pageSize - sz = ((sz + pageSize - 1) & ~(pageSize - 1)); - - return sz; -} From 2e1cf5de508e686ced41554f3c5e9fec183be7cd Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 14:15:36 +0300 Subject: [PATCH 24/32] PRIORITY_* unittests moved from posix implementation to osthread module --- druntime/src/core/thread/osthread.d | 32 +++++++++++++++++++++++++++ druntime/src/core/thread/posix_impl.d | 30 +------------------------ 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/druntime/src/core/thread/osthread.d b/druntime/src/core/thread/osthread.d index 98aab4cfd22a..58be6533e43d 100644 --- a/druntime/src/core/thread/osthread.d +++ b/druntime/src/core/thread/osthread.d @@ -563,6 +563,38 @@ unittest Thread.sleep(1.msecs); } +unittest +{ + with(Thread) + { + auto thr = Thread.getThis(); + immutable prio = thr.priority; + scope (exit) thr.priority = prio; + + assert(prio == PRIORITY_DEFAULT); + assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); + thr.priority = PRIORITY_MIN; + assert(thr.priority == PRIORITY_MIN); + thr.priority = PRIORITY_MAX; + assert(thr.priority == PRIORITY_MAX); + } +} + +unittest // Bugzilla 8960 +{ + import core.sync.semaphore; + + with(Thread) + { + auto thr = new Thread({}); + thr.start(); + Thread.sleep(1.msecs); // wait a little so the thread likely has finished + thr.priority = PRIORITY_MAX; // setting priority doesn't cause error + auto prio = thr.priority; // getting priority doesn't cause error + assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); + } +} + /////////////////////////////////////////////////////////////////////////////// // GC Support Routines /////////////////////////////////////////////////////////////////////////////// diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index ff85dc407d91..322d8df4f9d1 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -326,7 +326,7 @@ class Thread : ThreadBase version (all) { - private struct Priority + package struct Priority { int PRIORITY_MIN = int.min; int PRIORITY_DEFAULT = int.min; @@ -550,34 +550,6 @@ class Thread : ThreadBase } } - - //FIXME: move to osthread module: - unittest - { - auto thr = Thread.getThis(); - immutable prio = thr.priority; - scope (exit) thr.priority = prio; - - assert(prio == PRIORITY_DEFAULT); - assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); - thr.priority = PRIORITY_MIN; - assert(thr.priority == PRIORITY_MIN); - thr.priority = PRIORITY_MAX; - assert(thr.priority == PRIORITY_MAX); - } - - unittest // Bugzilla 8960 - { - import core.sync.semaphore; - - auto thr = new Thread({}); - thr.start(); - Thread.sleep(1.msecs); // wait a little so the thread likely has finished - thr.priority = PRIORITY_MAX; // setting priority doesn't cause error - auto prio = thr.priority; // getting priority doesn't cause error - assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); - } - override final @property bool isRunning() nothrow @nogc { if (!super.isRunning()) From fcc8fcae7680dffaaa365d496cd3da21d9ae53e2 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 14:16:46 +0300 Subject: [PATCH 25/32] windows_impl: some Posix version branches removed --- druntime/src/core/thread/windows_impl.d | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index ba297ff7074f..0208cbae2a40 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -232,18 +232,9 @@ class Thread : ThreadBase if (!super.isRunning()) return false; - version (Windows) - { - uint ecode = 0; - GetExitCodeThread( m_hndl, &ecode ); - return ecode == STILL_ACTIVE; - } - else version (Posix) - { - return atomicLoad(m_isRunning); - } - else - static assert(0, "unsupported os"); + uint ecode = 0; + GetExitCodeThread( m_hndl, &ecode ); + return ecode == STILL_ACTIVE; } static void sleep( Duration val ) @nogc nothrow @trusted @@ -299,11 +290,6 @@ class Thread : ThreadBase static void yield() @nogc nothrow { - version (Windows) - SwitchToThread(); - else version (Posix) - sched_yield(); - else - static assert(0, "unsupported os"); + SwitchToThread(); } } From 0274428f0f89c829516c58ff0592cf30e114edcc Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 14:17:31 +0300 Subject: [PATCH 26/32] windows_impl: some Posix version branches removed - 2 --- druntime/src/core/thread/windows_impl.d | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index 0208cbae2a40..dadb4e1f0445 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -244,7 +244,7 @@ class Thread : ThreadBase } do { - version (Windows) + version (all) { auto maxSleepMillis = dur!("msecs")( uint.max - 1 ); @@ -267,25 +267,6 @@ class Thread : ThreadBase } Sleep( cast(uint) val.total!"msecs" ); } - else version (Posix) - { - timespec tin = void; - timespec tout = void; - - val.split!("seconds", "nsecs")(tin.tv_sec, tin.tv_nsec); - if ( val.total!"seconds" > tin.tv_sec.max ) - tin.tv_sec = tin.tv_sec.max; - while ( true ) - { - if ( !nanosleep( &tin, &tout ) ) - return; - if ( errno != EINTR ) - assert(0, "Unable to sleep for the specified duration"); - tin = tout; - } - } - else - static assert(0, "unsupported os"); } static void yield() @nogc nothrow From 3f6a08cdf19cf1f735beb3d9364e9342175f8492 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 14:24:20 +0300 Subject: [PATCH 27/32] TODO and FIXME removed --- druntime/src/core/thread/osthread.d | 6 +++--- druntime/src/core/thread/posix_impl.d | 4 ++-- druntime/src/core/thread/windows_impl.d | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/druntime/src/core/thread/osthread.d b/druntime/src/core/thread/osthread.d index 58be6533e43d..871e964be2a4 100644 --- a/druntime/src/core/thread/osthread.d +++ b/druntime/src/core/thread/osthread.d @@ -430,7 +430,7 @@ class Thread : ThreadBase } } -package /*FIXME: private*/ Thread toThread(return scope ThreadBase t) @trusted nothrow @nogc pure +package Thread toThread(return scope ThreadBase t) @trusted nothrow @nogc pure { return cast(Thread) cast(void*) t; } @@ -1919,7 +1919,7 @@ else version (Posix) } } - package /*FIXME: private*/ + package { // // Entry point for POSIX threads @@ -2561,7 +2561,7 @@ nothrow @nogc unittest } version (Posix) -package /*FIXME: private*/ size_t adjustStackSize(size_t sz) nothrow @nogc +package size_t adjustStackSize(size_t sz) nothrow @nogc { if (sz == 0) return 0; diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index 322d8df4f9d1..6e3a2e41af60 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -102,7 +102,7 @@ version (GNU) version (CoreDdoc) {} else class Thread : ThreadBase { - package /*FIXME: private*/ shared bool m_isRunning; + package shared bool m_isRunning; version (Darwin) { @@ -148,7 +148,7 @@ class Thread : ThreadBase } } - package /*FIXME: private*/ final void run() + package final void run() { super.run(); } diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index dadb4e1f0445..e1c8506510d2 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -41,8 +41,7 @@ version (all) version (GNU) { - //FIXME: remove or not? - //~ import gcc.builtins; + import gcc.builtins; } /** From 95143aefdf729b9df1c3b676947c850ae56f0bf3 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 14:40:27 +0300 Subject: [PATCH 28/32] TODO removed from m_unhandled decl --- druntime/src/core/thread/threadbase.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druntime/src/core/thread/threadbase.d b/druntime/src/core/thread/threadbase.d index 5aad75360569..66a5504b837e 100644 --- a/druntime/src/core/thread/threadbase.d +++ b/druntime/src/core/thread/threadbase.d @@ -488,7 +488,7 @@ package: string m_name; size_t m_sz; bool m_isDaemon; - Throwable m_unhandled; //TODO: private + Throwable m_unhandled; /////////////////////////////////////////////////////////////////////////// // Storage of Active Thread From 528ef630400d98a021b6e2c3d48a5456d6ac43dc Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 14:53:01 +0300 Subject: [PATCH 29/32] thread_entryPoint access fix for Windows builds --- druntime/src/core/thread/osthread.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druntime/src/core/thread/osthread.d b/druntime/src/core/thread/osthread.d index 871e964be2a4..419d3565b25a 100644 --- a/druntime/src/core/thread/osthread.d +++ b/druntime/src/core/thread/osthread.d @@ -1803,7 +1803,7 @@ extern (C) void thread_term() @nogc nothrow version (Windows) { - private + package { // // Entry point for Windows threads From cfe1d21e40c5e1730a7001f0d83b0cd1aac06985 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 14:55:44 +0300 Subject: [PATCH 30/32] Darwin build fix --- druntime/src/core/thread/posix_impl.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druntime/src/core/thread/posix_impl.d b/druntime/src/core/thread/posix_impl.d index 6e3a2e41af60..a29ec71a8c4f 100644 --- a/druntime/src/core/thread/posix_impl.d +++ b/druntime/src/core/thread/posix_impl.d @@ -106,7 +106,7 @@ class Thread : ThreadBase version (Darwin) { - private mach_port_t m_tmach; + package mach_port_t m_tmach; } version (Solaris) From 934844609db280d70fd35808be5bfd27ce567121 Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 16:30:36 +0300 Subject: [PATCH 31/32] Windows build fix - 2 --- druntime/src/core/thread/windows_impl.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index e1c8506510d2..f000553f2f7e 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -73,7 +73,7 @@ else version (CoreDdoc) {} else class Thread : ThreadBase { - private HANDLE m_hndl; + package HANDLE m_hndl; alias TLSKey = uint; this( void function() fn, size_t sz = 0 ) @safe pure nothrow @nogc From 8a044e7e18698fb5e20a8f7ca84faf348d8c791e Mon Sep 17 00:00:00 2001 From: Denis Feklushkin Date: Thu, 30 Apr 2026 19:26:19 +0300 Subject: [PATCH 32/32] Windows build fix - 3 --- druntime/src/core/thread/windows_impl.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druntime/src/core/thread/windows_impl.d b/druntime/src/core/thread/windows_impl.d index f000553f2f7e..d34ba31fd798 100644 --- a/druntime/src/core/thread/windows_impl.d +++ b/druntime/src/core/thread/windows_impl.d @@ -101,7 +101,7 @@ class Thread : ThreadBase m_hndl = m_hndl.init; } - private final void run() + package final void run() { super.run(); }