Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Thread detach stability #1989

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 43 additions & 5 deletions src/core/thread.d
Original file line number Diff line number Diff line change
Expand Up @@ -617,8 +617,11 @@ class Thread
}
else version( Posix )
{
pthread_detach( m_addr );
m_addr = m_addr.init;
if (m_isOwned)
{
pthread_detach( m_addr );
m_addr = m_addr.init;
}
}
version( Darwin )
{
Expand Down Expand Up @@ -726,6 +729,7 @@ class Thread
if( pthread_create( &m_addr, &attr, &thread_entryPoint, cast(void*) this ) != 0 )
onThreadError( "Error creating thread" );
}
m_isOwned = true;
}
version( Darwin )
{
Expand Down Expand Up @@ -1536,6 +1540,7 @@ private:
version( Posix )
{
shared bool m_isRunning;
bool m_isOwned;
}
bool m_isDaemon;
bool m_isInCriticalRegion;
Expand Down Expand Up @@ -2111,14 +2116,22 @@ extern (C) bool thread_isMainThread() nothrow @nogc


/**
* 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.
* 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. On
* Posix systems, the D Runtime does not take ownership of the thread;
* specifically, it does not call `pthread_detach` during cleanup.
*
* Threads registered by this routine should normally be deregistered by
* `thread_detachThis`. Although `thread_detachByAddr` and
* `thread_detachInstance` can be used as well, such threads cannot be registered
* again by `thread_attachThis` unless `thread_setThis` is called with the
* `null` reference first.
*
* 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) void rt_moduleTlsCtor();`
*/
extern (C) Thread thread_attachThis()
{
Expand All @@ -2145,6 +2158,7 @@ extern (C) Thread thread_attachThis()
thisContext.tstack = thisContext.bstack;

atomicStore!(MemoryOrder.raw)(thisThread.m_isRunning, true);
thisThread.m_isOwned = false;
}
thisThread.m_isDaemon = true;
thisThread.m_tlsgcdata = rt_tlsgc_init();
Expand Down Expand Up @@ -2239,8 +2253,14 @@ version( Windows )
*/
extern (C) void thread_detachThis() nothrow @nogc
{
Thread.slock.lock_nothrow();
scope(exit) Thread.slock.unlock_nothrow();

if (auto t = Thread.getThis())
{
Thread.remove(t);
t.setThis(null);
}
}


Expand All @@ -2257,6 +2277,9 @@ extern (C) void thread_detachThis() nothrow @nogc
*/
extern (C) void thread_detachByAddr( ThreadID addr )
{
Thread.slock.lock_nothrow();
scope(exit) Thread.slock.unlock_nothrow();

if( auto t = thread_findByAddr( addr ) )
Thread.remove( t );
}
Expand All @@ -2265,6 +2288,9 @@ extern (C) void thread_detachByAddr( ThreadID addr )
/// ditto
extern (C) void thread_detachInstance( Thread t ) nothrow @nogc
{
Thread.slock.lock_nothrow();
scope(exit) Thread.slock.unlock_nothrow();

Thread.remove( t );
}

Expand Down Expand Up @@ -2330,6 +2356,18 @@ extern (C) void thread_setThis(Thread t) nothrow @nogc
Thread.setThis(t);
}

unittest
{
auto t = new Thread(
{
auto old = Thread.getThis();
assert(old !is null);
thread_setThis(null);
assert(Thread.getThis() is null);
thread_setThis(old);
}).start;
t.join();
}

/**
* Joins all non-daemon threads that are currently running. This is done by
Expand Down
7 changes: 6 additions & 1 deletion test/thread/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
include ../common.mak

TESTS:=fiber_guard_page
TESTS:=fiber_guard_page attach_detach

.PHONY: all clean
all: $(addprefix $(ROOT)/,$(addsuffix .done,$(TESTS)))
Expand All @@ -11,6 +11,11 @@ $(ROOT)/fiber_guard_page.done: $(ROOT)/%.done : $(ROOT)/%
$(QUIET)$(TIMELIMIT)$(ROOT)/$* $(RUN_ARGS); rc=$$?; [ $$rc -eq 139 ] || [ $$rc -eq 138 ]
@touch $@

$(ROOT)/attach_detach.done: $(ROOT)/%.done : $(ROOT)/%
@echo Testing $*
$(QUIET)$(TIMELIMIT)$(ROOT)/$*
@touch $@

$(ROOT)/%: $(SRC)/%.d
$(QUIET)$(DMD) $(DFLAGS) -of$@ $<

Expand Down
160 changes: 160 additions & 0 deletions test/thread/src/attach_detach.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
version (Posix)
{

import core.thread;
import core.sys.posix.pthread;
import core.stdc.stdlib;
import core.stdc.time;

// This program creates threads that are started outside of D runtime and
// stresses attaching and detaching those threads to the D runtime.

struct MyThread
{
pthread_t t;
bool stopRequested;
}

enum totalThreads = 4;

enum runTimeSeconds = 5; // Must be less than timelimit's
MyThread[totalThreads] threads;

auto exerciseGC() {
int[] arr;
foreach (i; 0 .. 1000)
arr ~= i;
return arr;
}

// This represents an API function of a non-D framework. Since we don't have any
// control on the lifetime of this thread, we have to attach upon entry and
// detach upon exit.
void api_foo()
{
auto t = thread_attachThis();
scope(exit)
{
// Pick a detachment method
final switch (rand() % 3)
{
case 0:
thread_detachThis();
break;
case 1:
thread_detachByAddr(t.id);
// thread_setThis must be called by the detached thread; it happens
// to be the case in this test.
thread_setThis(null);
break;
case 2:
thread_detachInstance(t);
// thread_setThis must be called by the detached thread; it happens
// to be the case in this test.
thread_setThis(null);
break;
}
}

assert_thread_is_attached(t.id);
cast(void)exerciseGC();
}

// Make calls to an api function and exit when requested
extern(C) void * thread_func(void * arg)
{
MyThread *t = cast(MyThread*)arg;

while (!t.stopRequested)
api_foo();

return arg;
}

void start_thread(ref MyThread t)
{
pthread_attr_t attr;
int err = pthread_attr_init(&attr);
assert(!err);

t.stopRequested = false;
err = pthread_create(&t.t, &attr, &thread_func, cast(void*)&t);
assert(!err);

err = pthread_attr_destroy(&attr);
assert(!err);
}

void start_threads()
{
foreach (ref t; threads)
start_thread(t);
}

void stop_thread(ref MyThread t)
{
t.stopRequested = true;
const err = pthread_join(t.t, null);
assert(!err);

assert_thread_is_gone(t.t);
}

void stop_threads()
{
foreach (ref t; threads)
stop_thread(t);
}

void assert_thread_is_attached(pthread_t tid)
{
size_t found = 0;
foreach (t; Thread.getAll())
if (tid == t.id)
{
++found;
}
assert(found == 1);
}

void assert_thread_is_gone(pthread_t tid)
{
foreach (t; Thread.getAll())
assert(tid != t.id);
}

// Occasionally stop threads and start new ones
void watch_threads()
{
const start = time(null);

while ((time(null) - start) < runTimeSeconds)
{
foreach (ref t; threads)
{
const shouldStop = ((rand() % 100) == 0);
if (shouldStop)
{
stop_thread(t);
start_thread(t);
}
}
}
}

void main()
{
start_threads();
watch_threads();
stop_threads();
}

} // version (Posix)
else
{

void main()
{
}

}