/** * The threadbase module provides OS-independent code * for thread storage and management. * * Copyright: Copyright Sean Kelly 2005 - 2012. * License: Distributed under the * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Sean Kelly, Walter Bright, Alex Rønne Petersen, Martin Nowak * Source: $(DRUNTIMESRC core/thread/osthread.d) */ /* NOTE: This file has been patched from the original DMD distribution to * work with the GDC compiler. */ module core.thread.threadbase; import core.thread.context; import core.thread.types; import core.time; import core.sync.mutex; import core.stdc.stdlib : free, realloc; private { import core.internal.traits : externDFunc; // interface to rt.tlsgc 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); } /////////////////////////////////////////////////////////////////////////////// // Thread and Fiber Exceptions /////////////////////////////////////////////////////////////////////////////// /** * Base class for thread exceptions. */ class ThreadException : Exception { @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { super(msg, file, line, next); } @nogc @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 { @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { super(msg, file, line, next); } @nogc @safe pure nothrow this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__) { super(msg, file, line, next); } } private { // 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); alias swapContext = externDFunc!("core.thread.osthread.swapContext", void* function(void*) nothrow @nogc); alias getStackBottom = externDFunc!("core.thread.osthread.getStackBottom", void* function() nothrow @nogc); alias getStackTop = externDFunc!("core.thread.osthread.getStackTop", void* function() nothrow @nogc); } /////////////////////////////////////////////////////////////////////////////// // Thread /////////////////////////////////////////////////////////////////////////////// class ThreadBase { /////////////////////////////////////////////////////////////////////////// // Initialization /////////////////////////////////////////////////////////////////////////// this(void function() fn, size_t sz = 0) @safe pure nothrow @nogc in(fn) { this(sz); m_call = fn; } this(void delegate() dg, size_t sz = 0) @safe pure nothrow @nogc in(dg) { this(sz); m_call = dg; } /** * Cleans up any remaining resources used by this object. */ package bool destructBeforeDtor() nothrow @nogc { destroyDataStorageIfAvail(); bool no_context = m_addr == m_addr.init; bool not_registered = !next && !prev && (sm_tbeg !is this); return (no_context || not_registered); } package void tlsGCdataInit() nothrow @nogc { m_tlsgcdata = rt_tlsgc_init(); } package void initDataStorage() nothrow { assert(m_curr is &m_main); m_main.bstack = getStackBottom(); m_main.tstack = m_main.bstack; tlsGCdataInit(); } package void destroyDataStorage() nothrow @nogc { rt_tlsgc_destroy(m_tlsgcdata); m_tlsgcdata = null; } package void destroyDataStorageIfAvail() nothrow @nogc { if (m_tlsgcdata) destroyDataStorage(); } /////////////////////////////////////////////////////////////////////////// // General Actions /////////////////////////////////////////////////////////////////////////// /** * 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. */ abstract Throwable join(bool rethrow = true); /////////////////////////////////////////////////////////////////////////// // 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 the main thread, i.e. the thread * that initialized the runtime * * Returns: * true if the thread is the main thread */ final @property bool isMainThread() nothrow @nogc { return this is sm_main; } /** * Tests whether this thread is running. * * Returns: * true if the thread is running, false if not. */ @property bool isRunning() nothrow @nogc { if (m_addr == m_addr.init) return false; return true; } /////////////////////////////////////////////////////////////////////////// // 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 ThreadBase 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. version (GNU) pragma(inline, false); 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 ThreadBase.)$(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 ThreadBase[] getAll() { static void resize(ref ThreadBase[] 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 ThreadBase.)$(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 ThreadBase) dg) { static void resize(ref ThreadBase[] buf, size_t nlen) { import core.exception: onOutOfMemoryError; auto newBuf = cast(ThreadBase*)realloc(buf.ptr, nlen * size_t.sizeof); if (newBuf is null) onOutOfMemoryError(); buf = newBuf[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; } private static ThreadBase[] getAllImpl(alias resize)() { import core.atomic; ThreadBase[] 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 (ThreadBase t = sm_tbeg; t; t = t.next) buf[pos++] = t; return buf; } } } } /////////////////////////////////////////////////////////////////////////// // Actions on Calling Thread /////////////////////////////////////////////////////////////////////////// /** * Forces a context switch to occur away from the calling thread. */ private static void yield() @nogc nothrow { thread_yield(); } /////////////////////////////////////////////////////////////////////////// // Stuff That Should Go Away /////////////////////////////////////////////////////////////////////////// // // Initializes a thread object which has no associated executable function. // This is used for the main thread initialized in thread_init(). // package this(size_t sz = 0) @safe pure nothrow @nogc { m_sz = sz; m_curr = &m_main; } // // Thread entry point. Invokes the function or delegate passed on // construction (if any). // package final void run() { m_call(); } package: // // Local storage // static ThreadBase sm_this; // // Main process thread // __gshared ThreadBase sm_main; // // Standard thread data // ThreadID m_addr; Callable m_call; string m_name; size_t m_sz; bool m_isDaemon; bool m_isInCriticalRegion; Throwable m_unhandled; /////////////////////////////////////////////////////////////////////////// // Storage of Active Thread /////////////////////////////////////////////////////////////////////////// // // Sets a thread-local reference to the current thread object. // package static void setThis(ThreadBase t) nothrow @nogc { sm_this = t; } package(core.thread): StackContext m_main; StackContext* m_curr; bool m_lock; private void* m_tlsgcdata; /////////////////////////////////////////////////////////////////////////// // Thread Context and GC Scanning Support /////////////////////////////////////////////////////////////////////////// final void pushContext(StackContext* c) nothrow @nogc in { assert(!c.within); } do { 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); } do { StackContext* c = m_curr; m_curr = c.within; c.ehContext = swapContext(m_curr.ehContext); c.within = null; } private final StackContext* topContext() nothrow @nogc in(m_curr) { return m_curr; } package(core.thread): /////////////////////////////////////////////////////////////////////////// // 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() @nogc { import core.lifetime : emplace; emplace!Mutex(_slock[]); emplace!Mutex(_criticalRegionLock[]); } static void termLocks() @nogc { (cast(Mutex)_slock.ptr).__dtor(); (cast(Mutex)_criticalRegionLock.ptr).__dtor(); } __gshared StackContext* sm_cbeg; __gshared ThreadBase sm_tbeg; __gshared size_t sm_tlen; // can't use core.internal.util.array in public code __gshared ThreadBase* pAboutToStart; __gshared size_t nAboutToStart; // // Used for ordering threads in the global thread list. // ThreadBase prev; ThreadBase next; /////////////////////////////////////////////////////////////////////////// // Global Context List Operations /////////////////////////////////////////////////////////////////////////// // // Add a context to the global context list. // static void add(StackContext* c) nothrow @nogc in { assert(c); assert(!c.next && !c.prev); } do { 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(StackContext* c) nothrow @nogc in { assert(c); assert(c.next || c.prev); } do { 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(ThreadBase t, bool rmAboutToStart = true) nothrow @nogc in { assert(t); assert(!t.next && !t.prev); } do { 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, size_t.sizeof * (nAboutToStart - idx - 1)); pAboutToStart = cast(ThreadBase*)realloc(pAboutToStart, size_t.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(ThreadBase t) nothrow @nogc in { assert(t); } do { // Thread was already removed earlier, might happen b/c of thread_detachInstance if (!t.next && !t.prev && (sm_tbeg !is t)) 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(); } } /////////////////////////////////////////////////////////////////////////////// // GC Support Routines /////////////////////////////////////////////////////////////////////////////// private alias attachThread = externDFunc!("core.thread.osthread.attachThread", ThreadBase function(ThreadBase) @nogc nothrow); extern (C) void _d_monitordelete_nogc(Object h) @nogc; /** * Terminates the thread module. No other thread routine may be called * afterwards. */ package void thread_term_tpl(ThreadT, MainThreadStore)(ref MainThreadStore _mainThreadStore) @nogc { assert(_mainThreadStore.ptr is cast(void*) ThreadBase.sm_main); // destruct manually as object.destroy is not @nogc (cast(ThreadT) cast(void*) ThreadBase.sm_main).__dtor(); _d_monitordelete_nogc(ThreadBase.sm_main); _mainThreadStore[] = __traits(initSymbol, ThreadT)[]; ThreadBase.sm_main = null; assert(ThreadBase.sm_tbeg && ThreadBase.sm_tlen == 1); assert(!ThreadBase.nAboutToStart); if (ThreadBase.pAboutToStart) // in case realloc(p, 0) doesn't return null { free(ThreadBase.pAboutToStart); ThreadBase.pAboutToStart = null; } ThreadBase.termLocks(); termLowlevelThreads(); } /** * */ extern (C) bool thread_isMainThread() nothrow @nogc { return ThreadBase.getThis() is ThreadBase.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(); */ package ThreadT thread_attachThis_tpl(ThreadT)() { if (auto t = ThreadT.getThis()) return t; return cast(ThreadT) attachThread(new ThreadT()); } /** * 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 = ThreadBase.getThis()) ThreadBase.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)) ThreadBase.remove(t); } /// ditto extern (C) void thread_detachInstance(ThreadBase t) nothrow @nogc { ThreadBase.remove(t); } /** * 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 ThreadBase thread_findByAddr(ThreadID addr) { ThreadBase.slock.lock_nothrow(); scope(exit) ThreadBase.slock.unlock_nothrow(); // also return just spawned thread so that // DLL_THREAD_ATTACH knows it's a D thread foreach (t; ThreadBase.pAboutToStart[0 .. ThreadBase.nAboutToStart]) if (t.m_addr == addr) return t; foreach (t; ThreadBase) 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 ThreadBase.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(ThreadBase t) nothrow @nogc { ThreadBase.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: ThreadBase.slock.lock_nothrow(); // wait for just spawned threads if (ThreadBase.nAboutToStart) { ThreadBase.slock.unlock_nothrow(); ThreadBase.yield(); goto Lagain; } // join all non-daemon threads, the main thread is also a daemon auto t = ThreadBase.sm_tbeg; while (t) { if (!t.isRunning) { auto tn = t.next; ThreadBase.remove(t); t = tn; } else if (t.isDaemon) { t = t.next; } else { ThreadBase.slock.unlock_nothrow(); t.join(); // might rethrow goto Lagain; // must restart iteration b/c of unlock } } ThreadBase.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 = ThreadBase.sm_tbeg; while (t) { auto tn = t.next; if (!t.isRunning) ThreadBase.remove(t); t = tn; } } // Used for needLock below. package __gshared bool multiThreadedFlag = false; // Used for suspendAll/resumeAll below. package __gshared uint suspendDepth = 0; private alias resume = externDFunc!("core.thread.osthread.resume", void function(ThreadBase) nothrow @nogc); /** * 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); } do { // NOTE: See thread_suspendAll for the logic behind this. if (!multiThreadedFlag && ThreadBase.sm_tbeg) { if (--suspendDepth == 0) resume(ThreadBase.getThis()); return; } scope(exit) ThreadBase.slock.unlock_nothrow(); { if (--suspendDepth > 0) return; for (ThreadBase t = ThreadBase.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); } do { callWithStackShell(sp => scanAllTypeImpl(scan, sp)); } package alias callWithStackShellDg = void delegate(void* sp) nothrow; private alias callWithStackShell = externDFunc!("core.thread.osthread.callWithStackShell", void function(scope callWithStackShellDg) nothrow); private void scanAllTypeImpl(scope ScanAllThreadsTypeFn scan, void* curStackTop) nothrow { ThreadBase thisThread = null; void* oldStackTop = null; if (ThreadBase.sm_tbeg) { thisThread = ThreadBase.getThis(); if (!thisThread.m_lock) { oldStackTop = thisThread.m_curr.tstack; thisThread.m_curr.tstack = curStackTop; } } scope(exit) { if (ThreadBase.sm_tbeg) { if (!thisThread.m_lock) { thisThread.m_curr.tstack = oldStackTop; } } } // NOTE: Synchronizing on ThreadBase.slock is not needed because this // function may only be called after all other threads have // been suspended from within the same lock. if (ThreadBase.nAboutToStart) scan(ScanType.stack, ThreadBase.pAboutToStart, ThreadBase.pAboutToStart + ThreadBase.nAboutToStart); for (StackContext* c = ThreadBase.sm_cbeg; c; c = c.next) { static if (isStackGrowingDown) { assert(c.tstack <= c.bstack, "stack bottom can't be less than top"); // NOTE: We can't index past the bottom of the stack // so don't do the "+1" if isStackGrowingDown. if (c.tstack && c.tstack < c.bstack) scan(ScanType.stack, c.tstack, c.bstack); } else { assert(c.bstack <= c.tstack, "stack top can't be less than bottom"); if (c.bstack && c.bstack < c.tstack) scan(ScanType.stack, c.bstack, c.tstack + 1); } } for (ThreadBase t = ThreadBase.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. scanWindowsOnly(scan, t); } if (t.m_tlsgcdata !is null) rt_tlsgc_scan(t.m_tlsgcdata, (p1, p2) => scan(ScanType.tls, p1, p2)); } } version (Windows) { // Currently scanWindowsOnly can't be handled properly by externDFunc // https://github.com/dlang/druntime/pull/3135#issuecomment-643673218 pragma(mangle, "_D4core6thread8osthread15scanWindowsOnlyFNbMDFNbEQBvQBt10threadbase8ScanTypePvQcZvCQDdQDbQBi10ThreadBaseZv") private extern (D) void scanWindowsOnly(scope ScanAllThreadsTypeFn scan, ThreadBase) nothrow; } /** * 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)); } private alias thread_yield = externDFunc!("core.thread.osthread.thread_yield", void function() @nogc nothrow); /** * 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(ThreadBase.getThis()); } do { synchronized (ThreadBase.criticalRegionLock) ThreadBase.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(ThreadBase.getThis()); } do { synchronized (ThreadBase.criticalRegionLock) ThreadBase.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(ThreadBase.getThis()); } do { synchronized (ThreadBase.criticalRegionLock) return ThreadBase.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 * * Returns: * never returns * Throws: * ThreadError. */ package void onThreadError(string msg) nothrow @nogc { __gshared ThreadError error = new ThreadError(null); error.msg = msg; error.next = null; 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()); } /** * 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 (ThreadBase t = ThreadBase.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); } } /** * 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(ThreadBase.getThis()); } do { 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 (ThreadBase.getThis()) { return ThreadBase.getThis().topContext().bstack; } /////////////////////////////////////////////////////////////////////////////// // lowlovel threading support /////////////////////////////////////////////////////////////////////////////// package { __gshared size_t ll_nThreads; __gshared ll_ThreadData* ll_pThreads; __gshared align(mutexAlign) void[mutexClassInstanceSize] ll_lock; @property Mutex lowlevelLock() nothrow @nogc { return cast(Mutex)ll_lock.ptr; } void initLowlevelThreads() @nogc { import core.lifetime : emplace; emplace(lowlevelLock()); } void termLowlevelThreads() @nogc { lowlevelLock.__dtor(); } void ll_removeThread(ThreadID tid) nothrow @nogc { lowlevelLock.lock_nothrow(); scope(exit) lowlevelLock.unlock_nothrow(); foreach (i; 0 .. ll_nThreads) { if (tid is ll_pThreads[i].tid) { import core.stdc.string : memmove; memmove(ll_pThreads + i, ll_pThreads + i + 1, ll_ThreadData.sizeof * (ll_nThreads - i - 1)); --ll_nThreads; // no need to minimize, next add will do break; } } } } /** * Check whether a thread was created by `createLowLevelThread`. * * Params: * tid = the platform specific thread ID. * * Returns: `true` if the thread was created by `createLowLevelThread` and is still running. */ bool findLowLevelThread(ThreadID tid) nothrow @nogc { lowlevelLock.lock_nothrow(); scope(exit) lowlevelLock.unlock_nothrow(); foreach (i; 0 .. ll_nThreads) if (tid is ll_pThreads[i].tid) return true; return false; }