When libphobos is configured with --enable-cet, this adds extra fields to the Fiber class to support the ucontext_t fallback implementation. These fields get omitted when compiling user code unless they also used `-fversion=CET' to build their project, which resulted in data being overwritten from within swapcontext(). On reviewing the ucontext_t definitions, it was found that the shadow stack fields were missing, and the struct size didn't match up on X32. This has been fixed in upstream druntime and merged down here. Reviewed-on: https://github.com/dlang/druntime/pull/3293 libphobos/ChangeLog: PR d/98025 * Makefile.in: Regenerate. * configure: Regenerate. * configure.ac (DCFG_ENABLE_CET): Substitute. * libdruntime/MERGE: Merge upstream druntime 0fe7974c. * libdruntime/Makefile.in: Regenerate. * libdruntime/core/thread.d: Import gcc.config. (class Fiber): Add ucontext_t fields when GNU_Enable_CET is true. * libdruntime/gcc/config.d.in (GNU_Enable_CET): Define. * src/Makefile.in: Regenerate. * testsuite/Makefile.in: Regenerate.
5733 lines
171 KiB
D
5733 lines
171 KiB
D
/**
|
|
* 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.d)
|
|
*/
|
|
|
|
/* NOTE: This file has been patched from the original DMD distribution to
|
|
* work with the GDC compiler.
|
|
*/
|
|
module core.thread;
|
|
|
|
|
|
public import core.time; // for Duration
|
|
import core.exception : onOutOfMemoryError;
|
|
|
|
version (OSX)
|
|
version = Darwin;
|
|
else version (iOS)
|
|
version = Darwin;
|
|
else version (TVOS)
|
|
version = Darwin;
|
|
else version (WatchOS)
|
|
version = Darwin;
|
|
|
|
private
|
|
{
|
|
// interface to rt.tlsgc
|
|
import core.internal.traits : externDFunc;
|
|
|
|
alias rt_tlsgc_init = externDFunc!("rt.tlsgc.init", void* function() nothrow @nogc);
|
|
alias rt_tlsgc_destroy = externDFunc!("rt.tlsgc.destroy", void function(void*) nothrow @nogc);
|
|
|
|
alias ScanDg = void delegate(void* pstart, void* pend) nothrow;
|
|
alias rt_tlsgc_scan =
|
|
externDFunc!("rt.tlsgc.scan", void function(void*, scope ScanDg) nothrow);
|
|
|
|
alias rt_tlsgc_processGCMarks =
|
|
externDFunc!("rt.tlsgc.processGCMarks", void function(void*, scope IsMarkedDg) nothrow);
|
|
}
|
|
|
|
version (Solaris)
|
|
{
|
|
import core.sys.solaris.sys.priocntl;
|
|
import core.sys.solaris.sys.types;
|
|
}
|
|
|
|
version (GNU)
|
|
{
|
|
import gcc.builtins;
|
|
import gcc.config;
|
|
version (GNU_StackGrowsDown)
|
|
version = StackGrowsDown;
|
|
}
|
|
else
|
|
{
|
|
// this should be true for most architectures
|
|
version = StackGrowsDown;
|
|
}
|
|
|
|
/**
|
|
* 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 = core.sys.posix.unistd.getpid;
|
|
}
|
|
else version (Windows)
|
|
{
|
|
alias getpid = core.sys.windows.windows.GetCurrentProcessId;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Thread and Fiber Exceptions
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* Base class for thread exceptions.
|
|
*/
|
|
class ThreadException : Exception
|
|
{
|
|
@safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
|
|
{
|
|
super(msg, file, line, next);
|
|
}
|
|
|
|
@safe pure nothrow this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__)
|
|
{
|
|
super(msg, file, line, next);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Base class for thread errors to be used for function inside GC when allocations are unavailable.
|
|
*/
|
|
class ThreadError : Error
|
|
{
|
|
@safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
|
|
{
|
|
super(msg, file, line, next);
|
|
}
|
|
|
|
@safe pure nothrow this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__)
|
|
{
|
|
super(msg, file, line, next);
|
|
}
|
|
}
|
|
|
|
private
|
|
{
|
|
import core.atomic, core.memory, core.sync.mutex;
|
|
|
|
// Handling unaligned mutexes are not supported on all platforms, so we must
|
|
// ensure that the address of all shared data are appropriately aligned.
|
|
import core.internal.traits : classInstanceAlignment;
|
|
|
|
enum mutexAlign = classInstanceAlignment!Mutex;
|
|
enum mutexClassInstanceSize = __traits(classInstanceSize, Mutex);
|
|
|
|
//
|
|
// exposed by compiler runtime
|
|
//
|
|
extern (C) void rt_moduleTlsCtor();
|
|
extern (C) void rt_moduleTlsDtor();
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc;
|
|
|
|
version (DigitalMars)
|
|
{
|
|
version (Windows)
|
|
alias swapContext = _d_eh_swapContext;
|
|
else
|
|
{
|
|
extern(C) void* _d_eh_swapContextDwarf(void* newContext) nothrow @nogc;
|
|
|
|
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
|
|
alias swapContext = _d_eh_swapContext;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Thread Entry Point and Signal Handlers
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
version (Windows)
|
|
{
|
|
private
|
|
{
|
|
import core.stdc.stdint : uintptr_t; // for _beginthreadex decl below
|
|
import core.stdc.stdlib; // for malloc, atexit
|
|
import core.sys.windows.windows;
|
|
import core.sys.windows.threadaux; // for OpenThreadHandle
|
|
|
|
extern (Windows) alias btex_fptr = uint function(void*);
|
|
extern (C) uintptr_t _beginthreadex(void*, uint, btex_fptr, void*, uint, uint*) nothrow;
|
|
|
|
//
|
|
// Entry point for Windows threads
|
|
//
|
|
extern (Windows) uint thread_entryPoint( void* arg ) nothrow
|
|
{
|
|
Thread obj = cast(Thread) arg;
|
|
assert( obj );
|
|
|
|
assert( obj.m_curr is &obj.m_main );
|
|
obj.m_main.bstack = getStackBottom();
|
|
obj.m_main.tstack = obj.m_main.bstack;
|
|
obj.m_tlsgcdata = rt_tlsgc_init();
|
|
|
|
Thread.setThis(obj);
|
|
Thread.add(obj);
|
|
scope (exit)
|
|
{
|
|
Thread.remove(obj);
|
|
}
|
|
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)
|
|
{
|
|
import rt.sections;
|
|
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) inheritLoadedLibraries(loadedLibraries);
|
|
|
|
assert( obj.m_curr is &obj.m_main );
|
|
obj.m_main.bstack = getStackBottom();
|
|
obj.m_main.tstack = obj.m_main.bstack;
|
|
obj.m_tlsgcdata = rt_tlsgc_init();
|
|
|
|
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);
|
|
}
|
|
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 (Shared) cleanupLoadedLibraries();
|
|
}
|
|
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 );
|
|
}
|
|
body
|
|
{
|
|
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 );
|
|
}
|
|
body
|
|
{
|
|
|
|
}
|
|
|
|
// 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." );
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// 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
|
|
{
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// 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
|
|
in
|
|
{
|
|
assert( fn );
|
|
}
|
|
body
|
|
{
|
|
this(sz);
|
|
() @trusted { m_fn = fn; }();
|
|
m_call = Call.FN;
|
|
m_curr = &m_main;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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
|
|
in
|
|
{
|
|
assert( dg );
|
|
}
|
|
body
|
|
{
|
|
this(sz);
|
|
() @trusted { m_dg = dg; }();
|
|
m_call = Call.DG;
|
|
m_curr = &m_main;
|
|
}
|
|
|
|
|
|
/**
|
|
* Cleans up any remaining resources used by this object.
|
|
*/
|
|
~this() nothrow @nogc
|
|
{
|
|
if ( m_addr == m_addr.init )
|
|
{
|
|
return;
|
|
}
|
|
|
|
version (Windows)
|
|
{
|
|
m_addr = m_addr.init;
|
|
CloseHandle( m_hndl );
|
|
m_hndl = m_hndl.init;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
pthread_detach( m_addr );
|
|
m_addr = m_addr.init;
|
|
}
|
|
version (Darwin)
|
|
{
|
|
m_tmach = m_tmach.init;
|
|
}
|
|
rt_tlsgc_destroy( m_tlsgcdata );
|
|
m_tlsgcdata = null;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// 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 );
|
|
}
|
|
body
|
|
{
|
|
auto wasThreaded = multiThreadedFlag;
|
|
multiThreadedFlag = true;
|
|
scope( failure )
|
|
{
|
|
if ( !wasThreaded )
|
|
multiThreadedFlag = false;
|
|
}
|
|
|
|
version (Windows) {} else
|
|
version (Posix)
|
|
{
|
|
pthread_attr_t attr;
|
|
|
|
if ( pthread_attr_init( &attr ) )
|
|
onThreadError( "Error initializing thread attributes" );
|
|
if ( m_sz && pthread_attr_setstacksize( &attr, m_sz ) )
|
|
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(Thread*)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)
|
|
{
|
|
import rt.sections;
|
|
auto libs = pinLoadedLibraries();
|
|
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 )
|
|
{
|
|
unpinLoadedLibraries(libs);
|
|
.free(ps);
|
|
onThreadError( "Error creating thread" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( pthread_create( &m_addr, &attr, &thread_entryPoint, cast(void*) this ) != 0 )
|
|
onThreadError( "Error creating thread" );
|
|
}
|
|
}
|
|
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.
|
|
*/
|
|
final Throwable join( bool rethrow = true )
|
|
{
|
|
version (Windows)
|
|
{
|
|
if ( 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 ( 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;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// General Properties
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* Gets the OS identifier for this thread.
|
|
*
|
|
* Returns:
|
|
* If the thread hasn't been started yet, returns $(LREF ThreadID)$(D.init).
|
|
* Otherwise, returns the result of $(D GetCurrentThreadId) on Windows,
|
|
* and $(D pthread_self) on POSIX.
|
|
*
|
|
* The value is unique for the current process.
|
|
*/
|
|
final @property ThreadID id() @safe @nogc
|
|
{
|
|
synchronized( this )
|
|
{
|
|
return m_addr;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the user-readable label for this thread.
|
|
*
|
|
* Returns:
|
|
* The name of this thread.
|
|
*/
|
|
final @property string name() @safe @nogc
|
|
{
|
|
synchronized( this )
|
|
{
|
|
return m_name;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the user-readable label for this thread.
|
|
*
|
|
* Params:
|
|
* val = The new name of this thread.
|
|
*/
|
|
final @property void name( string val ) @safe @nogc
|
|
{
|
|
synchronized( this )
|
|
{
|
|
m_name = val;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the daemon status for this thread. While the runtime will wait for
|
|
* all normal threads to complete before tearing down the process, daemon
|
|
* threads are effectively ignored and thus will not prevent the process
|
|
* from terminating. In effect, daemon threads will be terminated
|
|
* automatically by the OS when the process exits.
|
|
*
|
|
* Returns:
|
|
* true if this is a daemon thread.
|
|
*/
|
|
final @property bool isDaemon() @safe @nogc
|
|
{
|
|
synchronized( this )
|
|
{
|
|
return m_isDaemon;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the daemon status for this thread. While the runtime will wait for
|
|
* all normal threads to complete before tearing down the process, daemon
|
|
* threads are effectively ignored and thus will not prevent the process
|
|
* from terminating. In effect, daemon threads will be terminated
|
|
* automatically by the OS when the process exits.
|
|
*
|
|
* Params:
|
|
* val = The new daemon status for this thread.
|
|
*/
|
|
final @property void isDaemon( bool val ) @safe @nogc
|
|
{
|
|
synchronized( this )
|
|
{
|
|
m_isDaemon = val;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Tests whether this thread is running.
|
|
*
|
|
* Returns:
|
|
* true if the thread is running, false if not.
|
|
*/
|
|
final @property bool isRunning() nothrow @nogc
|
|
{
|
|
if ( m_addr == m_addr.init )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
version (Windows)
|
|
{
|
|
uint ecode = 0;
|
|
GetExitCodeThread( m_hndl, &ecode );
|
|
return ecode == STILL_ACTIVE;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
return atomicLoad(m_isRunning);
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// 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 int loadGlobal(string which)()
|
|
{
|
|
static shared Priority cache;
|
|
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, ¶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");
|
|
}
|
|
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, ¶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;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
body
|
|
{
|
|
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, ¶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");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// 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 );
|
|
}
|
|
body
|
|
{
|
|
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();
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Thread Accessors
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* 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
|
|
{
|
|
// NOTE: This function may not be called until thread_init has
|
|
// completed. See thread_suspendAll for more information
|
|
// on why this might occur.
|
|
return sm_this;
|
|
}
|
|
|
|
|
|
/**
|
|
* Provides a list of all threads currently being tracked by the system.
|
|
* Note that threads in the returned array might no longer run (see
|
|
* $(D Thread.)$(LREF isRunning)).
|
|
*
|
|
* Returns:
|
|
* An array containing references to all threads currently being
|
|
* tracked by the system. The result of deleting any contained
|
|
* objects is undefined.
|
|
*/
|
|
static Thread[] getAll()
|
|
{
|
|
static void resize(ref Thread[] buf, size_t nlen)
|
|
{
|
|
buf.length = nlen;
|
|
}
|
|
return getAllImpl!resize();
|
|
}
|
|
|
|
|
|
/**
|
|
* Operates on all threads currently being tracked by the system. The
|
|
* result of deleting any Thread object is undefined.
|
|
* Note that threads passed to the callback might no longer run (see
|
|
* $(D Thread.)$(LREF isRunning)).
|
|
*
|
|
* Params:
|
|
* dg = The supplied code as a delegate.
|
|
*
|
|
* Returns:
|
|
* Zero if all elemented are visited, nonzero if not.
|
|
*/
|
|
static int opApply(scope int delegate(ref Thread) dg)
|
|
{
|
|
import core.stdc.stdlib : free, realloc;
|
|
|
|
static void resize(ref Thread[] buf, size_t nlen)
|
|
{
|
|
buf = (cast(Thread*)realloc(buf.ptr, nlen * Thread.sizeof))[0 .. nlen];
|
|
}
|
|
auto buf = getAllImpl!resize;
|
|
scope(exit) if (buf.ptr) free(buf.ptr);
|
|
|
|
foreach (t; buf)
|
|
{
|
|
if (auto res = dg(t))
|
|
return res;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
auto t1 = new Thread({
|
|
foreach (_; 0 .. 20)
|
|
Thread.getAll;
|
|
}).start;
|
|
auto t2 = new Thread({
|
|
foreach (_; 0 .. 20)
|
|
GC.collect;
|
|
}).start;
|
|
t1.join();
|
|
t2.join();
|
|
}
|
|
|
|
private static Thread[] getAllImpl(alias resize)()
|
|
{
|
|
import core.atomic;
|
|
|
|
Thread[] buf;
|
|
while (true)
|
|
{
|
|
immutable len = atomicLoad!(MemoryOrder.raw)(*cast(shared)&sm_tlen);
|
|
resize(buf, len);
|
|
assert(buf.length == len);
|
|
synchronized (slock)
|
|
{
|
|
if (len == sm_tlen)
|
|
{
|
|
size_t pos;
|
|
for (Thread t = sm_tbeg; t; t = t.next)
|
|
buf[pos++] = t;
|
|
return buf;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Stuff That Should Go Away
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
private:
|
|
//
|
|
// Initializes a thread object which has no associated executable function.
|
|
// This is used for the main thread initialized in thread_init().
|
|
//
|
|
this(size_t sz = 0) @safe pure nothrow @nogc
|
|
{
|
|
if (sz)
|
|
{
|
|
version (Posix)
|
|
{
|
|
// stack size must be a multiple of PAGESIZE
|
|
sz += PAGESIZE - 1;
|
|
sz -= sz % PAGESIZE;
|
|
// and at least PTHREAD_STACK_MIN
|
|
if (PTHREAD_STACK_MIN > sz)
|
|
sz = PTHREAD_STACK_MIN;
|
|
}
|
|
m_sz = sz;
|
|
}
|
|
m_call = Call.NO;
|
|
m_curr = &m_main;
|
|
}
|
|
|
|
|
|
//
|
|
// Thread entry point. Invokes the function or delegate passed on
|
|
// construction (if any).
|
|
//
|
|
final void run()
|
|
{
|
|
switch ( m_call )
|
|
{
|
|
case Call.FN:
|
|
m_fn();
|
|
break;
|
|
case Call.DG:
|
|
m_dg();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
private:
|
|
//
|
|
// The type of routine passed on thread construction.
|
|
//
|
|
enum Call
|
|
{
|
|
NO,
|
|
FN,
|
|
DG
|
|
}
|
|
|
|
|
|
//
|
|
// Standard types
|
|
//
|
|
version (Windows)
|
|
{
|
|
alias TLSKey = uint;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
alias TLSKey = pthread_key_t;
|
|
}
|
|
|
|
|
|
//
|
|
// Local storage
|
|
//
|
|
static Thread sm_this;
|
|
|
|
|
|
//
|
|
// Main process thread
|
|
//
|
|
__gshared Thread sm_main;
|
|
|
|
version (FreeBSD)
|
|
{
|
|
// set when suspend failed and should be retried, see Issue 13416
|
|
shared bool m_suspendagain;
|
|
}
|
|
|
|
|
|
//
|
|
// Standard thread data
|
|
//
|
|
version (Windows)
|
|
{
|
|
HANDLE m_hndl;
|
|
}
|
|
else version (Darwin)
|
|
{
|
|
mach_port_t m_tmach;
|
|
}
|
|
ThreadID m_addr;
|
|
Call m_call;
|
|
string m_name;
|
|
union
|
|
{
|
|
void function() m_fn;
|
|
void delegate() m_dg;
|
|
}
|
|
size_t m_sz;
|
|
version (Posix)
|
|
{
|
|
shared bool m_isRunning;
|
|
}
|
|
bool m_isDaemon;
|
|
bool m_isInCriticalRegion;
|
|
Throwable m_unhandled;
|
|
|
|
version (Solaris)
|
|
{
|
|
__gshared bool m_isRTClass;
|
|
}
|
|
|
|
private:
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Storage of Active Thread
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
//
|
|
// Sets a thread-local reference to the current thread object.
|
|
//
|
|
static void setThis( Thread t ) nothrow @nogc
|
|
{
|
|
sm_this = t;
|
|
}
|
|
|
|
|
|
private:
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Thread Context and GC Scanning Support
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
final void pushContext( Context* c ) nothrow @nogc
|
|
in
|
|
{
|
|
assert( !c.within );
|
|
}
|
|
body
|
|
{
|
|
m_curr.ehContext = swapContext(c.ehContext);
|
|
c.within = m_curr;
|
|
m_curr = c;
|
|
}
|
|
|
|
|
|
final void popContext() nothrow @nogc
|
|
in
|
|
{
|
|
assert( m_curr && m_curr.within );
|
|
}
|
|
body
|
|
{
|
|
Context* c = m_curr;
|
|
m_curr = c.within;
|
|
c.ehContext = swapContext(m_curr.ehContext);
|
|
c.within = null;
|
|
}
|
|
|
|
|
|
final Context* topContext() nothrow @nogc
|
|
in
|
|
{
|
|
assert( m_curr );
|
|
}
|
|
body
|
|
{
|
|
return m_curr;
|
|
}
|
|
|
|
|
|
static struct Context
|
|
{
|
|
void* bstack,
|
|
tstack;
|
|
|
|
/// Slot for the EH implementation to keep some state for each stack
|
|
/// (will be necessary for exception chaining, etc.). Opaque as far as
|
|
/// we are concerned here.
|
|
void* ehContext;
|
|
|
|
Context* within;
|
|
Context* next,
|
|
prev;
|
|
}
|
|
|
|
|
|
Context m_main;
|
|
Context* m_curr;
|
|
bool m_lock;
|
|
void* m_tlsgcdata;
|
|
|
|
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
|
|
{
|
|
static assert(false, "Architecture not supported." );
|
|
}
|
|
}
|
|
|
|
|
|
private:
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// GC Scanning Support
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
// NOTE: The GC scanning process works like so:
|
|
//
|
|
// 1. Suspend all threads.
|
|
// 2. Scan the stacks of all suspended threads for roots.
|
|
// 3. Resume all threads.
|
|
//
|
|
// Step 1 and 3 require a list of all threads in the system, while
|
|
// step 2 requires a list of all thread stacks (each represented by
|
|
// a Context struct). Traditionally, there was one stack per thread
|
|
// and the Context structs were not necessary. However, Fibers have
|
|
// changed things so that each thread has its own 'main' stack plus
|
|
// an arbitrary number of nested stacks (normally referenced via
|
|
// m_curr). Also, there may be 'free-floating' stacks in the system,
|
|
// which are Fibers that are not currently executing on any specific
|
|
// thread but are still being processed and still contain valid
|
|
// roots.
|
|
//
|
|
// To support all of this, the Context struct has been created to
|
|
// represent a stack range, and a global list of Context structs has
|
|
// been added to enable scanning of these stack ranges. The lifetime
|
|
// (and presence in the Context list) of a thread's 'main' stack will
|
|
// be equivalent to the thread's lifetime. So the Ccontext will be
|
|
// added to the list on thread entry, and removed from the list on
|
|
// thread exit (which is essentially the same as the presence of a
|
|
// Thread object in its own global list). The lifetime of a Fiber's
|
|
// context, however, will be tied to the lifetime of the Fiber object
|
|
// itself, and Fibers are expected to add/remove their Context struct
|
|
// on construction/deletion.
|
|
|
|
|
|
//
|
|
// All use of the global thread lists/array should synchronize on this lock.
|
|
//
|
|
// Careful as the GC acquires this lock after the GC lock to suspend all
|
|
// threads any GC usage with slock held can result in a deadlock through
|
|
// lock order inversion.
|
|
@property static Mutex slock() nothrow @nogc
|
|
{
|
|
return cast(Mutex)_slock.ptr;
|
|
}
|
|
|
|
@property static Mutex criticalRegionLock() nothrow @nogc
|
|
{
|
|
return cast(Mutex)_criticalRegionLock.ptr;
|
|
}
|
|
|
|
__gshared align(mutexAlign) void[mutexClassInstanceSize] _slock;
|
|
__gshared align(mutexAlign) void[mutexClassInstanceSize] _criticalRegionLock;
|
|
|
|
static void initLocks()
|
|
{
|
|
_slock[] = typeid(Mutex).initializer[];
|
|
(cast(Mutex)_slock.ptr).__ctor();
|
|
|
|
_criticalRegionLock[] = typeid(Mutex).initializer[];
|
|
(cast(Mutex)_criticalRegionLock.ptr).__ctor();
|
|
}
|
|
|
|
static void termLocks()
|
|
{
|
|
(cast(Mutex)_slock.ptr).__dtor();
|
|
(cast(Mutex)_criticalRegionLock.ptr).__dtor();
|
|
}
|
|
|
|
__gshared Context* sm_cbeg;
|
|
|
|
__gshared Thread sm_tbeg;
|
|
__gshared size_t sm_tlen;
|
|
|
|
// can't use rt.util.array in public code
|
|
__gshared Thread* pAboutToStart;
|
|
__gshared size_t nAboutToStart;
|
|
|
|
//
|
|
// Used for ordering threads in the global thread list.
|
|
//
|
|
Thread prev;
|
|
Thread next;
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Global Context List Operations
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
//
|
|
// Add a context to the global context list.
|
|
//
|
|
static void add( Context* c ) nothrow @nogc
|
|
in
|
|
{
|
|
assert( c );
|
|
assert( !c.next && !c.prev );
|
|
}
|
|
body
|
|
{
|
|
slock.lock_nothrow();
|
|
scope(exit) slock.unlock_nothrow();
|
|
assert(!suspendDepth); // must be 0 b/c it's only set with slock held
|
|
|
|
if (sm_cbeg)
|
|
{
|
|
c.next = sm_cbeg;
|
|
sm_cbeg.prev = c;
|
|
}
|
|
sm_cbeg = c;
|
|
}
|
|
|
|
|
|
//
|
|
// Remove a context from the global context list.
|
|
//
|
|
// This assumes slock being acquired. This isn't done here to
|
|
// avoid double locking when called from remove(Thread)
|
|
static void remove( Context* c ) nothrow @nogc
|
|
in
|
|
{
|
|
assert( c );
|
|
assert( c.next || c.prev );
|
|
}
|
|
body
|
|
{
|
|
if ( c.prev )
|
|
c.prev.next = c.next;
|
|
if ( c.next )
|
|
c.next.prev = c.prev;
|
|
if ( sm_cbeg == c )
|
|
sm_cbeg = c.next;
|
|
// NOTE: Don't null out c.next or c.prev because opApply currently
|
|
// follows c.next after removing a node. This could be easily
|
|
// addressed by simply returning the next node from this
|
|
// function, however, a context should never be re-added to the
|
|
// list anyway and having next and prev be non-null is a good way
|
|
// to ensure that.
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Global Thread List Operations
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
//
|
|
// Add a thread to the global thread list.
|
|
//
|
|
static void add( Thread t, bool rmAboutToStart = true ) nothrow @nogc
|
|
in
|
|
{
|
|
assert( t );
|
|
assert( !t.next && !t.prev );
|
|
}
|
|
body
|
|
{
|
|
slock.lock_nothrow();
|
|
scope(exit) slock.unlock_nothrow();
|
|
assert(t.isRunning); // check this with slock to ensure pthread_create already returned
|
|
assert(!suspendDepth); // must be 0 b/c it's only set with slock held
|
|
|
|
if (rmAboutToStart)
|
|
{
|
|
size_t idx = -1;
|
|
foreach (i, thr; pAboutToStart[0 .. nAboutToStart])
|
|
{
|
|
if (thr is t)
|
|
{
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
assert(idx != -1);
|
|
import core.stdc.string : memmove;
|
|
memmove(pAboutToStart + idx, pAboutToStart + idx + 1, Thread.sizeof * (nAboutToStart - idx - 1));
|
|
pAboutToStart =
|
|
cast(Thread*)realloc(pAboutToStart, Thread.sizeof * --nAboutToStart);
|
|
}
|
|
|
|
if (sm_tbeg)
|
|
{
|
|
t.next = sm_tbeg;
|
|
sm_tbeg.prev = t;
|
|
}
|
|
sm_tbeg = t;
|
|
++sm_tlen;
|
|
}
|
|
|
|
|
|
//
|
|
// Remove a thread from the global thread list.
|
|
//
|
|
static void remove( Thread t ) nothrow @nogc
|
|
in
|
|
{
|
|
assert( t );
|
|
}
|
|
body
|
|
{
|
|
// Thread was already removed earlier, might happen b/c of thread_detachInstance
|
|
if (!t.next && !t.prev)
|
|
return;
|
|
slock.lock_nothrow();
|
|
{
|
|
// NOTE: When a thread is removed from the global thread list its
|
|
// main context is invalid and should be removed as well.
|
|
// It is possible that t.m_curr could reference more
|
|
// than just the main context if the thread exited abnormally
|
|
// (if it was terminated), but we must assume that the user
|
|
// retains a reference to them and that they may be re-used
|
|
// elsewhere. Therefore, it is the responsibility of any
|
|
// object that creates contexts to clean them up properly
|
|
// when it is done with them.
|
|
remove( &t.m_main );
|
|
|
|
if ( t.prev )
|
|
t.prev.next = t.next;
|
|
if ( t.next )
|
|
t.next.prev = t.prev;
|
|
if ( sm_tbeg is t )
|
|
sm_tbeg = t.next;
|
|
t.prev = t.next = null;
|
|
--sm_tlen;
|
|
}
|
|
// NOTE: Don't null out t.next or t.prev because opApply currently
|
|
// follows t.next after removing a node. This could be easily
|
|
// addressed by simply returning the next node from this
|
|
// function, however, a thread should never be re-added to the
|
|
// list anyway and having next and prev be non-null is a good way
|
|
// to ensure that.
|
|
slock.unlock_nothrow();
|
|
}
|
|
}
|
|
|
|
///
|
|
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 );
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// 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(suspendSignalNumber == 0);
|
|
assert(resumeSignalNumber == 0);
|
|
assert(suspendSignalNo != 0);
|
|
assert(resumeSignalNo != 0);
|
|
}
|
|
out
|
|
{
|
|
assert(suspendSignalNumber != 0);
|
|
assert(resumeSignalNumber != 0);
|
|
}
|
|
body
|
|
{
|
|
suspendSignalNumber = suspendSignalNo;
|
|
resumeSignalNumber = resumeSignalNo;
|
|
}
|
|
}
|
|
|
|
version (Posix)
|
|
{
|
|
__gshared int suspendSignalNumber;
|
|
__gshared int resumeSignalNumber;
|
|
}
|
|
|
|
/**
|
|
* 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()
|
|
{
|
|
// 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.
|
|
|
|
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)
|
|
{
|
|
}
|
|
else version (Posix)
|
|
{
|
|
if ( suspendSignalNumber == 0 )
|
|
{
|
|
suspendSignalNumber = SIGUSR1;
|
|
}
|
|
|
|
if ( resumeSignalNumber == 0 )
|
|
{
|
|
resumeSignalNumber = SIGUSR2;
|
|
}
|
|
|
|
int status;
|
|
sigaction_t sigusr1 = void;
|
|
sigaction_t sigusr2 = 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*) &sigusr1)[0 .. sigaction_t.sizeof] = 0;
|
|
(cast(byte*) &sigusr2)[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 ) )
|
|
sigusr1.sa_flags = SA_RESTART;
|
|
else
|
|
sigusr1.sa_flags = 0;
|
|
sigusr1.sa_handler = &thread_suspendHandler;
|
|
// NOTE: We want to ignore all signals while in this handler, so fill
|
|
// sa_mask to indicate this.
|
|
status = sigfillset( &sigusr1.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.
|
|
sigusr2.sa_flags = 0;
|
|
sigusr2.sa_handler = &thread_resumeHandler;
|
|
// NOTE: We want to ignore all signals while in this handler, so fill
|
|
// sa_mask to indicate this.
|
|
status = sigfillset( &sigusr2.sa_mask );
|
|
assert( status == 0 );
|
|
|
|
status = sigaction( suspendSignalNumber, &sigusr1, null );
|
|
assert( status == 0 );
|
|
|
|
status = sigaction( resumeSignalNumber, &sigusr2, null );
|
|
assert( status == 0 );
|
|
|
|
status = sem_init( &suspendCount, 0, 0 );
|
|
assert( status == 0 );
|
|
}
|
|
Thread.sm_main = thread_attachThis();
|
|
}
|
|
|
|
|
|
/**
|
|
* Terminates the thread module. No other thread routine may be called
|
|
* afterwards.
|
|
*/
|
|
extern (C) void thread_term()
|
|
{
|
|
assert(Thread.sm_tbeg && Thread.sm_tlen == 1);
|
|
assert(!Thread.nAboutToStart);
|
|
if (Thread.pAboutToStart) // in case realloc(p, 0) doesn't return null
|
|
{
|
|
free(Thread.pAboutToStart);
|
|
Thread.pAboutToStart = null;
|
|
}
|
|
Thread.termLocks();
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
extern (C) bool thread_isMainThread() nothrow @nogc
|
|
{
|
|
return Thread.getThis() is Thread.sm_main;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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()
|
|
{
|
|
GC.disable(); scope(exit) GC.enable();
|
|
|
|
if (auto t = Thread.getThis())
|
|
return t;
|
|
|
|
Thread thisThread = new Thread();
|
|
Thread.Context* 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.m_isRunning, true);
|
|
}
|
|
thisThread.m_isDaemon = true;
|
|
thisThread.m_tlsgcdata = rt_tlsgc_init();
|
|
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;
|
|
}
|
|
|
|
|
|
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))
|
|
return t;
|
|
|
|
Thread thisThread = new Thread();
|
|
Thread.Context* 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.m_tlsgcdata = rt_tlsgc_init();
|
|
Thread.setThis( thisThread );
|
|
}
|
|
else
|
|
{
|
|
thisThread.m_hndl = OpenThreadHandle( addr );
|
|
impersonate_thread(addr,
|
|
{
|
|
thisThread.m_tlsgcdata = rt_tlsgc_init();
|
|
Thread.setThis( thisThread );
|
|
});
|
|
}
|
|
|
|
Thread.add( thisThread, false );
|
|
Thread.add( thisContext );
|
|
if ( Thread.sm_main !is null )
|
|
multiThreadedFlag = true;
|
|
return thisThread;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Deregisters the calling thread from use with the runtime. If this routine
|
|
* is called for a thread which is not registered, the result is undefined.
|
|
*
|
|
* NOTE: This routine does not run thread-local static destructors when called.
|
|
* If full functionality as a D thread is desired, the following function
|
|
* must be called after thread_detachThis, particularly if the thread is
|
|
* being detached at some indeterminate time before program termination:
|
|
*
|
|
* $(D extern(C) void rt_moduleTlsDtor();)
|
|
*/
|
|
extern (C) void thread_detachThis() nothrow @nogc
|
|
{
|
|
if (auto t = Thread.getThis())
|
|
Thread.remove(t);
|
|
}
|
|
|
|
|
|
/**
|
|
* Deregisters the given thread from use with the runtime. If this routine
|
|
* is called for a thread which is not registered, the result is undefined.
|
|
*
|
|
* NOTE: This routine does not run thread-local static destructors when called.
|
|
* If full functionality as a D thread is desired, the following function
|
|
* must be called by the detached thread, particularly if the thread is
|
|
* being detached at some indeterminate time before program termination:
|
|
*
|
|
* $(D extern(C) void rt_moduleTlsDtor();)
|
|
*/
|
|
extern (C) void thread_detachByAddr( ThreadID addr )
|
|
{
|
|
if ( auto t = thread_findByAddr( addr ) )
|
|
Thread.remove( t );
|
|
}
|
|
|
|
|
|
/// ditto
|
|
extern (C) void thread_detachInstance( Thread t ) nothrow @nogc
|
|
{
|
|
Thread.remove( t );
|
|
}
|
|
|
|
|
|
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();
|
|
}
|
|
|
|
|
|
/**
|
|
* Search the list of all threads for a thread with the given thread identifier.
|
|
*
|
|
* Params:
|
|
* addr = The thread identifier to search for.
|
|
* Returns:
|
|
* The thread object associated with the thread identifier, null if not found.
|
|
*/
|
|
static Thread thread_findByAddr( ThreadID addr )
|
|
{
|
|
Thread.slock.lock_nothrow();
|
|
scope(exit) Thread.slock.unlock_nothrow();
|
|
|
|
// also return just spawned thread so that
|
|
// DLL_THREAD_ATTACH knows it's a D thread
|
|
foreach (t; Thread.pAboutToStart[0 .. Thread.nAboutToStart])
|
|
if (t.m_addr == addr)
|
|
return t;
|
|
|
|
foreach (t; Thread)
|
|
if (t.m_addr == addr)
|
|
return t;
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the current thread to a specific reference. Only to be used
|
|
* when dealing with externally-created threads (in e.g. C code).
|
|
* The primary use of this function is when Thread.getThis() must
|
|
* return a sensible value in, for example, TLS destructors. In
|
|
* other words, don't touch this unless you know what you're doing.
|
|
*
|
|
* Params:
|
|
* t = A reference to the current thread. May be null.
|
|
*/
|
|
extern (C) void thread_setThis(Thread t) nothrow @nogc
|
|
{
|
|
Thread.setThis(t);
|
|
}
|
|
|
|
|
|
/**
|
|
* Joins all non-daemon threads that are currently running. This is done by
|
|
* performing successive scans through the thread list until a scan consists
|
|
* of only daemon threads.
|
|
*/
|
|
extern (C) void thread_joinAll()
|
|
{
|
|
Lagain:
|
|
Thread.slock.lock_nothrow();
|
|
// wait for just spawned threads
|
|
if (Thread.nAboutToStart)
|
|
{
|
|
Thread.slock.unlock_nothrow();
|
|
Thread.yield();
|
|
goto Lagain;
|
|
}
|
|
|
|
// join all non-daemon threads, the main thread is also a daemon
|
|
auto t = Thread.sm_tbeg;
|
|
while (t)
|
|
{
|
|
if (!t.isRunning)
|
|
{
|
|
auto tn = t.next;
|
|
Thread.remove(t);
|
|
t = tn;
|
|
}
|
|
else if (t.isDaemon)
|
|
{
|
|
t = t.next;
|
|
}
|
|
else
|
|
{
|
|
Thread.slock.unlock_nothrow();
|
|
t.join(); // might rethrow
|
|
goto Lagain; // must restart iteration b/c of unlock
|
|
}
|
|
}
|
|
Thread.slock.unlock_nothrow();
|
|
}
|
|
|
|
|
|
/**
|
|
* Performs intermediate shutdown of the thread module.
|
|
*/
|
|
shared static ~this()
|
|
{
|
|
// NOTE: The functionality related to garbage collection must be minimally
|
|
// operable after this dtor completes. Therefore, only minimal
|
|
// cleanup may occur.
|
|
auto t = Thread.sm_tbeg;
|
|
while (t)
|
|
{
|
|
auto tn = t.next;
|
|
if (!t.isRunning)
|
|
Thread.remove(t);
|
|
t = tn;
|
|
}
|
|
}
|
|
|
|
|
|
// Used for needLock below.
|
|
private __gshared bool multiThreadedFlag = false;
|
|
|
|
// Calls the given delegate, passing the current thread's stack pointer to it.
|
|
private void callWithStackShell(scope void delegate(void* sp) nothrow fn) nothrow
|
|
in
|
|
{
|
|
assert(fn);
|
|
}
|
|
body
|
|
{
|
|
// 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)
|
|
{
|
|
__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);
|
|
}
|
|
|
|
// Used for suspendAll/resumeAll below.
|
|
private __gshared uint suspendDepth = 0;
|
|
|
|
/**
|
|
* 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 bool suspend( Thread t ) nothrow
|
|
{
|
|
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
|
|
{
|
|
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;
|
|
auto t = Thread.sm_tbeg;
|
|
while (t)
|
|
{
|
|
auto tn = t.next;
|
|
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 = Thread.sm_tbeg;
|
|
while (t)
|
|
{
|
|
auto tn = t.next;
|
|
if (t.m_suspendagain && suspend(t))
|
|
++cnt;
|
|
t = tn;
|
|
}
|
|
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 void resume( Thread t ) nothrow
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resume all threads but the calling thread for "stop the world" garbage
|
|
* collection runs. This function must be called once for each preceding
|
|
* call to thread_suspendAll before the threads are actually resumed.
|
|
*
|
|
* In:
|
|
* This routine must be preceded by a call to thread_suspendAll.
|
|
*
|
|
* Throws:
|
|
* ThreadError if the resume operation fails for a running thread.
|
|
*/
|
|
extern (C) void thread_resumeAll() nothrow
|
|
in
|
|
{
|
|
assert( suspendDepth > 0 );
|
|
}
|
|
body
|
|
{
|
|
// NOTE: See thread_suspendAll for the logic behind this.
|
|
if ( !multiThreadedFlag && Thread.sm_tbeg )
|
|
{
|
|
if ( --suspendDepth == 0 )
|
|
resume( Thread.getThis() );
|
|
return;
|
|
}
|
|
|
|
scope(exit) Thread.slock.unlock_nothrow();
|
|
{
|
|
if ( --suspendDepth > 0 )
|
|
return;
|
|
|
|
for ( Thread t = Thread.sm_tbeg; t; t = t.next )
|
|
{
|
|
// NOTE: We do not need to care about critical regions at all
|
|
// here. thread_suspendAll takes care of everything.
|
|
resume( t );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Indicates the kind of scan being performed by $(D thread_scanAllType).
|
|
*/
|
|
enum ScanType
|
|
{
|
|
stack, /// The stack and/or registers are being scanned.
|
|
tls, /// TLS data is being scanned.
|
|
}
|
|
|
|
alias ScanAllThreadsFn = void delegate(void*, void*) nothrow; /// The scanning function.
|
|
alias ScanAllThreadsTypeFn = void delegate(ScanType, void*, void*) nothrow; /// ditto
|
|
|
|
/**
|
|
* The main entry point for garbage collection. The supplied delegate
|
|
* will be passed ranges representing both stack and register values.
|
|
*
|
|
* Params:
|
|
* scan = The scanner function. It should scan from p1 through p2 - 1.
|
|
*
|
|
* In:
|
|
* This routine must be preceded by a call to thread_suspendAll.
|
|
*/
|
|
extern (C) void thread_scanAllType( scope ScanAllThreadsTypeFn scan ) nothrow
|
|
in
|
|
{
|
|
assert( suspendDepth > 0 );
|
|
}
|
|
body
|
|
{
|
|
callWithStackShell(sp => scanAllTypeImpl(scan, sp));
|
|
}
|
|
|
|
|
|
private void scanAllTypeImpl( scope ScanAllThreadsTypeFn scan, void* curStackTop ) nothrow
|
|
{
|
|
Thread thisThread = null;
|
|
void* oldStackTop = null;
|
|
|
|
if ( Thread.sm_tbeg )
|
|
{
|
|
thisThread = Thread.getThis();
|
|
if ( !thisThread.m_lock )
|
|
{
|
|
oldStackTop = thisThread.m_curr.tstack;
|
|
thisThread.m_curr.tstack = curStackTop;
|
|
}
|
|
}
|
|
|
|
scope( exit )
|
|
{
|
|
if ( Thread.sm_tbeg )
|
|
{
|
|
if ( !thisThread.m_lock )
|
|
{
|
|
thisThread.m_curr.tstack = oldStackTop;
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: Synchronizing on Thread.slock is not needed because this
|
|
// function may only be called after all other threads have
|
|
// been suspended from within the same lock.
|
|
if (Thread.nAboutToStart)
|
|
scan(ScanType.stack, Thread.pAboutToStart, Thread.pAboutToStart + Thread.nAboutToStart);
|
|
|
|
for ( Thread.Context* c = Thread.sm_cbeg; c; c = c.next )
|
|
{
|
|
version (StackGrowsDown)
|
|
{
|
|
// NOTE: We can't index past the bottom of the stack
|
|
// so don't do the "+1" for StackGrowsDown.
|
|
if ( c.tstack && c.tstack < c.bstack )
|
|
scan( ScanType.stack, c.tstack, c.bstack );
|
|
}
|
|
else
|
|
{
|
|
if ( c.bstack && c.bstack < c.tstack )
|
|
scan( ScanType.stack, c.bstack, c.tstack + 1 );
|
|
}
|
|
}
|
|
|
|
for ( Thread t = Thread.sm_tbeg; t; t = t.next )
|
|
{
|
|
version (Windows)
|
|
{
|
|
// Ideally, we'd pass ScanType.regs or something like that, but this
|
|
// would make portability annoying because it only makes sense on Windows.
|
|
scan( ScanType.stack, t.m_reg.ptr, t.m_reg.ptr + t.m_reg.length );
|
|
}
|
|
|
|
if (t.m_tlsgcdata !is null)
|
|
rt_tlsgc_scan(t.m_tlsgcdata, (p1, p2) => scan(ScanType.tls, p1, p2));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The main entry point for garbage collection. The supplied delegate
|
|
* will be passed ranges representing both stack and register values.
|
|
*
|
|
* Params:
|
|
* scan = The scanner function. It should scan from p1 through p2 - 1.
|
|
*
|
|
* In:
|
|
* This routine must be preceded by a call to thread_suspendAll.
|
|
*/
|
|
extern (C) void thread_scanAll( scope ScanAllThreadsFn scan ) nothrow
|
|
{
|
|
thread_scanAllType((type, p1, p2) => scan(p1, p2));
|
|
}
|
|
|
|
|
|
/**
|
|
* Signals that the code following this call is a critical region. Any code in
|
|
* this region must finish running before the calling thread can be suspended
|
|
* by a call to thread_suspendAll.
|
|
*
|
|
* This function is, in particular, meant to help maintain garbage collector
|
|
* invariants when a lock is not used.
|
|
*
|
|
* A critical region is exited with thread_exitCriticalRegion.
|
|
*
|
|
* $(RED Warning):
|
|
* Using critical regions is extremely error-prone. For instance, using locks
|
|
* inside a critical region can easily result in a deadlock when another thread
|
|
* holding the lock already got suspended.
|
|
*
|
|
* The term and concept of a 'critical region' comes from
|
|
* $(LINK2 https://github.com/mono/mono/blob/521f4a198e442573c400835ef19bbb36b60b0ebb/mono/metadata/sgen-gc.h#L925 Mono's SGen garbage collector).
|
|
*
|
|
* In:
|
|
* The calling thread must be attached to the runtime.
|
|
*/
|
|
extern (C) void thread_enterCriticalRegion() @nogc
|
|
in
|
|
{
|
|
assert(Thread.getThis());
|
|
}
|
|
body
|
|
{
|
|
synchronized (Thread.criticalRegionLock)
|
|
Thread.getThis().m_isInCriticalRegion = true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Signals that the calling thread is no longer in a critical region. Following
|
|
* a call to this function, the thread can once again be suspended.
|
|
*
|
|
* In:
|
|
* The calling thread must be attached to the runtime.
|
|
*/
|
|
extern (C) void thread_exitCriticalRegion() @nogc
|
|
in
|
|
{
|
|
assert(Thread.getThis());
|
|
}
|
|
body
|
|
{
|
|
synchronized (Thread.criticalRegionLock)
|
|
Thread.getThis().m_isInCriticalRegion = false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns true if the current thread is in a critical region; otherwise, false.
|
|
*
|
|
* In:
|
|
* The calling thread must be attached to the runtime.
|
|
*/
|
|
extern (C) bool thread_inCriticalRegion() @nogc
|
|
in
|
|
{
|
|
assert(Thread.getThis());
|
|
}
|
|
body
|
|
{
|
|
synchronized (Thread.criticalRegionLock)
|
|
return Thread.getThis().m_isInCriticalRegion;
|
|
}
|
|
|
|
|
|
/**
|
|
* A callback for thread errors in D during collections. Since an allocation is not possible
|
|
* a preallocated ThreadError will be used as the Error instance
|
|
*
|
|
* Throws:
|
|
* ThreadError.
|
|
*/
|
|
private void onThreadError(string msg = null, Throwable next = null) nothrow
|
|
{
|
|
__gshared ThreadError error = new ThreadError(null);
|
|
error.msg = msg;
|
|
error.next = next;
|
|
import core.exception : SuppressTraceInfo;
|
|
error.info = SuppressTraceInfo.instance;
|
|
throw error;
|
|
}
|
|
|
|
|
|
unittest
|
|
{
|
|
assert(!thread_inCriticalRegion());
|
|
|
|
{
|
|
thread_enterCriticalRegion();
|
|
|
|
scope (exit)
|
|
thread_exitCriticalRegion();
|
|
|
|
assert(thread_inCriticalRegion());
|
|
}
|
|
|
|
assert(!thread_inCriticalRegion());
|
|
}
|
|
|
|
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 (Thread.criticalRegionLock)
|
|
assert(thr.m_isInCriticalRegion);
|
|
semb.notify();
|
|
|
|
sema.wait();
|
|
synchronized (Thread.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();
|
|
}
|
|
|
|
/**
|
|
* Indicates whether an address has been marked by the GC.
|
|
*/
|
|
enum IsMarked : int
|
|
{
|
|
no, /// Address is not marked.
|
|
yes, /// Address is marked.
|
|
unknown, /// Address is not managed by the GC.
|
|
}
|
|
|
|
alias IsMarkedDg = int delegate( void* addr ) nothrow; /// The isMarked callback function.
|
|
|
|
/**
|
|
* This routine allows the runtime to process any special per-thread handling
|
|
* for the GC. This is needed for taking into account any memory that is
|
|
* referenced by non-scanned pointers but is about to be freed. That currently
|
|
* means the array append cache.
|
|
*
|
|
* Params:
|
|
* isMarked = The function used to check if $(D addr) is marked.
|
|
*
|
|
* In:
|
|
* This routine must be called just prior to resuming all threads.
|
|
*/
|
|
extern(C) void thread_processGCMarks( scope IsMarkedDg isMarked ) nothrow
|
|
{
|
|
for ( Thread t = Thread.sm_tbeg; t; t = t.next )
|
|
{
|
|
/* Can be null if collection was triggered between adding a
|
|
* thread and calling rt_tlsgc_init.
|
|
*/
|
|
if (t.m_tlsgcdata !is null)
|
|
rt_tlsgc_processGCMarks(t.m_tlsgcdata, isMarked);
|
|
}
|
|
}
|
|
|
|
|
|
extern (C) @nogc nothrow
|
|
{
|
|
version (CRuntime_Glibc) int pthread_getattr_np(pthread_t thread, pthread_attr_t* attr);
|
|
version (FreeBSD) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr);
|
|
version (NetBSD) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr);
|
|
version (OpenBSD) int pthread_stackseg_np(pthread_t thread, stack_t* sinfo);
|
|
version (DragonFlyBSD) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr);
|
|
version (Solaris) int thr_stksegment(stack_t* stk);
|
|
version (CRuntime_Bionic) int pthread_getattr_np(pthread_t thid, pthread_attr_t* attr);
|
|
version (CRuntime_Musl) int pthread_getattr_np(pthread_t, pthread_attr_t*);
|
|
version (CRuntime_UClibc) int pthread_getattr_np(pthread_t thread, pthread_attr_t* attr);
|
|
}
|
|
|
|
|
|
private 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.");
|
|
}
|
|
|
|
|
|
private 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 (CRuntime_Glibc)
|
|
{
|
|
pthread_attr_t attr;
|
|
void* addr; size_t size;
|
|
|
|
pthread_getattr_np(pthread_self(), &attr);
|
|
pthread_attr_getstack(&attr, &addr, &size);
|
|
pthread_attr_destroy(&attr);
|
|
version (StackGrowsDown)
|
|
addr += size;
|
|
return addr;
|
|
}
|
|
else version (FreeBSD)
|
|
{
|
|
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);
|
|
version (StackGrowsDown)
|
|
addr += size;
|
|
return addr;
|
|
}
|
|
else version (NetBSD)
|
|
{
|
|
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);
|
|
version (StackGrowsDown)
|
|
addr += size;
|
|
return addr;
|
|
}
|
|
else version (OpenBSD)
|
|
{
|
|
stack_t stk;
|
|
|
|
pthread_stackseg_np(pthread_self(), &stk);
|
|
return stk.ss_sp;
|
|
}
|
|
else version (DragonFlyBSD)
|
|
{
|
|
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);
|
|
version (StackGrowsDown)
|
|
addr += size;
|
|
return addr;
|
|
}
|
|
else version (Solaris)
|
|
{
|
|
stack_t stk;
|
|
|
|
thr_stksegment(&stk);
|
|
return stk.ss_sp;
|
|
}
|
|
else version (CRuntime_Bionic)
|
|
{
|
|
pthread_attr_t attr;
|
|
void* addr; size_t size;
|
|
|
|
pthread_getattr_np(pthread_self(), &attr);
|
|
pthread_attr_getstack(&attr, &addr, &size);
|
|
pthread_attr_destroy(&attr);
|
|
version (StackGrowsDown)
|
|
addr += size;
|
|
return addr;
|
|
}
|
|
else version (CRuntime_Musl)
|
|
{
|
|
pthread_attr_t attr;
|
|
void* addr; size_t size;
|
|
|
|
pthread_getattr_np(pthread_self(), &attr);
|
|
pthread_attr_getstack(&attr, &addr, &size);
|
|
pthread_attr_destroy(&attr);
|
|
version (StackGrowsDown)
|
|
addr += size;
|
|
return addr;
|
|
}
|
|
else version (CRuntime_UClibc)
|
|
{
|
|
pthread_attr_t attr;
|
|
void* addr; size_t size;
|
|
|
|
pthread_getattr_np(pthread_self(), &attr);
|
|
pthread_attr_getstack(&attr, &addr, &size);
|
|
pthread_attr_destroy(&attr);
|
|
version (StackGrowsDown)
|
|
addr += size;
|
|
return addr;
|
|
}
|
|
else
|
|
static assert(false, "Platform not supported.");
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the stack top of the currently active stack within the calling
|
|
* thread.
|
|
*
|
|
* In:
|
|
* The calling thread must be attached to the runtime.
|
|
*
|
|
* Returns:
|
|
* The address of the stack top.
|
|
*/
|
|
extern (C) void* thread_stackTop() nothrow @nogc
|
|
in
|
|
{
|
|
// Not strictly required, but it gives us more flexibility.
|
|
assert(Thread.getThis());
|
|
}
|
|
body
|
|
{
|
|
return getStackTop();
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the stack bottom of the currently active stack within the calling
|
|
* thread.
|
|
*
|
|
* In:
|
|
* The calling thread must be attached to the runtime.
|
|
*
|
|
* Returns:
|
|
* The address of the stack bottom.
|
|
*/
|
|
extern (C) void* thread_stackBottom() nothrow @nogc
|
|
in
|
|
{
|
|
assert(Thread.getThis());
|
|
}
|
|
body
|
|
{
|
|
return Thread.getThis().topContext().bstack;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Thread Group
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* This class is intended to simplify certain common programming techniques.
|
|
*/
|
|
class ThreadGroup
|
|
{
|
|
/**
|
|
* Creates and starts a new Thread object that executes fn and adds it to
|
|
* the list of tracked threads.
|
|
*
|
|
* Params:
|
|
* fn = The thread function.
|
|
*
|
|
* Returns:
|
|
* A reference to the newly created thread.
|
|
*/
|
|
final Thread create( void function() fn )
|
|
{
|
|
Thread t = new Thread( fn ).start();
|
|
|
|
synchronized( this )
|
|
{
|
|
m_all[t] = t;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates and starts a new Thread object that executes dg and adds it to
|
|
* the list of tracked threads.
|
|
*
|
|
* Params:
|
|
* dg = The thread function.
|
|
*
|
|
* Returns:
|
|
* A reference to the newly created thread.
|
|
*/
|
|
final Thread create( void delegate() dg )
|
|
{
|
|
Thread t = new Thread( dg ).start();
|
|
|
|
synchronized( this )
|
|
{
|
|
m_all[t] = t;
|
|
}
|
|
return t;
|
|
}
|
|
|
|
|
|
/**
|
|
* Add t to the list of tracked threads if it is not already being tracked.
|
|
*
|
|
* Params:
|
|
* t = The thread to add.
|
|
*
|
|
* In:
|
|
* t must not be null.
|
|
*/
|
|
final void add( Thread t )
|
|
in
|
|
{
|
|
assert( t );
|
|
}
|
|
body
|
|
{
|
|
synchronized( this )
|
|
{
|
|
m_all[t] = t;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Removes t from the list of tracked threads. No operation will be
|
|
* performed if t is not currently being tracked by this object.
|
|
*
|
|
* Params:
|
|
* t = The thread to remove.
|
|
*
|
|
* In:
|
|
* t must not be null.
|
|
*/
|
|
final void remove( Thread t )
|
|
in
|
|
{
|
|
assert( t );
|
|
}
|
|
body
|
|
{
|
|
synchronized( this )
|
|
{
|
|
m_all.remove( t );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Operates on all threads currently tracked by this object.
|
|
*/
|
|
final int opApply( scope int delegate( ref Thread ) dg )
|
|
{
|
|
synchronized( this )
|
|
{
|
|
int ret = 0;
|
|
|
|
// NOTE: This loop relies on the knowledge that m_all uses the
|
|
// Thread object for both the key and the mapped value.
|
|
foreach ( Thread t; m_all.keys )
|
|
{
|
|
ret = dg( t );
|
|
if ( ret )
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Iteratively joins all tracked threads. This function will block add,
|
|
* remove, and opApply until it completes.
|
|
*
|
|
* Params:
|
|
* rethrow = Rethrow any unhandled exception which may have caused the
|
|
* current thread to terminate.
|
|
*
|
|
* Throws:
|
|
* Any exception not handled by the joined threads.
|
|
*/
|
|
final void joinAll( bool rethrow = true )
|
|
{
|
|
synchronized( this )
|
|
{
|
|
// NOTE: This loop relies on the knowledge that m_all uses the
|
|
// Thread object for both the key and the mapped value.
|
|
foreach ( Thread t; m_all.keys )
|
|
{
|
|
t.join( rethrow );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private:
|
|
Thread[Thread] m_all;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Fiber Platform Detection and Memory Allocation
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
private
|
|
{
|
|
version (D_InlineAsm_X86)
|
|
{
|
|
version (Windows)
|
|
version = AsmX86_Windows;
|
|
else version (Posix)
|
|
version = AsmX86_Posix;
|
|
|
|
version (Darwin)
|
|
version = AlignFiberStackTo16Byte;
|
|
}
|
|
else version (D_InlineAsm_X86_64)
|
|
{
|
|
version (Windows)
|
|
{
|
|
version = AsmX86_64_Windows;
|
|
version = AlignFiberStackTo16Byte;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
version = AsmX86_64_Posix;
|
|
version = AlignFiberStackTo16Byte;
|
|
}
|
|
}
|
|
else version (X86)
|
|
{
|
|
version = AlignFiberStackTo16Byte;
|
|
|
|
version (CET)
|
|
{
|
|
// fiber_switchContext does not support shadow stack from
|
|
// Intel CET. So use ucontext implementation.
|
|
}
|
|
else
|
|
{
|
|
version = AsmExternal;
|
|
|
|
version (MinGW)
|
|
version = GNU_AsmX86_Windows;
|
|
else version (Posix)
|
|
version = AsmX86_Posix;
|
|
}
|
|
}
|
|
else version (X86_64)
|
|
{
|
|
version = AlignFiberStackTo16Byte;
|
|
|
|
version (CET)
|
|
{
|
|
// fiber_switchContext does not support shadow stack from
|
|
// Intel CET. So use ucontext implementation.
|
|
}
|
|
else version (D_X32)
|
|
{
|
|
// let X32 be handled by ucontext swapcontext
|
|
}
|
|
else
|
|
{
|
|
version = AsmExternal;
|
|
|
|
version (MinGW)
|
|
version = GNU_AsmX86_64_Windows;
|
|
else version (Posix)
|
|
version = AsmX86_64_Posix;
|
|
}
|
|
}
|
|
else version (PPC)
|
|
{
|
|
version (Posix)
|
|
{
|
|
version = AsmPPC_Posix;
|
|
version = AsmExternal;
|
|
}
|
|
}
|
|
else version (PPC64)
|
|
{
|
|
version (Posix)
|
|
{
|
|
version = AlignFiberStackTo16Byte;
|
|
}
|
|
}
|
|
else version (MIPS_O32)
|
|
{
|
|
version (Posix)
|
|
{
|
|
version = AsmMIPS_O32_Posix;
|
|
version = AsmExternal;
|
|
}
|
|
}
|
|
else version (AArch64)
|
|
{
|
|
version (Posix)
|
|
{
|
|
version = AsmAArch64_Posix;
|
|
version = AsmExternal;
|
|
version = AlignFiberStackTo16Byte;
|
|
}
|
|
}
|
|
else version (ARM)
|
|
{
|
|
version (Posix)
|
|
{
|
|
version = AsmARM_Posix;
|
|
version = AsmExternal;
|
|
}
|
|
}
|
|
else version (SPARC)
|
|
{
|
|
// NOTE: The SPARC ABI specifies only doubleword alignment.
|
|
version = AlignFiberStackTo16Byte;
|
|
}
|
|
else version (SPARC64)
|
|
{
|
|
version = AlignFiberStackTo16Byte;
|
|
}
|
|
|
|
version (Posix)
|
|
{
|
|
import core.sys.posix.unistd; // for sysconf
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
static immutable size_t PAGESIZE;
|
|
version (Posix) static immutable size_t PTHREAD_STACK_MIN;
|
|
}
|
|
|
|
|
|
shared static this()
|
|
{
|
|
version (Windows)
|
|
{
|
|
SYSTEM_INFO info;
|
|
GetSystemInfo(&info);
|
|
|
|
PAGESIZE = info.dwPageSize;
|
|
assert(PAGESIZE < int.max);
|
|
}
|
|
else version (Posix)
|
|
{
|
|
PAGESIZE = cast(size_t)sysconf(_SC_PAGESIZE);
|
|
PTHREAD_STACK_MIN = cast(size_t)sysconf(_SC_THREAD_STACK_MIN);
|
|
}
|
|
else
|
|
{
|
|
static assert(0, "unimplemented");
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Fiber Entry Point and Context Switch
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
private
|
|
{
|
|
extern (C) void fiber_entryPoint() nothrow
|
|
{
|
|
Fiber obj = Fiber.getThis();
|
|
assert( obj );
|
|
|
|
assert( Thread.getThis().m_curr is obj.m_ctxt );
|
|
atomicStore!(MemoryOrder.raw)(*cast(shared)&Thread.getThis().m_lock, false);
|
|
obj.m_ctxt.tstack = obj.m_ctxt.bstack;
|
|
obj.m_state = Fiber.State.EXEC;
|
|
|
|
try
|
|
{
|
|
obj.run();
|
|
}
|
|
catch ( Throwable t )
|
|
{
|
|
obj.m_unhandled = t;
|
|
}
|
|
|
|
static if ( __traits( compiles, ucontext_t ) )
|
|
obj.m_ucur = &obj.m_utxt;
|
|
|
|
obj.m_state = Fiber.State.TERM;
|
|
obj.switchOut();
|
|
}
|
|
|
|
// Look above the definition of 'class Fiber' for some information about the implementation of this routine
|
|
version (AsmExternal)
|
|
{
|
|
extern (C) void fiber_switchContext( void** oldp, void* newp ) nothrow @nogc;
|
|
version (AArch64)
|
|
extern (C) void fiber_trampoline() nothrow;
|
|
}
|
|
else
|
|
extern (C) void fiber_switchContext( void** oldp, void* newp ) nothrow @nogc
|
|
{
|
|
// NOTE: The data pushed and popped in this routine must match the
|
|
// default stack created by Fiber.initStack or the initial
|
|
// switch into a new context will fail.
|
|
|
|
version (AsmX86_Windows)
|
|
{
|
|
asm pure nothrow @nogc
|
|
{
|
|
naked;
|
|
|
|
// save current stack state
|
|
push EBP;
|
|
mov EBP, ESP;
|
|
push EDI;
|
|
push ESI;
|
|
push EBX;
|
|
push dword ptr FS:[0];
|
|
push dword ptr FS:[4];
|
|
push dword ptr FS:[8];
|
|
push EAX;
|
|
|
|
// store oldp again with more accurate address
|
|
mov EAX, dword ptr 8[EBP];
|
|
mov [EAX], ESP;
|
|
// load newp to begin context switch
|
|
mov ESP, dword ptr 12[EBP];
|
|
|
|
// load saved state from new stack
|
|
pop EAX;
|
|
pop dword ptr FS:[8];
|
|
pop dword ptr FS:[4];
|
|
pop dword ptr FS:[0];
|
|
pop EBX;
|
|
pop ESI;
|
|
pop EDI;
|
|
pop EBP;
|
|
|
|
// 'return' to complete switch
|
|
pop ECX;
|
|
jmp ECX;
|
|
}
|
|
}
|
|
else version (AsmX86_64_Windows)
|
|
{
|
|
asm pure nothrow @nogc
|
|
{
|
|
naked;
|
|
|
|
// save current stack state
|
|
// NOTE: When changing the layout of registers on the stack,
|
|
// make sure that the XMM registers are still aligned.
|
|
// On function entry, the stack is guaranteed to not
|
|
// be aligned to 16 bytes because of the return address
|
|
// on the stack.
|
|
push RBP;
|
|
mov RBP, RSP;
|
|
push R12;
|
|
push R13;
|
|
push R14;
|
|
push R15;
|
|
push RDI;
|
|
push RSI;
|
|
// 7 registers = 56 bytes; stack is now aligned to 16 bytes
|
|
sub RSP, 160;
|
|
movdqa [RSP + 144], XMM6;
|
|
movdqa [RSP + 128], XMM7;
|
|
movdqa [RSP + 112], XMM8;
|
|
movdqa [RSP + 96], XMM9;
|
|
movdqa [RSP + 80], XMM10;
|
|
movdqa [RSP + 64], XMM11;
|
|
movdqa [RSP + 48], XMM12;
|
|
movdqa [RSP + 32], XMM13;
|
|
movdqa [RSP + 16], XMM14;
|
|
movdqa [RSP], XMM15;
|
|
push RBX;
|
|
xor RAX,RAX;
|
|
push qword ptr GS:[RAX];
|
|
push qword ptr GS:8[RAX];
|
|
push qword ptr GS:16[RAX];
|
|
|
|
// store oldp
|
|
mov [RCX], RSP;
|
|
// load newp to begin context switch
|
|
mov RSP, RDX;
|
|
|
|
// load saved state from new stack
|
|
pop qword ptr GS:16[RAX];
|
|
pop qword ptr GS:8[RAX];
|
|
pop qword ptr GS:[RAX];
|
|
pop RBX;
|
|
movdqa XMM15, [RSP];
|
|
movdqa XMM14, [RSP + 16];
|
|
movdqa XMM13, [RSP + 32];
|
|
movdqa XMM12, [RSP + 48];
|
|
movdqa XMM11, [RSP + 64];
|
|
movdqa XMM10, [RSP + 80];
|
|
movdqa XMM9, [RSP + 96];
|
|
movdqa XMM8, [RSP + 112];
|
|
movdqa XMM7, [RSP + 128];
|
|
movdqa XMM6, [RSP + 144];
|
|
add RSP, 160;
|
|
pop RSI;
|
|
pop RDI;
|
|
pop R15;
|
|
pop R14;
|
|
pop R13;
|
|
pop R12;
|
|
pop RBP;
|
|
|
|
// 'return' to complete switch
|
|
pop RCX;
|
|
jmp RCX;
|
|
}
|
|
}
|
|
else version (AsmX86_Posix)
|
|
{
|
|
asm pure nothrow @nogc
|
|
{
|
|
naked;
|
|
|
|
// save current stack state
|
|
push EBP;
|
|
mov EBP, ESP;
|
|
push EDI;
|
|
push ESI;
|
|
push EBX;
|
|
push EAX;
|
|
|
|
// store oldp again with more accurate address
|
|
mov EAX, dword ptr 8[EBP];
|
|
mov [EAX], ESP;
|
|
// load newp to begin context switch
|
|
mov ESP, dword ptr 12[EBP];
|
|
|
|
// load saved state from new stack
|
|
pop EAX;
|
|
pop EBX;
|
|
pop ESI;
|
|
pop EDI;
|
|
pop EBP;
|
|
|
|
// 'return' to complete switch
|
|
pop ECX;
|
|
jmp ECX;
|
|
}
|
|
}
|
|
else version (AsmX86_64_Posix)
|
|
{
|
|
asm pure nothrow @nogc
|
|
{
|
|
naked;
|
|
|
|
// save current stack state
|
|
push RBP;
|
|
mov RBP, RSP;
|
|
push RBX;
|
|
push R12;
|
|
push R13;
|
|
push R14;
|
|
push R15;
|
|
|
|
// store oldp
|
|
mov [RDI], RSP;
|
|
// load newp to begin context switch
|
|
mov RSP, RSI;
|
|
|
|
// load saved state from new stack
|
|
pop R15;
|
|
pop R14;
|
|
pop R13;
|
|
pop R12;
|
|
pop RBX;
|
|
pop RBP;
|
|
|
|
// 'return' to complete switch
|
|
pop RCX;
|
|
jmp RCX;
|
|
}
|
|
}
|
|
else static if ( __traits( compiles, ucontext_t ) )
|
|
{
|
|
Fiber cfib = Fiber.getThis();
|
|
void* ucur = cfib.m_ucur;
|
|
|
|
*oldp = &ucur;
|
|
swapcontext( **(cast(ucontext_t***) oldp),
|
|
*(cast(ucontext_t**) newp) );
|
|
}
|
|
else
|
|
static assert(0, "Not implemented");
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Fiber
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/*
|
|
* Documentation of Fiber internals:
|
|
*
|
|
* The main routines to implement when porting Fibers to new architectures are
|
|
* fiber_switchContext and initStack. Some version constants have to be defined
|
|
* for the new platform as well, search for "Fiber Platform Detection and Memory Allocation".
|
|
*
|
|
* Fibers are based on a concept called 'Context'. A Context describes the execution
|
|
* state of a Fiber or main thread which is fully described by the stack, some
|
|
* registers and a return address at which the Fiber/Thread should continue executing.
|
|
* Please note that not only each Fiber has a Context, but each thread also has got a
|
|
* Context which describes the threads stack and state. If you call Fiber fib; fib.call
|
|
* the first time in a thread you switch from Threads Context into the Fibers Context.
|
|
* If you call fib.yield in that Fiber you switch out of the Fibers context and back
|
|
* into the Thread Context. (However, this is not always the case. You can call a Fiber
|
|
* from within another Fiber, then you switch Contexts between the Fibers and the Thread
|
|
* Context is not involved)
|
|
*
|
|
* In all current implementations the registers and the return address are actually
|
|
* saved on a Contexts stack.
|
|
*
|
|
* The fiber_switchContext routine has got two parameters:
|
|
* void** a: This is the _location_ where we have to store the current stack pointer,
|
|
* the stack pointer of the currently executing Context (Fiber or Thread).
|
|
* void* b: This is the pointer to the stack of the Context which we want to switch into.
|
|
* Note that we get the same pointer here as the one we stored into the void** a
|
|
* in a previous call to fiber_switchContext.
|
|
*
|
|
* In the simplest case, a fiber_switchContext rountine looks like this:
|
|
* fiber_switchContext:
|
|
* push {return Address}
|
|
* push {registers}
|
|
* copy {stack pointer} into {location pointed to by a}
|
|
* //We have now switch to the stack of a different Context!
|
|
* copy {b} into {stack pointer}
|
|
* pop {registers}
|
|
* pop {return Address}
|
|
* jump to {return Address}
|
|
*
|
|
* The GC uses the value returned in parameter a to scan the Fibers stack. It scans from
|
|
* the stack base to that value. As the GC dislikes false pointers we can actually optimize
|
|
* this a little: By storing registers which can not contain references to memory managed
|
|
* by the GC outside of the region marked by the stack base pointer and the stack pointer
|
|
* saved in fiber_switchContext we can prevent the GC from scanning them.
|
|
* Such registers are usually floating point registers and the return address. In order to
|
|
* implement this, we return a modified stack pointer from fiber_switchContext. However,
|
|
* we have to remember that when we restore the registers from the stack!
|
|
*
|
|
* --------------------------- <= Stack Base
|
|
* | Frame | <= Many other stack frames
|
|
* | Frame |
|
|
* |-------------------------| <= The last stack frame. This one is created by fiber_switchContext
|
|
* | registers with pointers |
|
|
* | | <= Stack pointer. GC stops scanning here
|
|
* | return address |
|
|
* |floating point registers |
|
|
* --------------------------- <= Real Stack End
|
|
*
|
|
* fiber_switchContext:
|
|
* push {registers with pointers}
|
|
* copy {stack pointer} into {location pointed to by a}
|
|
* push {return Address}
|
|
* push {Floating point registers}
|
|
* //We have now switch to the stack of a different Context!
|
|
* copy {b} into {stack pointer}
|
|
* //We now have to adjust the stack pointer to point to 'Real Stack End' so we can pop
|
|
* //the FP registers
|
|
* //+ or - depends on if your stack grows downwards or upwards
|
|
* {stack pointer} = {stack pointer} +- ({FPRegisters}.sizeof + {return address}.sizeof}
|
|
* pop {Floating point registers}
|
|
* pop {return Address}
|
|
* pop {registers with pointers}
|
|
* jump to {return Address}
|
|
*
|
|
* So the question now is which registers need to be saved? This depends on the specific
|
|
* architecture ABI of course, but here are some general guidelines:
|
|
* - If a register is callee-save (if the callee modifies the register it must saved and
|
|
* restored by the callee) it needs to be saved/restored in switchContext
|
|
* - If a register is caller-save it needn't be saved/restored. (Calling fiber_switchContext
|
|
* is a function call and the compiler therefore already must save these registers before
|
|
* calling fiber_switchContext)
|
|
* - Argument registers used for passing parameters to functions needn't be saved/restored
|
|
* - The return register needn't be saved/restored (fiber_switchContext hasn't got a return type)
|
|
* - All scratch registers needn't be saved/restored
|
|
* - The link register usually needn't be saved/restored (but sometimes it must be cleared -
|
|
* see below for details)
|
|
* - The frame pointer register - if it exists - is usually callee-save
|
|
* - All current implementations do not save control registers
|
|
*
|
|
* What happens on the first switch into a Fiber? We never saved a state for this fiber before,
|
|
* but the initial state is prepared in the initStack routine. (This routine will also be called
|
|
* when a Fiber is being resetted). initStack must produce exactly the same stack layout as the
|
|
* part of fiber_switchContext which saves the registers. Pay special attention to set the stack
|
|
* pointer correctly if you use the GC optimization mentioned before. the return Address saved in
|
|
* initStack must be the address of fiber_entrypoint.
|
|
*
|
|
* There's now a small but important difference between the first context switch into a fiber and
|
|
* further context switches. On the first switch, Fiber.call is used and the returnAddress in
|
|
* fiber_switchContext will point to fiber_entrypoint. The important thing here is that this jump
|
|
* is a _function call_, we call fiber_entrypoint by jumping before it's function prologue. On later
|
|
* calls, the user used yield() in a function, and therefore the return address points into a user
|
|
* function, after the yield call. So here the jump in fiber_switchContext is a _function return_,
|
|
* not a function call!
|
|
*
|
|
* The most important result of this is that on entering a function, i.e. fiber_entrypoint, we
|
|
* would have to provide a return address / set the link register once fiber_entrypoint
|
|
* returns. Now fiber_entrypoint does never return and therefore the actual value of the return
|
|
* address / link register is never read/used and therefore doesn't matter. When fiber_switchContext
|
|
* performs a _function return_ the value in the link register doesn't matter either.
|
|
* However, the link register will still be saved to the stack in fiber_entrypoint and some
|
|
* exception handling / stack unwinding code might read it from this stack location and crash.
|
|
* The exact solution depends on your architecture, but see the ARM implementation for a way
|
|
* to deal with this issue.
|
|
*
|
|
* The ARM implementation is meant to be used as a kind of documented example implementation.
|
|
* Look there for a concrete example.
|
|
*
|
|
* FIXME: fiber_entrypoint might benefit from a @noreturn attribute, but D doesn't have one.
|
|
*/
|
|
|
|
/**
|
|
* This class provides a cooperative concurrency mechanism integrated with the
|
|
* threading and garbage collection functionality. Calling a fiber may be
|
|
* considered a blocking operation that returns when the fiber yields (via
|
|
* Fiber.yield()). Execution occurs within the context of the calling thread
|
|
* so synchronization is not necessary to guarantee memory visibility so long
|
|
* as the same thread calls the fiber each time. Please note that there is no
|
|
* requirement that a fiber be bound to one specific thread. Rather, fibers
|
|
* may be freely passed between threads so long as they are not currently
|
|
* executing. Like threads, a new fiber thread may be created using either
|
|
* derivation or composition, as in the following example.
|
|
*
|
|
* Warning:
|
|
* Status registers are not saved by the current implementations. This means
|
|
* floating point exception status bits (overflow, divide by 0), rounding mode
|
|
* and similar stuff is set per-thread, not per Fiber!
|
|
*
|
|
* Warning:
|
|
* On ARM FPU registers are not saved if druntime was compiled as ARM_SoftFloat.
|
|
* If such a build is used on a ARM_SoftFP system which actually has got a FPU
|
|
* and other libraries are using the FPU registers (other code is compiled
|
|
* as ARM_SoftFP) this can cause problems. Druntime must be compiled as
|
|
* ARM_SoftFP in this case.
|
|
*
|
|
* Example:
|
|
* ----------------------------------------------------------------------
|
|
*
|
|
* class DerivedFiber : Fiber
|
|
* {
|
|
* this()
|
|
* {
|
|
* super( &run );
|
|
* }
|
|
*
|
|
* private :
|
|
* void run()
|
|
* {
|
|
* printf( "Derived fiber running.\n" );
|
|
* }
|
|
* }
|
|
*
|
|
* void fiberFunc()
|
|
* {
|
|
* printf( "Composed fiber running.\n" );
|
|
* Fiber.yield();
|
|
* printf( "Composed fiber running.\n" );
|
|
* }
|
|
*
|
|
* // create instances of each type
|
|
* Fiber derived = new DerivedFiber();
|
|
* Fiber composed = new Fiber( &fiberFunc );
|
|
*
|
|
* // call both fibers once
|
|
* derived.call();
|
|
* composed.call();
|
|
* printf( "Execution returned to calling context.\n" );
|
|
* composed.call();
|
|
*
|
|
* // since each fiber has run to completion, each should have state TERM
|
|
* assert( derived.state == Fiber.State.TERM );
|
|
* assert( composed.state == Fiber.State.TERM );
|
|
*
|
|
* ----------------------------------------------------------------------
|
|
*
|
|
* Authors: Based on a design by Mikola Lysenko.
|
|
*/
|
|
class Fiber
|
|
{
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Initialization
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* Initializes a fiber object which is associated with a static
|
|
* D function.
|
|
*
|
|
* Params:
|
|
* fn = The fiber function.
|
|
* sz = The stack size for this fiber.
|
|
* guardPageSize = size of the guard page to trap fiber's stack
|
|
* overflows
|
|
*
|
|
* In:
|
|
* fn must not be null.
|
|
*/
|
|
this( void function() fn, size_t sz = PAGESIZE*4,
|
|
size_t guardPageSize = PAGESIZE ) nothrow
|
|
in
|
|
{
|
|
assert( fn );
|
|
}
|
|
body
|
|
{
|
|
allocStack( sz, guardPageSize );
|
|
reset( fn );
|
|
}
|
|
|
|
|
|
/**
|
|
* Initializes a fiber object which is associated with a dynamic
|
|
* D function.
|
|
*
|
|
* Params:
|
|
* dg = The fiber function.
|
|
* sz = The stack size for this fiber.
|
|
* guardPageSize = size of the guard page to trap fiber's stack
|
|
* overflows
|
|
*
|
|
* In:
|
|
* dg must not be null.
|
|
*/
|
|
this( void delegate() dg, size_t sz = PAGESIZE*4,
|
|
size_t guardPageSize = PAGESIZE ) nothrow
|
|
in
|
|
{
|
|
assert( dg );
|
|
}
|
|
body
|
|
{
|
|
allocStack( sz, guardPageSize);
|
|
reset( dg );
|
|
}
|
|
|
|
|
|
/**
|
|
* Cleans up any remaining resources used by this object.
|
|
*/
|
|
~this() nothrow @nogc
|
|
{
|
|
// NOTE: A live reference to this object will exist on its associated
|
|
// stack from the first time its call() method has been called
|
|
// until its execution completes with State.TERM. Thus, the only
|
|
// times this dtor should be called are either if the fiber has
|
|
// terminated (and therefore has no active stack) or if the user
|
|
// explicitly deletes this object. The latter case is an error
|
|
// but is not easily tested for, since State.HOLD may imply that
|
|
// the fiber was just created but has never been run. There is
|
|
// not a compelling case to create a State.INIT just to offer a
|
|
// means of ensuring the user isn't violating this object's
|
|
// contract, so for now this requirement will be enforced by
|
|
// documentation only.
|
|
freeStack();
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// General Actions
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* Transfers execution to this fiber object. The calling context will be
|
|
* suspended until the fiber calls Fiber.yield() or until it terminates
|
|
* via an unhandled exception.
|
|
*
|
|
* Params:
|
|
* rethrow = Rethrow any unhandled exception which may have caused this
|
|
* fiber to terminate.
|
|
*
|
|
* In:
|
|
* This fiber must be in state HOLD.
|
|
*
|
|
* Throws:
|
|
* Any exception not handled by the joined thread.
|
|
*
|
|
* Returns:
|
|
* Any exception not handled by this fiber if rethrow = false, null
|
|
* otherwise.
|
|
*/
|
|
// Not marked with any attributes, even though `nothrow @nogc` works
|
|
// because it calls arbitrary user code. Most of the implementation
|
|
// is already `@nogc nothrow`, but in order for `Fiber.call` to
|
|
// propagate the attributes of the user's function, the Fiber
|
|
// class needs to be templated.
|
|
final Throwable call( Rethrow rethrow = Rethrow.yes )
|
|
{
|
|
return rethrow ? call!(Rethrow.yes)() : call!(Rethrow.no);
|
|
}
|
|
|
|
/// ditto
|
|
final Throwable call( Rethrow rethrow )()
|
|
{
|
|
callImpl();
|
|
if ( m_unhandled )
|
|
{
|
|
Throwable t = m_unhandled;
|
|
m_unhandled = null;
|
|
static if ( rethrow )
|
|
throw t;
|
|
else
|
|
return t;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// ditto
|
|
deprecated("Please pass Fiber.Rethrow.yes or .no instead of a boolean.")
|
|
final Throwable call( bool rethrow )
|
|
{
|
|
return rethrow ? call!(Rethrow.yes)() : call!(Rethrow.no);
|
|
}
|
|
|
|
private void callImpl() nothrow @nogc
|
|
in
|
|
{
|
|
assert( m_state == State.HOLD );
|
|
}
|
|
body
|
|
{
|
|
Fiber cur = getThis();
|
|
|
|
static if ( __traits( compiles, ucontext_t ) )
|
|
m_ucur = cur ? &cur.m_utxt : &Fiber.sm_utxt;
|
|
|
|
setThis( this );
|
|
this.switchIn();
|
|
setThis( cur );
|
|
|
|
static if ( __traits( compiles, ucontext_t ) )
|
|
m_ucur = null;
|
|
|
|
// NOTE: If the fiber has terminated then the stack pointers must be
|
|
// reset. This ensures that the stack for this fiber is not
|
|
// scanned if the fiber has terminated. This is necessary to
|
|
// prevent any references lingering on the stack from delaying
|
|
// the collection of otherwise dead objects. The most notable
|
|
// being the current object, which is referenced at the top of
|
|
// fiber_entryPoint.
|
|
if ( m_state == State.TERM )
|
|
{
|
|
m_ctxt.tstack = m_ctxt.bstack;
|
|
}
|
|
}
|
|
|
|
/// Flag to control rethrow behavior of $(D $(LREF call))
|
|
enum Rethrow : bool { no, yes }
|
|
|
|
/**
|
|
* Resets this fiber so that it may be re-used, optionally with a
|
|
* new function/delegate. This routine should only be called for
|
|
* fibers that have terminated, as doing otherwise could result in
|
|
* scope-dependent functionality that is not executed.
|
|
* Stack-based classes, for example, may not be cleaned up
|
|
* properly if a fiber is reset before it has terminated.
|
|
*
|
|
* In:
|
|
* This fiber must be in state TERM or HOLD.
|
|
*/
|
|
final void reset() nothrow @nogc
|
|
in
|
|
{
|
|
assert( m_state == State.TERM || m_state == State.HOLD );
|
|
}
|
|
body
|
|
{
|
|
m_ctxt.tstack = m_ctxt.bstack;
|
|
m_state = State.HOLD;
|
|
initStack();
|
|
m_unhandled = null;
|
|
}
|
|
|
|
/// ditto
|
|
final void reset( void function() fn ) nothrow @nogc
|
|
{
|
|
reset();
|
|
m_fn = fn;
|
|
m_call = Call.FN;
|
|
}
|
|
|
|
/// ditto
|
|
final void reset( void delegate() dg ) nothrow @nogc
|
|
{
|
|
reset();
|
|
m_dg = dg;
|
|
m_call = Call.DG;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// General Properties
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* A fiber may occupy one of three states: HOLD, EXEC, and TERM. The HOLD
|
|
* state applies to any fiber that is suspended and ready to be called.
|
|
* The EXEC state will be set for any fiber that is currently executing.
|
|
* And the TERM state is set when a fiber terminates. Once a fiber
|
|
* terminates, it must be reset before it may be called again.
|
|
*/
|
|
enum State
|
|
{
|
|
HOLD, ///
|
|
EXEC, ///
|
|
TERM ///
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the current state of this fiber.
|
|
*
|
|
* Returns:
|
|
* The state of this fiber as an enumerated value.
|
|
*/
|
|
final @property State state() const @safe pure nothrow @nogc
|
|
{
|
|
return m_state;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Actions on Calling Fiber
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* Forces a context switch to occur away from the calling fiber.
|
|
*/
|
|
static void yield() nothrow @nogc
|
|
{
|
|
Fiber cur = getThis();
|
|
assert( cur, "Fiber.yield() called with no active fiber" );
|
|
assert( cur.m_state == State.EXEC );
|
|
|
|
static if ( __traits( compiles, ucontext_t ) )
|
|
cur.m_ucur = &cur.m_utxt;
|
|
|
|
cur.m_state = State.HOLD;
|
|
cur.switchOut();
|
|
cur.m_state = State.EXEC;
|
|
}
|
|
|
|
|
|
/**
|
|
* Forces a context switch to occur away from the calling fiber and then
|
|
* throws obj in the calling fiber.
|
|
*
|
|
* Params:
|
|
* t = The object to throw.
|
|
*
|
|
* In:
|
|
* t must not be null.
|
|
*/
|
|
static void yieldAndThrow( Throwable t ) nothrow @nogc
|
|
in
|
|
{
|
|
assert( t );
|
|
}
|
|
body
|
|
{
|
|
Fiber cur = getThis();
|
|
assert( cur, "Fiber.yield() called with no active fiber" );
|
|
assert( cur.m_state == State.EXEC );
|
|
|
|
static if ( __traits( compiles, ucontext_t ) )
|
|
cur.m_ucur = &cur.m_utxt;
|
|
|
|
cur.m_unhandled = t;
|
|
cur.m_state = State.HOLD;
|
|
cur.switchOut();
|
|
cur.m_state = State.EXEC;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Fiber Accessors
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**
|
|
* Provides a reference to the calling fiber or null if no fiber is
|
|
* currently active.
|
|
*
|
|
* Returns:
|
|
* The fiber object representing the calling fiber or null if no fiber
|
|
* is currently active within this thread. The result of deleting this object is undefined.
|
|
*/
|
|
static Fiber getThis() @safe nothrow @nogc
|
|
{
|
|
return sm_this;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Static Initialization
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
version (Posix)
|
|
{
|
|
static this()
|
|
{
|
|
static if ( __traits( compiles, ucontext_t ) )
|
|
{
|
|
int status = getcontext( &sm_utxt );
|
|
assert( status == 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
//
|
|
// Initializes a fiber object which has no associated executable function.
|
|
//
|
|
this() @safe pure nothrow @nogc
|
|
{
|
|
m_call = Call.NO;
|
|
}
|
|
|
|
|
|
//
|
|
// Fiber entry point. Invokes the function or delegate passed on
|
|
// construction (if any).
|
|
//
|
|
final void run()
|
|
{
|
|
switch ( m_call )
|
|
{
|
|
case Call.FN:
|
|
m_fn();
|
|
break;
|
|
case Call.DG:
|
|
m_dg();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
private:
|
|
//
|
|
// The type of routine passed on fiber construction.
|
|
//
|
|
enum Call
|
|
{
|
|
NO,
|
|
FN,
|
|
DG
|
|
}
|
|
|
|
|
|
//
|
|
// Standard fiber data
|
|
//
|
|
Call m_call;
|
|
union
|
|
{
|
|
void function() m_fn;
|
|
void delegate() m_dg;
|
|
}
|
|
bool m_isRunning;
|
|
Throwable m_unhandled;
|
|
State m_state;
|
|
|
|
|
|
private:
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Stack Management
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
//
|
|
// Allocate a new stack for this fiber.
|
|
//
|
|
final void allocStack( size_t sz, size_t guardPageSize ) nothrow
|
|
in
|
|
{
|
|
assert( !m_pmem && !m_ctxt );
|
|
}
|
|
body
|
|
{
|
|
// adjust alloc size to a multiple of PAGESIZE
|
|
sz += PAGESIZE - 1;
|
|
sz -= sz % PAGESIZE;
|
|
|
|
// NOTE: This instance of Thread.Context is dynamic so Fiber objects
|
|
// can be collected by the GC so long as no user level references
|
|
// to the object exist. If m_ctxt were not dynamic then its
|
|
// presence in the global context list would be enough to keep
|
|
// this object alive indefinitely. An alternative to allocating
|
|
// room for this struct explicitly would be to mash it into the
|
|
// base of the stack being allocated below. However, doing so
|
|
// requires too much special logic to be worthwhile.
|
|
m_ctxt = new Thread.Context;
|
|
|
|
static if ( __traits( compiles, VirtualAlloc ) )
|
|
{
|
|
// reserve memory for stack
|
|
m_pmem = VirtualAlloc( null,
|
|
sz + guardPageSize,
|
|
MEM_RESERVE,
|
|
PAGE_NOACCESS );
|
|
if ( !m_pmem )
|
|
onOutOfMemoryError();
|
|
|
|
version (StackGrowsDown)
|
|
{
|
|
void* stack = m_pmem + guardPageSize;
|
|
void* guard = m_pmem;
|
|
void* pbase = stack + sz;
|
|
}
|
|
else
|
|
{
|
|
void* stack = m_pmem;
|
|
void* guard = m_pmem + sz;
|
|
void* pbase = stack;
|
|
}
|
|
|
|
// allocate reserved stack segment
|
|
stack = VirtualAlloc( stack,
|
|
sz,
|
|
MEM_COMMIT,
|
|
PAGE_READWRITE );
|
|
if ( !stack )
|
|
onOutOfMemoryError();
|
|
|
|
if (guardPageSize)
|
|
{
|
|
// allocate reserved guard page
|
|
guard = VirtualAlloc( guard,
|
|
guardPageSize,
|
|
MEM_COMMIT,
|
|
PAGE_READWRITE | PAGE_GUARD );
|
|
if ( !guard )
|
|
onOutOfMemoryError();
|
|
}
|
|
|
|
m_ctxt.bstack = pbase;
|
|
m_ctxt.tstack = pbase;
|
|
m_size = sz;
|
|
}
|
|
else
|
|
{
|
|
version (Posix) import core.sys.posix.sys.mman; // mmap
|
|
version (FreeBSD) import core.sys.freebsd.sys.mman : MAP_ANON;
|
|
version (NetBSD) import core.sys.netbsd.sys.mman : MAP_ANON;
|
|
version (OpenBSD) import core.sys.openbsd.sys.mman : MAP_ANON;
|
|
version (DragonFlyBSD) import core.sys.dragonflybsd.sys.mman : MAP_ANON;
|
|
version (CRuntime_Glibc) import core.sys.linux.sys.mman : MAP_ANON;
|
|
version (Darwin) import core.sys.darwin.sys.mman : MAP_ANON;
|
|
version (CRuntime_UClibc) import core.sys.linux.sys.mman : MAP_ANON;
|
|
|
|
static if ( __traits( compiles, mmap ) )
|
|
{
|
|
// Allocate more for the memory guard
|
|
sz += guardPageSize;
|
|
|
|
m_pmem = mmap( null,
|
|
sz,
|
|
PROT_READ | PROT_WRITE,
|
|
MAP_PRIVATE | MAP_ANON,
|
|
-1,
|
|
0 );
|
|
if ( m_pmem == MAP_FAILED )
|
|
m_pmem = null;
|
|
}
|
|
else static if ( __traits( compiles, valloc ) )
|
|
{
|
|
m_pmem = valloc( sz );
|
|
}
|
|
else static if ( __traits( compiles, malloc ) )
|
|
{
|
|
m_pmem = malloc( sz );
|
|
}
|
|
else
|
|
{
|
|
m_pmem = null;
|
|
}
|
|
|
|
if ( !m_pmem )
|
|
onOutOfMemoryError();
|
|
|
|
version (StackGrowsDown)
|
|
{
|
|
m_ctxt.bstack = m_pmem + sz;
|
|
m_ctxt.tstack = m_pmem + sz;
|
|
void* guard = m_pmem;
|
|
}
|
|
else
|
|
{
|
|
m_ctxt.bstack = m_pmem;
|
|
m_ctxt.tstack = m_pmem;
|
|
void* guard = m_pmem + sz - guardPageSize;
|
|
}
|
|
m_size = sz;
|
|
|
|
static if ( __traits( compiles, mmap ) )
|
|
{
|
|
if (guardPageSize)
|
|
{
|
|
// protect end of stack
|
|
if ( mprotect(guard, guardPageSize, PROT_NONE) == -1 )
|
|
abort();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Supported only for mmap allocated memory - results are
|
|
// undefined if applied to memory not obtained by mmap
|
|
}
|
|
}
|
|
|
|
Thread.add( m_ctxt );
|
|
}
|
|
|
|
|
|
//
|
|
// Free this fiber's stack.
|
|
//
|
|
final void freeStack() nothrow @nogc
|
|
in
|
|
{
|
|
assert( m_pmem && m_ctxt );
|
|
}
|
|
body
|
|
{
|
|
// NOTE: m_ctxt is guaranteed to be alive because it is held in the
|
|
// global context list.
|
|
Thread.slock.lock_nothrow();
|
|
scope(exit) Thread.slock.unlock_nothrow();
|
|
Thread.remove( m_ctxt );
|
|
|
|
static if ( __traits( compiles, VirtualAlloc ) )
|
|
{
|
|
VirtualFree( m_pmem, 0, MEM_RELEASE );
|
|
}
|
|
else
|
|
{
|
|
import core.sys.posix.sys.mman; // munmap
|
|
|
|
static if ( __traits( compiles, mmap ) )
|
|
{
|
|
munmap( m_pmem, m_size );
|
|
}
|
|
else static if ( __traits( compiles, valloc ) )
|
|
{
|
|
free( m_pmem );
|
|
}
|
|
else static if ( __traits( compiles, malloc ) )
|
|
{
|
|
free( m_pmem );
|
|
}
|
|
}
|
|
m_pmem = null;
|
|
m_ctxt = null;
|
|
}
|
|
|
|
|
|
//
|
|
// Initialize the allocated stack.
|
|
// Look above the definition of 'class Fiber' for some information about the implementation of this routine
|
|
//
|
|
final void initStack() nothrow @nogc
|
|
in
|
|
{
|
|
assert( m_ctxt.tstack && m_ctxt.tstack == m_ctxt.bstack );
|
|
assert( cast(size_t) m_ctxt.bstack % (void*).sizeof == 0 );
|
|
}
|
|
body
|
|
{
|
|
void* pstack = m_ctxt.tstack;
|
|
scope( exit ) m_ctxt.tstack = pstack;
|
|
|
|
void push( size_t val ) nothrow
|
|
{
|
|
version (StackGrowsDown)
|
|
{
|
|
pstack -= size_t.sizeof;
|
|
*(cast(size_t*) pstack) = val;
|
|
}
|
|
else
|
|
{
|
|
pstack += size_t.sizeof;
|
|
*(cast(size_t*) pstack) = val;
|
|
}
|
|
}
|
|
|
|
// NOTE: On OS X the stack must be 16-byte aligned according
|
|
// to the IA-32 call spec. For x86_64 the stack also needs to
|
|
// be aligned to 16-byte according to SysV AMD64 ABI.
|
|
version (AlignFiberStackTo16Byte)
|
|
{
|
|
version (StackGrowsDown)
|
|
{
|
|
pstack = cast(void*)(cast(size_t)(pstack) - (cast(size_t)(pstack) & 0x0F));
|
|
}
|
|
else
|
|
{
|
|
pstack = cast(void*)(cast(size_t)(pstack) + (cast(size_t)(pstack) & 0x0F));
|
|
}
|
|
}
|
|
|
|
version (AsmX86_Windows)
|
|
{
|
|
version (StackGrowsDown) {} else static assert( false );
|
|
|
|
// On Windows Server 2008 and 2008 R2, an exploit mitigation
|
|
// technique known as SEHOP is activated by default. To avoid
|
|
// hijacking of the exception handler chain, the presence of a
|
|
// Windows-internal handler (ntdll.dll!FinalExceptionHandler) at
|
|
// its end is tested by RaiseException. If it is not present, all
|
|
// handlers are disregarded, and the program is thus aborted
|
|
// (see http://blogs.technet.com/b/srd/archive/2009/02/02/
|
|
// preventing-the-exploitation-of-seh-overwrites-with-sehop.aspx).
|
|
// For new threads, this handler is installed by Windows immediately
|
|
// after creation. To make exception handling work in fibers, we
|
|
// have to insert it for our new stacks manually as well.
|
|
//
|
|
// To do this, we first determine the handler by traversing the SEH
|
|
// chain of the current thread until its end, and then construct a
|
|
// registration block for the last handler on the newly created
|
|
// thread. We then continue to push all the initial register values
|
|
// for the first context switch as for the other implementations.
|
|
//
|
|
// Note that this handler is never actually invoked, as we install
|
|
// our own one on top of it in the fiber entry point function.
|
|
// Thus, it should not have any effects on OSes not implementing
|
|
// exception chain verification.
|
|
|
|
alias fp_t = void function(); // Actual signature not relevant.
|
|
static struct EXCEPTION_REGISTRATION
|
|
{
|
|
EXCEPTION_REGISTRATION* next; // sehChainEnd if last one.
|
|
fp_t handler;
|
|
}
|
|
enum sehChainEnd = cast(EXCEPTION_REGISTRATION*) 0xFFFFFFFF;
|
|
|
|
__gshared static fp_t finalHandler = null;
|
|
if ( finalHandler is null )
|
|
{
|
|
static EXCEPTION_REGISTRATION* fs0() nothrow
|
|
{
|
|
asm pure nothrow @nogc
|
|
{
|
|
naked;
|
|
mov EAX, FS:[0];
|
|
ret;
|
|
}
|
|
}
|
|
auto reg = fs0();
|
|
while ( reg.next != sehChainEnd ) reg = reg.next;
|
|
|
|
// Benign races are okay here, just to avoid re-lookup on every
|
|
// fiber creation.
|
|
finalHandler = reg.handler;
|
|
}
|
|
|
|
// When linking with /safeseh (supported by LDC, but not DMD)
|
|
// the exception chain must not extend to the very top
|
|
// of the stack, otherwise the exception chain is also considered
|
|
// invalid. Reserving additional 4 bytes at the top of the stack will
|
|
// keep the EXCEPTION_REGISTRATION below that limit
|
|
size_t reserve = EXCEPTION_REGISTRATION.sizeof + 4;
|
|
pstack -= reserve;
|
|
*(cast(EXCEPTION_REGISTRATION*)pstack) =
|
|
EXCEPTION_REGISTRATION( sehChainEnd, finalHandler );
|
|
|
|
push( cast(size_t) &fiber_entryPoint ); // EIP
|
|
push( cast(size_t) m_ctxt.bstack - reserve ); // EBP
|
|
push( 0x00000000 ); // EDI
|
|
push( 0x00000000 ); // ESI
|
|
push( 0x00000000 ); // EBX
|
|
push( cast(size_t) m_ctxt.bstack - reserve ); // FS:[0]
|
|
push( cast(size_t) m_ctxt.bstack ); // FS:[4]
|
|
push( cast(size_t) m_ctxt.bstack - m_size ); // FS:[8]
|
|
push( 0x00000000 ); // EAX
|
|
}
|
|
else version (AsmX86_64_Windows)
|
|
{
|
|
// Using this trampoline instead of the raw fiber_entryPoint
|
|
// ensures that during context switches, source and destination
|
|
// stacks have the same alignment. Otherwise, the stack would need
|
|
// to be shifted by 8 bytes for the first call, as fiber_entryPoint
|
|
// is an actual function expecting a stack which is not aligned
|
|
// to 16 bytes.
|
|
static void trampoline()
|
|
{
|
|
asm pure nothrow @nogc
|
|
{
|
|
naked;
|
|
sub RSP, 32; // Shadow space (Win64 calling convention)
|
|
call fiber_entryPoint;
|
|
xor RCX, RCX; // This should never be reached, as
|
|
jmp RCX; // fiber_entryPoint must never return.
|
|
}
|
|
}
|
|
|
|
push( cast(size_t) &trampoline ); // RIP
|
|
push( 0x00000000_00000000 ); // RBP
|
|
push( 0x00000000_00000000 ); // R12
|
|
push( 0x00000000_00000000 ); // R13
|
|
push( 0x00000000_00000000 ); // R14
|
|
push( 0x00000000_00000000 ); // R15
|
|
push( 0x00000000_00000000 ); // RDI
|
|
push( 0x00000000_00000000 ); // RSI
|
|
push( 0x00000000_00000000 ); // XMM6 (high)
|
|
push( 0x00000000_00000000 ); // XMM6 (low)
|
|
push( 0x00000000_00000000 ); // XMM7 (high)
|
|
push( 0x00000000_00000000 ); // XMM7 (low)
|
|
push( 0x00000000_00000000 ); // XMM8 (high)
|
|
push( 0x00000000_00000000 ); // XMM8 (low)
|
|
push( 0x00000000_00000000 ); // XMM9 (high)
|
|
push( 0x00000000_00000000 ); // XMM9 (low)
|
|
push( 0x00000000_00000000 ); // XMM10 (high)
|
|
push( 0x00000000_00000000 ); // XMM10 (low)
|
|
push( 0x00000000_00000000 ); // XMM11 (high)
|
|
push( 0x00000000_00000000 ); // XMM11 (low)
|
|
push( 0x00000000_00000000 ); // XMM12 (high)
|
|
push( 0x00000000_00000000 ); // XMM12 (low)
|
|
push( 0x00000000_00000000 ); // XMM13 (high)
|
|
push( 0x00000000_00000000 ); // XMM13 (low)
|
|
push( 0x00000000_00000000 ); // XMM14 (high)
|
|
push( 0x00000000_00000000 ); // XMM14 (low)
|
|
push( 0x00000000_00000000 ); // XMM15 (high)
|
|
push( 0x00000000_00000000 ); // XMM15 (low)
|
|
push( 0x00000000_00000000 ); // RBX
|
|
push( 0xFFFFFFFF_FFFFFFFF ); // GS:[0]
|
|
version (StackGrowsDown)
|
|
{
|
|
push( cast(size_t) m_ctxt.bstack ); // GS:[8]
|
|
push( cast(size_t) m_ctxt.bstack - m_size ); // GS:[16]
|
|
}
|
|
else
|
|
{
|
|
push( cast(size_t) m_ctxt.bstack ); // GS:[8]
|
|
push( cast(size_t) m_ctxt.bstack + m_size ); // GS:[16]
|
|
}
|
|
}
|
|
else version (AsmX86_Posix)
|
|
{
|
|
push( 0x00000000 ); // Return address of fiber_entryPoint call
|
|
push( cast(size_t) &fiber_entryPoint ); // EIP
|
|
push( cast(size_t) m_ctxt.bstack ); // EBP
|
|
push( 0x00000000 ); // EDI
|
|
push( 0x00000000 ); // ESI
|
|
push( 0x00000000 ); // EBX
|
|
push( 0x00000000 ); // EAX
|
|
}
|
|
else version (AsmX86_64_Posix)
|
|
{
|
|
push( 0x00000000_00000000 ); // Return address of fiber_entryPoint call
|
|
push( cast(size_t) &fiber_entryPoint ); // RIP
|
|
push( cast(size_t) m_ctxt.bstack ); // RBP
|
|
push( 0x00000000_00000000 ); // RBX
|
|
push( 0x00000000_00000000 ); // R12
|
|
push( 0x00000000_00000000 ); // R13
|
|
push( 0x00000000_00000000 ); // R14
|
|
push( 0x00000000_00000000 ); // R15
|
|
}
|
|
else version (AsmPPC_Posix)
|
|
{
|
|
version (StackGrowsDown)
|
|
{
|
|
pstack -= int.sizeof * 5;
|
|
}
|
|
else
|
|
{
|
|
pstack += int.sizeof * 5;
|
|
}
|
|
|
|
push( cast(size_t) &fiber_entryPoint ); // link register
|
|
push( 0x00000000 ); // control register
|
|
push( 0x00000000 ); // old stack pointer
|
|
|
|
// GPR values
|
|
version (StackGrowsDown)
|
|
{
|
|
pstack -= int.sizeof * 20;
|
|
}
|
|
else
|
|
{
|
|
pstack += int.sizeof * 20;
|
|
}
|
|
|
|
assert( (cast(size_t) pstack & 0x0f) == 0 );
|
|
}
|
|
else version (AsmMIPS_O32_Posix)
|
|
{
|
|
version (StackGrowsDown) {}
|
|
else static assert(0);
|
|
|
|
/* We keep the FP registers and the return address below
|
|
* the stack pointer, so they don't get scanned by the
|
|
* GC. The last frame before swapping the stack pointer is
|
|
* organized like the following.
|
|
*
|
|
* |-----------|<= frame pointer
|
|
* | $gp |
|
|
* | $s0-8 |
|
|
* |-----------|<= stack pointer
|
|
* | $ra |
|
|
* | align(8) |
|
|
* | $f20-30 |
|
|
* |-----------|
|
|
*
|
|
*/
|
|
enum SZ_GP = 10 * size_t.sizeof; // $gp + $s0-8
|
|
enum SZ_RA = size_t.sizeof; // $ra
|
|
version (MIPS_HardFloat)
|
|
{
|
|
enum SZ_FP = 6 * 8; // $f20-30
|
|
enum ALIGN = -(SZ_FP + SZ_RA) & (8 - 1);
|
|
}
|
|
else
|
|
{
|
|
enum SZ_FP = 0;
|
|
enum ALIGN = 0;
|
|
}
|
|
|
|
enum BELOW = SZ_FP + ALIGN + SZ_RA;
|
|
enum ABOVE = SZ_GP;
|
|
enum SZ = BELOW + ABOVE;
|
|
|
|
(cast(ubyte*)pstack - SZ)[0 .. SZ] = 0;
|
|
pstack -= ABOVE;
|
|
*cast(size_t*)(pstack - SZ_RA) = cast(size_t)&fiber_entryPoint;
|
|
}
|
|
else version (AsmAArch64_Posix)
|
|
{
|
|
// Like others, FP registers and return address (lr) are kept
|
|
// below the saved stack top (tstack) to hide from GC scanning.
|
|
// fiber_switchContext expects newp sp to look like this:
|
|
// 19: x19
|
|
// ...
|
|
// 9: x29 (fp) <-- newp tstack
|
|
// 8: x30 (lr) [&fiber_entryPoint]
|
|
// 7: d8
|
|
// ...
|
|
// 0: d15
|
|
|
|
version (StackGrowsDown) {}
|
|
else
|
|
static assert(false, "Only full descending stacks supported on AArch64");
|
|
|
|
// Only need to set return address (lr). Everything else is fine
|
|
// zero initialized.
|
|
pstack -= size_t.sizeof * 11; // skip past x19-x29
|
|
push(cast(size_t) &fiber_trampoline); // see threadasm.S for docs
|
|
pstack += size_t.sizeof; // adjust sp (newp) above lr
|
|
}
|
|
else version (AsmARM_Posix)
|
|
{
|
|
/* We keep the FP registers and the return address below
|
|
* the stack pointer, so they don't get scanned by the
|
|
* GC. The last frame before swapping the stack pointer is
|
|
* organized like the following.
|
|
*
|
|
* | |-----------|<= 'frame starts here'
|
|
* | | fp | (the actual frame pointer, r11 isn't
|
|
* | | r10-r4 | updated and still points to the previous frame)
|
|
* | |-----------|<= stack pointer
|
|
* | | lr |
|
|
* | | 4byte pad |
|
|
* | | d15-d8 |(if FP supported)
|
|
* | |-----------|
|
|
* Y
|
|
* stack grows down: The pointer value here is smaller than some lines above
|
|
*/
|
|
// frame pointer can be zero, r10-r4 also zero initialized
|
|
version (StackGrowsDown)
|
|
pstack -= int.sizeof * 8;
|
|
else
|
|
static assert(false, "Only full descending stacks supported on ARM");
|
|
|
|
// link register
|
|
push( cast(size_t) &fiber_entryPoint );
|
|
/*
|
|
* We do not push padding and d15-d8 as those are zero initialized anyway
|
|
* Position the stack pointer above the lr register
|
|
*/
|
|
pstack += int.sizeof * 1;
|
|
}
|
|
else version (GNU_AsmX86_Windows)
|
|
{
|
|
version (StackGrowsDown) {} else static assert( false );
|
|
|
|
// Currently, MinGW doesn't utilize SEH exceptions.
|
|
// See DMD AsmX86_Windows If this code ever becomes fails and SEH is used.
|
|
|
|
push( 0x00000000 ); // Return address of fiber_entryPoint call
|
|
push( cast(size_t) &fiber_entryPoint ); // EIP
|
|
push( 0x00000000 ); // EBP
|
|
push( 0x00000000 ); // EDI
|
|
push( 0x00000000 ); // ESI
|
|
push( 0x00000000 ); // EBX
|
|
push( 0xFFFFFFFF ); // FS:[0] - Current SEH frame
|
|
push( cast(size_t) m_ctxt.bstack ); // FS:[4] - Top of stack
|
|
push( cast(size_t) m_ctxt.bstack - m_size ); // FS:[8] - Bottom of stack
|
|
push( 0x00000000 ); // EAX
|
|
}
|
|
else version (GNU_AsmX86_64_Windows)
|
|
{
|
|
push( 0x00000000_00000000 ); // Return address of fiber_entryPoint call
|
|
push( cast(size_t) &fiber_entryPoint ); // RIP
|
|
push( 0x00000000_00000000 ); // RBP
|
|
push( 0x00000000_00000000 ); // RBX
|
|
push( 0x00000000_00000000 ); // R12
|
|
push( 0x00000000_00000000 ); // R13
|
|
push( 0x00000000_00000000 ); // R14
|
|
push( 0x00000000_00000000 ); // R15
|
|
push( 0xFFFFFFFF_FFFFFFFF ); // GS:[0] - Current SEH frame
|
|
version (StackGrowsDown)
|
|
{
|
|
push( cast(size_t) m_ctxt.bstack ); // GS:[8] - Top of stack
|
|
push( cast(size_t) m_ctxt.bstack - m_size ); // GS:[16] - Bottom of stack
|
|
}
|
|
else
|
|
{
|
|
push( cast(size_t) m_ctxt.bstack ); // GS:[8] - Top of stack
|
|
push( cast(size_t) m_ctxt.bstack + m_size ); // GS:[16] - Bottom of stack
|
|
}
|
|
}
|
|
else static if ( __traits( compiles, ucontext_t ) )
|
|
{
|
|
getcontext( &m_utxt );
|
|
m_utxt.uc_stack.ss_sp = m_pmem;
|
|
m_utxt.uc_stack.ss_size = m_size;
|
|
makecontext( &m_utxt, &fiber_entryPoint, 0 );
|
|
// NOTE: If ucontext is being used then the top of the stack will
|
|
// be a pointer to the ucontext_t struct for that fiber.
|
|
push( cast(size_t) &m_utxt );
|
|
}
|
|
else
|
|
static assert(0, "Not implemented");
|
|
}
|
|
|
|
|
|
Thread.Context* m_ctxt;
|
|
size_t m_size;
|
|
void* m_pmem;
|
|
|
|
static if ( __traits( compiles, ucontext_t ) )
|
|
{
|
|
// NOTE: The static ucontext instance is used to represent the context
|
|
// of the executing thread.
|
|
static ucontext_t sm_utxt = void;
|
|
ucontext_t m_utxt = void;
|
|
ucontext_t* m_ucur = null;
|
|
}
|
|
else static if (GNU_Enable_CET)
|
|
{
|
|
// When libphobos was built with --enable-cet, these fields need to
|
|
// always be present in the Fiber class layout.
|
|
import core.sys.posix.ucontext;
|
|
static ucontext_t sm_utxt = void;
|
|
ucontext_t m_utxt = void;
|
|
ucontext_t* m_ucur = null;
|
|
}
|
|
|
|
|
|
private:
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Storage of Active Fiber
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
//
|
|
// Sets a thread-local reference to the current fiber object.
|
|
//
|
|
static void setThis( Fiber f ) nothrow @nogc
|
|
{
|
|
sm_this = f;
|
|
}
|
|
|
|
static Fiber sm_this;
|
|
|
|
|
|
private:
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Context Switching
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
//
|
|
// Switches into the stack held by this fiber.
|
|
//
|
|
final void switchIn() nothrow @nogc
|
|
{
|
|
Thread tobj = Thread.getThis();
|
|
void** oldp = &tobj.m_curr.tstack;
|
|
void* newp = m_ctxt.tstack;
|
|
|
|
// NOTE: The order of operations here is very important. The current
|
|
// stack top must be stored before m_lock is set, and pushContext
|
|
// must not be called until after m_lock is set. This process
|
|
// is intended to prevent a race condition with the suspend
|
|
// mechanism used for garbage collection. If it is not followed,
|
|
// a badly timed collection could cause the GC to scan from the
|
|
// bottom of one stack to the top of another, or to miss scanning
|
|
// a stack that still contains valid data. The old stack pointer
|
|
// oldp will be set again before the context switch to guarantee
|
|
// that it points to exactly the correct stack location so the
|
|
// successive pop operations will succeed.
|
|
*oldp = getStackTop();
|
|
atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, true);
|
|
tobj.pushContext( m_ctxt );
|
|
|
|
fiber_switchContext( oldp, newp );
|
|
|
|
// NOTE: As above, these operations must be performed in a strict order
|
|
// to prevent Bad Things from happening.
|
|
tobj.popContext();
|
|
atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, false);
|
|
tobj.m_curr.tstack = tobj.m_curr.bstack;
|
|
}
|
|
|
|
|
|
//
|
|
// Switches out of the current stack and into the enclosing stack.
|
|
//
|
|
final void switchOut() nothrow @nogc
|
|
{
|
|
Thread tobj = Thread.getThis();
|
|
void** oldp = &m_ctxt.tstack;
|
|
void* newp = tobj.m_curr.within.tstack;
|
|
|
|
// NOTE: The order of operations here is very important. The current
|
|
// stack top must be stored before m_lock is set, and pushContext
|
|
// must not be called until after m_lock is set. This process
|
|
// is intended to prevent a race condition with the suspend
|
|
// mechanism used for garbage collection. If it is not followed,
|
|
// a badly timed collection could cause the GC to scan from the
|
|
// bottom of one stack to the top of another, or to miss scanning
|
|
// a stack that still contains valid data. The old stack pointer
|
|
// oldp will be set again before the context switch to guarantee
|
|
// that it points to exactly the correct stack location so the
|
|
// successive pop operations will succeed.
|
|
*oldp = getStackTop();
|
|
atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, true);
|
|
|
|
fiber_switchContext( oldp, newp );
|
|
|
|
// NOTE: As above, these operations must be performed in a strict order
|
|
// to prevent Bad Things from happening.
|
|
// NOTE: If use of this fiber is multiplexed across threads, the thread
|
|
// executing here may be different from the one above, so get the
|
|
// current thread handle before unlocking, etc.
|
|
tobj = Thread.getThis();
|
|
atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, false);
|
|
tobj.m_curr.tstack = tobj.m_curr.bstack;
|
|
}
|
|
}
|
|
|
|
|
|
version (unittest)
|
|
{
|
|
class TestFiber : Fiber
|
|
{
|
|
this()
|
|
{
|
|
super(&run);
|
|
}
|
|
|
|
void run()
|
|
{
|
|
foreach (i; 0 .. 1000)
|
|
{
|
|
sum += i;
|
|
Fiber.yield();
|
|
}
|
|
}
|
|
|
|
enum expSum = 1000 * 999 / 2;
|
|
size_t sum;
|
|
}
|
|
|
|
void runTen()
|
|
{
|
|
TestFiber[10] fibs;
|
|
foreach (ref fib; fibs)
|
|
fib = new TestFiber();
|
|
|
|
bool cont;
|
|
do {
|
|
cont = false;
|
|
foreach (fib; fibs) {
|
|
if (fib.state == Fiber.State.HOLD)
|
|
{
|
|
fib.call();
|
|
cont |= fib.state != Fiber.State.TERM;
|
|
}
|
|
}
|
|
} while (cont);
|
|
|
|
foreach (fib; fibs)
|
|
{
|
|
assert(fib.sum == TestFiber.expSum);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Single thread running separate fibers
|
|
unittest
|
|
{
|
|
runTen();
|
|
}
|
|
|
|
|
|
// Multiple threads running separate fibers
|
|
unittest
|
|
{
|
|
auto group = new ThreadGroup();
|
|
foreach (_; 0 .. 4)
|
|
{
|
|
group.create(&runTen);
|
|
}
|
|
group.joinAll();
|
|
}
|
|
|
|
|
|
// Multiple threads running shared fibers
|
|
version (PPC) version = UnsafeFiberMigration;
|
|
version (PPC64) version = UnsafeFiberMigration;
|
|
|
|
version (UnsafeFiberMigration)
|
|
{
|
|
// XBUG: core.thread fibers are supposed to be safe to migrate across
|
|
// threads, however, there is a problem: GCC always assumes that the
|
|
// address of thread-local variables don't change while on a given stack.
|
|
// In consequence, migrating fibers between threads currently is an unsafe
|
|
// thing to do, and will break on some targets (possibly PR26461).
|
|
}
|
|
else
|
|
{
|
|
version = FiberMigrationUnittest;
|
|
}
|
|
|
|
version (FiberMigrationUnittest)
|
|
unittest
|
|
{
|
|
shared bool[10] locks;
|
|
TestFiber[10] fibs;
|
|
|
|
void runShared()
|
|
{
|
|
bool cont;
|
|
do {
|
|
cont = false;
|
|
foreach (idx; 0 .. 10)
|
|
{
|
|
if (cas(&locks[idx], false, true))
|
|
{
|
|
if (fibs[idx].state == Fiber.State.HOLD)
|
|
{
|
|
fibs[idx].call();
|
|
cont |= fibs[idx].state != Fiber.State.TERM;
|
|
}
|
|
locks[idx] = false;
|
|
}
|
|
else
|
|
{
|
|
cont = true;
|
|
}
|
|
}
|
|
} while (cont);
|
|
}
|
|
|
|
foreach (ref fib; fibs)
|
|
{
|
|
fib = new TestFiber();
|
|
}
|
|
|
|
auto group = new ThreadGroup();
|
|
foreach (_; 0 .. 4)
|
|
{
|
|
group.create(&runShared);
|
|
}
|
|
group.joinAll();
|
|
|
|
foreach (fib; fibs)
|
|
{
|
|
assert(fib.sum == TestFiber.expSum);
|
|
}
|
|
}
|
|
|
|
|
|
// Test exception handling inside fibers.
|
|
version (Win32) {
|
|
// broken on win32 under windows server 2012: bug 13821
|
|
} else unittest {
|
|
enum MSG = "Test message.";
|
|
string caughtMsg;
|
|
(new Fiber({
|
|
try
|
|
{
|
|
throw new Exception(MSG);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
caughtMsg = e.msg;
|
|
}
|
|
})).call();
|
|
assert(caughtMsg == MSG);
|
|
}
|
|
|
|
|
|
unittest
|
|
{
|
|
int x = 0;
|
|
|
|
(new Fiber({
|
|
x++;
|
|
})).call();
|
|
assert( x == 1 );
|
|
}
|
|
|
|
nothrow unittest
|
|
{
|
|
new Fiber({}).call!(Fiber.Rethrow.no)();
|
|
}
|
|
|
|
unittest
|
|
{
|
|
new Fiber({}).call(Fiber.Rethrow.yes);
|
|
new Fiber({}).call(Fiber.Rethrow.no);
|
|
}
|
|
|
|
deprecated unittest
|
|
{
|
|
new Fiber({}).call(true);
|
|
new Fiber({}).call(false);
|
|
}
|
|
|
|
version (Win32) {
|
|
// broken on win32 under windows server 2012: bug 13821
|
|
} else unittest {
|
|
enum MSG = "Test message.";
|
|
|
|
try
|
|
{
|
|
(new Fiber({
|
|
throw new Exception( MSG );
|
|
})).call();
|
|
assert( false, "Expected rethrown exception." );
|
|
}
|
|
catch ( Throwable t )
|
|
{
|
|
assert( t.msg == MSG );
|
|
}
|
|
}
|
|
|
|
// Test exception chaining when switching contexts in finally blocks.
|
|
unittest
|
|
{
|
|
static void throwAndYield(string msg) {
|
|
try {
|
|
throw new Exception(msg);
|
|
} finally {
|
|
Fiber.yield();
|
|
}
|
|
}
|
|
|
|
static void fiber(string name) {
|
|
try {
|
|
try {
|
|
throwAndYield(name ~ ".1");
|
|
} finally {
|
|
throwAndYield(name ~ ".2");
|
|
}
|
|
} catch (Exception e) {
|
|
assert(e.msg == name ~ ".1");
|
|
assert(e.next);
|
|
assert(e.next.msg == name ~ ".2");
|
|
assert(!e.next.next);
|
|
}
|
|
}
|
|
|
|
auto first = new Fiber(() => fiber("first"));
|
|
auto second = new Fiber(() => fiber("second"));
|
|
first.call();
|
|
second.call();
|
|
first.call();
|
|
second.call();
|
|
first.call();
|
|
second.call();
|
|
assert(first.state == Fiber.State.TERM);
|
|
assert(second.state == Fiber.State.TERM);
|
|
}
|
|
|
|
// Test Fiber resetting
|
|
unittest
|
|
{
|
|
static string method;
|
|
|
|
static void foo()
|
|
{
|
|
method = "foo";
|
|
}
|
|
|
|
void bar()
|
|
{
|
|
method = "bar";
|
|
}
|
|
|
|
static void expect(Fiber fib, string s)
|
|
{
|
|
assert(fib.state == Fiber.State.HOLD);
|
|
fib.call();
|
|
assert(fib.state == Fiber.State.TERM);
|
|
assert(method == s); method = null;
|
|
}
|
|
auto fib = new Fiber(&foo);
|
|
expect(fib, "foo");
|
|
|
|
fib.reset();
|
|
expect(fib, "foo");
|
|
|
|
fib.reset(&foo);
|
|
expect(fib, "foo");
|
|
|
|
fib.reset(&bar);
|
|
expect(fib, "bar");
|
|
|
|
fib.reset(function void(){method = "function";});
|
|
expect(fib, "function");
|
|
|
|
fib.reset(delegate void(){method = "delegate";});
|
|
expect(fib, "delegate");
|
|
}
|
|
|
|
// Test unsafe reset in hold state
|
|
unittest
|
|
{
|
|
auto fib = new Fiber(function {ubyte[2048] buf = void; Fiber.yield();}, 4096);
|
|
foreach (_; 0 .. 10)
|
|
{
|
|
fib.call();
|
|
assert(fib.state == Fiber.State.HOLD);
|
|
fib.reset();
|
|
}
|
|
}
|
|
|
|
// stress testing GC stack scanning
|
|
unittest
|
|
{
|
|
import core.memory;
|
|
|
|
static void unreferencedThreadObject()
|
|
{
|
|
static void sleep() { Thread.sleep(dur!"msecs"(100)); }
|
|
auto thread = new Thread(&sleep).start();
|
|
}
|
|
unreferencedThreadObject();
|
|
GC.collect();
|
|
|
|
static class Foo
|
|
{
|
|
this(int value)
|
|
{
|
|
_value = value;
|
|
}
|
|
|
|
int bar()
|
|
{
|
|
return _value;
|
|
}
|
|
|
|
int _value;
|
|
}
|
|
|
|
static void collect()
|
|
{
|
|
auto foo = new Foo(2);
|
|
assert(foo.bar() == 2);
|
|
GC.collect();
|
|
Fiber.yield();
|
|
GC.collect();
|
|
assert(foo.bar() == 2);
|
|
}
|
|
|
|
auto fiber = new Fiber(&collect);
|
|
|
|
fiber.call();
|
|
GC.collect();
|
|
fiber.call();
|
|
|
|
// thread reference
|
|
auto foo = new Foo(2);
|
|
|
|
void collect2()
|
|
{
|
|
assert(foo.bar() == 2);
|
|
GC.collect();
|
|
Fiber.yield();
|
|
GC.collect();
|
|
assert(foo.bar() == 2);
|
|
}
|
|
|
|
fiber = new Fiber(&collect2);
|
|
|
|
fiber.call();
|
|
GC.collect();
|
|
fiber.call();
|
|
|
|
static void recurse(size_t cnt)
|
|
{
|
|
--cnt;
|
|
Fiber.yield();
|
|
if (cnt)
|
|
{
|
|
auto fib = new Fiber(() { recurse(cnt); });
|
|
fib.call();
|
|
GC.collect();
|
|
fib.call();
|
|
}
|
|
}
|
|
fiber = new Fiber(() { recurse(20); });
|
|
fiber.call();
|
|
}
|
|
|
|
|
|
version (AsmX86_64_Windows)
|
|
{
|
|
// Test Windows x64 calling convention
|
|
unittest
|
|
{
|
|
void testNonvolatileRegister(alias REG)()
|
|
{
|
|
auto zeroRegister = new Fiber(() {
|
|
mixin("asm pure nothrow @nogc { naked; xor "~REG~", "~REG~"; ret; }");
|
|
});
|
|
long after;
|
|
|
|
mixin("asm pure nothrow @nogc { mov "~REG~", 0xFFFFFFFFFFFFFFFF; }");
|
|
zeroRegister.call();
|
|
mixin("asm pure nothrow @nogc { mov after, "~REG~"; }");
|
|
|
|
assert(after == -1);
|
|
}
|
|
|
|
void testNonvolatileRegisterSSE(alias REG)()
|
|
{
|
|
auto zeroRegister = new Fiber(() {
|
|
mixin("asm pure nothrow @nogc { naked; xorpd "~REG~", "~REG~"; ret; }");
|
|
});
|
|
long[2] before = [0xFFFFFFFF_FFFFFFFF, 0xFFFFFFFF_FFFFFFFF], after;
|
|
|
|
mixin("asm pure nothrow @nogc { movdqu "~REG~", before; }");
|
|
zeroRegister.call();
|
|
mixin("asm pure nothrow @nogc { movdqu after, "~REG~"; }");
|
|
|
|
assert(before == after);
|
|
}
|
|
|
|
testNonvolatileRegister!("R12")();
|
|
testNonvolatileRegister!("R13")();
|
|
testNonvolatileRegister!("R14")();
|
|
testNonvolatileRegister!("R15")();
|
|
testNonvolatileRegister!("RDI")();
|
|
testNonvolatileRegister!("RSI")();
|
|
testNonvolatileRegister!("RBX")();
|
|
|
|
testNonvolatileRegisterSSE!("XMM6")();
|
|
testNonvolatileRegisterSSE!("XMM7")();
|
|
testNonvolatileRegisterSSE!("XMM8")();
|
|
testNonvolatileRegisterSSE!("XMM9")();
|
|
testNonvolatileRegisterSSE!("XMM10")();
|
|
testNonvolatileRegisterSSE!("XMM11")();
|
|
testNonvolatileRegisterSSE!("XMM12")();
|
|
testNonvolatileRegisterSSE!("XMM13")();
|
|
testNonvolatileRegisterSSE!("XMM14")();
|
|
testNonvolatileRegisterSSE!("XMM15")();
|
|
}
|
|
}
|
|
|
|
|
|
version (D_InlineAsm_X86_64)
|
|
{
|
|
unittest
|
|
{
|
|
void testStackAlignment()
|
|
{
|
|
void* pRSP;
|
|
asm pure nothrow @nogc
|
|
{
|
|
mov pRSP, RSP;
|
|
}
|
|
assert((cast(size_t)pRSP & 0xF) == 0);
|
|
}
|
|
|
|
auto fib = new Fiber(&testStackAlignment);
|
|
fib.call();
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
unittest
|
|
{
|
|
// use >PAGESIZE to avoid stack overflow (e.g. in an syscall)
|
|
auto thr = new Thread(function{}, 4096 + 1).start();
|
|
thr.join();
|
|
}
|
|
|
|
/**
|
|
* Represents the ID of a thread, as returned by $(D Thread.)$(LREF id).
|
|
* The exact type varies from platform to platform.
|
|
*/
|
|
version (Windows)
|
|
alias ThreadID = uint;
|
|
else
|
|
version (Posix)
|
|
alias ThreadID = pthread_t;
|