blob: 9fcd30e50fb77d9194b6eacdee59b901087cc5de [file] [log] [blame]
/**
* 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)
*/
/* NOTE: This file has been patched from the original DMD distribution to
* work with the GDC compiler.
*/
module core.thread.osthread;
import core.thread.threadbase;
import core.thread.context;
import core.thread.types;
import core.atomic;
import core.memory : GC;
import core.time;
import core.exception : onOutOfMemoryError;
import core.internal.traits : externDFunc;
///////////////////////////////////////////////////////////////////////////////
// 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 (Shared)
version (GNU)
version = GNUShared;
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;
}
}
else version (X86)
{
version (CET) {} else
{
version = AsmExternal;
}
}
else version (X86_64)
{
version (CET) {} else
version (D_X32) {} else
{
version = AsmExternal;
}
}
else version (PPC)
{
version (Posix)
{
version = AsmExternal;
}
}
else version (MIPS_O32)
{
version (Posix)
{
version = AsmExternal;
}
}
else version (AArch64)
{
version (Posix)
{
version = AsmExternal;
}
}
else version (ARM)
{
version (Posix)
{
version = AsmExternal;
}
}
version (Posix)
{
version (AsmX86_Windows) {} else
version (AsmX86_Posix) {} else
version (AsmX86_64_Windows) {} else
version (AsmX86_64_Posix) {} else
version (AsmExternal) {} else
{
// NOTE: The ucontext implementation requires architecture specific
// data definitions to operate so testing for it must be done
// by checking for the existence of ucontext_t rather than by
// a version identifier. Please note that this is considered
// an obsolescent feature according to the POSIX spec, so a
// custom solution is still preferred.
import core.sys.posix.ucontext;
}
}
version (Windows)
{
import core.stdc.stdint : uintptr_t; // for _beginthreadex decl below
import core.stdc.stdlib; // for malloc, atexit
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)
{
import core.stdc.errno;
import core.sys.posix.semaphore;
import core.sys.posix.stdlib; // for malloc, valloc, free, atexit
import core.sys.posix.pthread;
import core.sys.posix.signal;
import core.sys.posix.time;
version (Darwin)
{
import core.sys.darwin.mach.thread_act;
import core.sys.darwin.pthread : pthread_mach_thread_np;
}
}
version (Solaris)
{
import core.sys.solaris.sys.priocntl;
import core.sys.solaris.sys.types;
import core.sys.posix.sys.wait : idtype_t;
}
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);
}
}
///////////////////////////////////////////////////////////////////////////////
// Thread
///////////////////////////////////////////////////////////////////////////////
/**
* 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.
*/
class Thread : ThreadBase
{
//
// Main process thread
//
version (FreeBSD)
{
// set when suspend failed and should be retried, see Issue 13416
private shared bool m_suspendagain;
}
//
// Standard thread data
//
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;
}
//
// Standard types
//
version (Windows)
{
alias TLSKey = uint;
}
else version (Posix)
{
alias TLSKey = pthread_key_t;
}
///////////////////////////////////////////////////////////////////////////
// Initialization
///////////////////////////////////////////////////////////////////////////
/**
* 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
{
super(fn, sz);
}
/**
* 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
{
super(dg, sz);
}
package this( size_t sz = 0 ) @safe pure nothrow @nogc
{
super(sz);
}
/**
* Cleans up any remaining resources used by this object.
*/
~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;
}
}
//
// Thread entry point. Invokes the function or delegate passed on
// construction (if any).
//
private final void run()
{
super.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 ThreadBase.getThis().toThread;
}
///////////////////////////////////////////////////////////////////////////
// Thread Context and GC Scanning Support
///////////////////////////////////////////////////////////////////////////
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." );
}
}
///////////////////////////////////////////////////////////////////////////
// General Actions
///////////////////////////////////////////////////////////////////////////
/**
* 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
in
{
assert( !next && !prev );
}
do
{
auto wasThreaded = multiThreadedFlag;
multiThreadedFlag = true;
scope( failure )
{
if ( !wasThreaded )
multiThreadedFlag = false;
}
version (Windows) {} 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" );
}
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" );
}
slock.lock_nothrow();
scope(exit) slock.unlock_nothrow();
{
++nAboutToStart;
pAboutToStart = cast(ThreadBase*)realloc(pAboutToStart, Thread.sizeof * nAboutToStart);
pAboutToStart[nAboutToStart - 1] = 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)
{
version (GNU)
{
auto libs = externDFunc!("gcc.sections.pinLoadedLibraries",
void* function() @nogc nothrow)();
}
else
{
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 )
{
version (GNU)
{
externDFunc!("gcc.sections.unpinLoadedLibraries",
void function(void*) @nogc nothrow)(libs);
}
else
{
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" );
}
return this;
}
}
/**
* 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 )
{
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;
}
if ( m_unhandled )
{
if ( rethrow )
throw m_unhandled;
return m_unhandled;
}
return null;
}
///////////////////////////////////////////////////////////////////////////
// Thread Priority Actions
///////////////////////////////////////////////////////////////////////////
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
{
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
cache = loadPriorities;
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 = -clinfo[0];
// by definition
result.PRIORITY_DEFAULT = 0;
}
}
else version (Posix)
{
int policy;
sched_param param;
pthread_getschedparam( pthread_self(), &policy, &param ) == 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");
}
else
{
static assert(0, "Your code here.");
}
return result;
}
/**
* 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 (cast(int function() @nogc nothrow pure @safe)
&loadGlobal!"PRIORITY_MIN")();
}
/**
* 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 (cast(int function() @nogc nothrow pure @safe)
&loadGlobal!"PRIORITY_MAX")();
}
/**
* 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 (cast(int function() @nogc nothrow pure @safe)
&loadGlobal!"PRIORITY_DEFAULT")();
}
}
version (NetBSD)
{
//NetBSD does not support priority for default policy
// and it is not possible change policy without root access
int fakePriority = int.max;
}
/**
* 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()
{
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, &param))
{
// 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;
}
}
/**
* 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 )
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, 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, &param))
{
// 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, &param))
{
// ignore error if thread is not running => Bugzilla 8960
if (!atomicLoad(m_isRunning)) return;
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);
}
/**
* Tests whether this thread is running.
*
* Returns:
* true if the thread is running, false if not.
*/
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);
}
}
///////////////////////////////////////////////////////////////////////////
// Actions on Calling Thread
///////////////////////////////////////////////////////////////////////////
/**
* 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
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;
}
}
}
/**
* Forces a context switch to occur away from the calling thread.
*/
static void yield() @nogc nothrow
{
version (Windows)
SwitchToThread();
else version (Posix)
sched_yield();
}
}
private Thread toThread(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(
{
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();
}
unittest
{
// NOTE: This entire test is based on the assumption that no
// memory is allocated after the child thread is
// started. If an allocation happens, a collection could
// trigger, which would cause the synchronization below
// to cause a deadlock.
// NOTE: DO NOT USE LOCKS IN CRITICAL REGIONS IN NORMAL CODE.
import core.sync.semaphore;
auto sema = new Semaphore(),
semb = new Semaphore();
auto thr = new Thread(
{
thread_enterCriticalRegion();
assert(thread_inCriticalRegion());
sema.notify();
semb.wait();
assert(thread_inCriticalRegion());
thread_exitCriticalRegion();
assert(!thread_inCriticalRegion());
sema.notify();
semb.wait();
assert(!thread_inCriticalRegion());
});
thr.start();
sema.wait();
synchronized (ThreadBase.criticalRegionLock)
assert(thr.m_isInCriticalRegion);
semb.notify();
sema.wait();
synchronized (ThreadBase.criticalRegionLock)
assert(!thr.m_isInCriticalRegion);
semb.notify();
thr.join();
}
unittest
{
import core.sync.semaphore;
shared bool inCriticalRegion;
auto sema = new Semaphore(),
semb = new Semaphore();
auto thr = new Thread(
{
thread_enterCriticalRegion();
inCriticalRegion = true;
sema.notify();
semb.wait();
Thread.sleep(dur!"msecs"(1));
inCriticalRegion = false;
thread_exitCriticalRegion();
});
thr.start();
sema.wait();
assert(inCriticalRegion);
semb.notify();
thread_suspendAll();
assert(!inCriticalRegion);
thread_resumeAll();
}
///////////////////////////////////////////////////////////////////////////////
// GC Support Routines
///////////////////////////////////////////////////////////////////////////////
version (CoreDdoc)
{
/**
* Instruct the thread module, when initialized, to use a different set of
* signals besides SIGUSR1 and SIGUSR2 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
{
}
}
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;
}
}
version (Posix)
{
private __gshared int suspendSignalNumber = SIGUSR1;
private __gshared int resumeSignalNumber = SIGUSR2;
}
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);
}
thisThread.m_isDaemon = true;
thisThread.tlsGCdataInit();
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();
*/
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.tlsGCdataInit();
Thread.setThis( thisThread );
}
else
{
thisThread.m_hndl = OpenThreadHandle( addr );
impersonate_thread(addr,
{
thisThread.tlsGCdataInit();
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*)&regs[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*)&regs[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*)&regs[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*)&regs[0];
}
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
{
static assert(false, "Architecture not supported.");
}
fn(sp);
}
version (Windows)
private extern (D) void scanWindowsOnly(scope ScanAllThreadsTypeFn scan, ThreadBase _t) nothrow
{
auto t = _t.toThread;
scan( ScanType.stack, t.m_reg.ptr, t.m_reg.ptr + t.m_reg.length );
}
/**
* 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)
{
import core.sys.posix.unistd;
alias getpid = core.sys.posix.unistd.getpid;
}
else version (Windows)
{
alias getpid = core.sys.windows.winbase.GetCurrentProcessId;
}
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 (Solaris) int thr_stksegment(stack_t* stk);
version (OpenBSD) int pthread_stackseg_np(pthread_t thread, stack_t* sinfo);
}
package 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 (GNU)
return __builtin_frame_address(0);
else
static assert(false, "Architecture not supported.");
}
package 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, "Platform not supported.");
return bottom;
}
else
static assert(false, "Architecture not supported.");
}
else version (Darwin)
{
import core.sys.darwin.pthread;
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
{
Duration waittime = dur!"usecs"(10);
Lagain:
if (!t.isRunning)
{
Thread.remove(t);
return false;
}
else if (t.m_isInCriticalRegion)
{
Thread.criticalRegionLock.unlock_nothrow();
Thread.sleep(waittime);
if (waittime < dur!"msecs"(10)) waittime *= 2;
Thread.criticalRegionLock.lock_nothrow();
goto Lagain;
}
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 (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();
}
}
return true;
}
/**
* 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.slock.lock_nothrow();
{
if ( ++suspendDepth > 1 )
return;
Thread.criticalRegionLock.lock_nothrow();
scope (exit) Thread.criticalRegionLock.unlock_nothrow();
size_t cnt;
Thread t = ThreadBase.sm_tbeg.toThread;
while (t)
{
auto tn = t.next.toThread;
if (suspend(t))
++cnt;
t = tn;
}
version (Darwin)
{}
else version (Posix)
{
// subtract own thread
assert(cnt >= 1);
--cnt;
Lagain:
// wait for semaphore notifications
for (; cnt; --cnt)
{
while (sem_wait(&suspendCount) != 0)
{
if (errno != EINTR)
onThreadError("Unable to wait for semaphore");
errno = 0;
}
}
version (FreeBSD)
{
// avoid deadlocks, see Issue 13416
t = ThreadBase.sm_tbeg.toThread;
while (t)
{
auto tn = t.next;
if (t.m_suspendagain && suspend(t))
++cnt;
t = tn.toThread;
}
if (cnt)
goto Lagain;
}
}
}
}
/**
* 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 (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;
}
}
}
/**
* Initializes the thread module. This function must be called by the
* garbage collector on startup and before any other thread routines
* are called.
*/
extern (C) void thread_init() @nogc
{
// 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();
// The Android VM runtime intercepts SIGUSR1 and apparently doesn't allow
// its signal handler to run, so swap the two signals on Android, since
// thread_resumeHandler does nothing.
version (Android) thread_setGCSignals(SIGUSR2, SIGUSR1);
version (Darwin)
{
// thread id different in forked child process
static extern(C) void initChildAfterFork()
{
auto thisThread = Thread.getThis();
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 (Posix)
{
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, 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 );
}
if (typeid(Thread).initializer.ptr)
_mainThreadStore[] = typeid(Thread).initializer[];
Thread.sm_main = attachThread((cast(Thread)_mainThreadStore.ptr).__ctor());
}
private alias MainThreadStore = void[__traits(classInstanceSize, Thread)];
package __gshared align(Thread.alignof) MainThreadStore _mainThreadStore;
/**
* Terminates the thread module. No other thread routine may be called
* afterwards.
*/
extern (C) void thread_term() @nogc
{
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.setThis(obj);
Thread.add(obj);
scope (exit)
{
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 )
{
if ( obj.m_unhandled is null )
obj.m_unhandled = t;
else
{
Throwable last = obj.m_unhandled;
while ( last.next !is null )
last = last.next;
last.next = 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)
{
private
{
import core.stdc.errno;
import core.sys.posix.semaphore;
import core.sys.posix.stdlib; // for malloc, valloc, free, atexit
import core.sys.posix.pthread;
import core.sys.posix.signal;
import core.sys.posix.time;
version (Darwin)
{
import core.sys.darwin.mach.thread_act;
import core.sys.darwin.pthread : pthread_mach_thread_np;
}
//
// Entry point for POSIX threads
//
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 (GNUShared)
{
externDFunc!("gcc.sections.inheritLoadedLibraries",
void function(void*) @nogc nothrow)(loadedLibraries);
}
else version (Shared)
{
externDFunc!("rt.sections_elf_shared.inheritLoadedLibraries",
void function(void*) @nogc nothrow)(loadedLibraries);
}
obj.initDataStorage();
atomicStore!(MemoryOrder.raw)(obj.m_isRunning, true);
Thread.setThis(obj); // allocates lazy TLS (see Issue 11981)
Thread.add(obj); // can only receive signals from here on
scope (exit)
{
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, pthread_cleanup ) )
{
pthread_cleanup cleanup = void;
cleanup.push( &thread_cleanupHandler, cast(void*) obj );
}
else static if ( __traits( compiles, 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 )
{
if ( obj.m_unhandled is null )
obj.m_unhandled = t;
else
{
Throwable last = obj.m_unhandled;
while ( last.next !is null )
last = last.next;
last.next = t;
}
}
try
{
rt_moduleTlsCtor();
try
{
obj.run();
}
catch ( Throwable t )
{
append( t );
}
rt_moduleTlsDtor();
version (GNUShared)
{
externDFunc!("gcc.sections.cleanupLoadedLibraries",
void function() @nogc nothrow)();
}
else 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, pthread_cleanup ) )
{
cleanup.pop( 0 );
}
else static if ( __traits( compiles, pthread_cleanup_push ) )
{
pthread_cleanup_pop( 0 );
}
return null;
}
//
// Used to track the number of suspended threads
//
__gshared sem_t suspendCount;
extern (C) void thread_suspendHandler( int sig ) nothrow
in
{
assert( sig == suspendSignalNumber );
}
do
{
void op(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();
assert(obj !is null);
if ( !obj.m_lock )
{
obj.m_curr.tstack = getStackTop();
}
sigset_t sigres = void;
int status;
status = sigfillset( &sigres );
assert( status == 0 );
status = sigdelset( &sigres, resumeSignalNumber );
assert( status == 0 );
version (FreeBSD) obj.m_suspendagain = false;
status = sem_post( &suspendCount );
assert( status == 0 );
sigsuspend( &sigres );
if ( !obj.m_lock )
{
obj.m_curr.tstack = obj.m_curr.bstack;
}
}
// avoid deadlocks on FreeBSD, see Issue 13416
version (FreeBSD)
{
auto obj = Thread.getThis();
if (THR_IN_CRITICAL(obj.m_addr))
{
obj.m_suspendagain = true;
if (sem_post(&suspendCount)) assert(0);
return;
}
}
callWithStackShell(&op);
}
extern (C) void thread_resumeHandler( int sig ) nothrow
in
{
assert( sig == resumeSignalNumber );
}
do
{
}
// HACK libthr internal (thr_private.h) macro, used to
// avoid deadlocks in signal handler, see Issue 13416
version (FreeBSD) bool THR_IN_CRITICAL(pthread_t p) nothrow @nogc
{
import core.sys.posix.config : c_long;
import core.sys.posix.sys.types : lwpid_t;
// If the begin of pthread would be changed in libthr (unlikely)
// we'll run into undefined behavior, compare with thr_private.h.
static struct pthread
{
c_long tid;
static struct umutex { lwpid_t owner; uint flags; uint[2] ceilings; uint[4] spare; }
umutex lock;
uint cycle;
int locklevel;
int critical_count;
// ...
}
auto priv = cast(pthread*)p;
return priv.locklevel > 0 || priv.critical_count > 0;
}
}
}
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.winbase : FreeLibraryAndExitThread, GetModuleHandleExW,
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT;
import core.sys.windows.windef : HMODULE;
import core.sys.windows.dll : dll_getRefCount;
version (CRuntime_Microsoft)
extern(C) extern __gshared ubyte msvcUsesUCRT; // from rt/msvc.d
/// set during termination of a DLL on Windows, i.e. while executing DllMain(DLL_PROCESS_DETACH)
public __gshared bool thread_DLLProcessDetaching;
__gshared HMODULE ll_dllModule;
__gshared ThreadID ll_dllMonitorThread;
int ll_countLowLevelThreadsWithDLLUnloadCallback() nothrow
{
lowlevelLock.lock_nothrow();
scope(exit) lowlevelLock.unlock_nothrow();
int cnt = 0;
foreach (i; 0 .. ll_nThreads)
if (ll_pThreads[i].cbDllUnload)
cnt++;
return cnt;
}
bool ll_dllHasExternalReferences() nothrow
{
version (CRuntime_DigitalMars)
enum internalReferences = 1; // only the watchdog thread
else
int internalReferences = msvcUsesUCRT ? 1 + ll_countLowLevelThreadsWithDLLUnloadCallback() : 1;
int refcnt = dll_getRefCount(ll_dllModule);
return refcnt > internalReferences;
}
private void monitorDLLRefCnt() nothrow
{
// this thread keeps the DLL alive until all external references are gone
while (ll_dllHasExternalReferences())
{
Thread.sleep(100.msecs);
}
// the current thread will be terminated below
ll_removeThread(GetCurrentThreadId());
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)
{
cbDllUnload = ll_pThreads[i].cbDllUnload;
tid = ll_pThreads[0].tid;
}
}
if (!cbDllUnload)
break;
cbDllUnload();
assert(!findLowLevelThread(tid));
}
FreeLibraryAndExitThread(ll_dllModule, 0);
}
int ll_getDLLRefCount() nothrow @nogc
{
if (!ll_dllModule &&
!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
cast(const(wchar)*) &ll_getDLLRefCount, &ll_dllModule))
return -1;
return dll_getRefCount(ll_dllModule);
}
bool ll_startDLLUnloadThread() nothrow @nogc
{
int refcnt = ll_getDLLRefCount();
if (refcnt < 0)
return false; // not a dynamically loaded DLL
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
version (CRuntime_DigitalMars)
enum needRef = true;
else
bool needRef = !msvcUsesUCRT;
if (needRef)
{
HMODULE hmod;
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, cast(const(wchar)*) &ll_getDLLRefCount, &hmod);
}
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
{
void delegate() nothrow* context = cast(void delegate() nothrow*)malloc(dg.sizeof);
*context = dg;
ThreadID tid;
version (Windows)
{
// the thread won't start until after the DLL is unloaded
if (thread_DLLProcessDetaching)
return ThreadID.init;
static extern (Windows) uint thread_lowlevelEntry(void* ctx) nothrow
{
auto dg = *cast(void delegate() nothrow*)ctx;
free(ctx);
dg();
ll_removeThread(GetCurrentThreadId());
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);
version (Windows)
{
ll_pThreads[ll_nThreads - 1].tid = tid;
ll_pThreads[ll_nThreads - 1].cbDllUnload = cbDllUnload;
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 dg = *cast(void delegate() nothrow*)ctx;
free(ctx);
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;
if ((rc = pthread_attr_destroy(&attr)) != 0)
return ThreadID.init;
ll_pThreads[ll_nThreads - 1].tid = tid;
}
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");
}
}
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
version (GNU)
{
sz += externDFunc!("gcc.sections.elf.sizeOfTLS",
size_t function() @nogc nothrow)();
}
else
{
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;
}