Assert if a thread dies while registered, but make a best effort to continue working after the assertion, by marking the thread as dead and moving it to a ring of dead threads.

Copied from Perforce
 Change: 187393
 ServerID: perforce.ravenbrook.com
This commit is contained in:
Gareth Rees 2014-10-25 17:41:42 +01:00
parent d0ba77de47
commit abbdcfc59f
14 changed files with 187 additions and 109 deletions

View file

@ -153,6 +153,7 @@ Bool GlobalsCheck(Globals arenaGlobals)
}
CHECKD_NOSIG(Ring, &arena->threadRing);
CHECKD_NOSIG(Ring, &arena->deadRing);
CHECKL(BoolCheck(arena->insideShield));
CHECKL(arena->shCacheLimit <= ShieldCacheSIZE);
@ -277,6 +278,7 @@ Res GlobalsInit(Globals arenaGlobals)
arenaGlobals->rememberedSummaryIndex = 0;
RingInit(&arena->threadRing);
RingInit(&arena->deadRing);
arena->threadSerial = (Serial)0;
RingInit(&arena->formatRing);
arena->formatSerial = (Serial)0;
@ -405,6 +407,7 @@ void GlobalsFinish(Globals arenaGlobals)
RingFinish(&arena->chainRing);
RingFinish(&arena->messageRing);
RingFinish(&arena->threadRing);
RingFinish(&arena->deadRing);
for(rank = RankMIN; rank < RankLIMIT; ++rank)
RingFinish(&arena->greyRing[rank]);
RingFinish(&arenaGlobals->rootRing);
@ -495,6 +498,7 @@ void GlobalsPrepareToDestroy(Globals arenaGlobals)
AVER(RingIsSingle(&arena->chainRing));
AVER(RingIsSingle(&arena->messageRing));
AVER(RingIsSingle(&arena->threadRing));
AVER(RingIsSingle(&arena->deadRing));
AVER(RingIsSingle(&arenaGlobals->rootRing));
for(rank = RankMIN; rank < RankLIMIT; ++rank)
AVER(RingIsSingle(&arena->greyRing[rank]));

View file

@ -520,6 +520,7 @@ extern Ring GlobalsRememberedSummaryRing(Globals);
#define GlobalsArena(glob) PARENT(ArenaStruct, globals, glob)
#define ArenaThreadRing(arena) (&(arena)->threadRing)
#define ArenaDeadRing(arena) (&(arena)->deadRing)
#define ArenaEpoch(arena) ((arena)->epoch) /* .epoch.ts */
#define ArenaTrace(arena, ti) (&(arena)->trace[ti])
#define ArenaZoneShift(arena) ((arena)->zoneShift)

View file

@ -755,6 +755,7 @@ typedef struct mps_arena_s {
/* thread fields (<code/thread.c>) */
RingStruct threadRing; /* ring of attached threads */
RingStruct deadRing; /* ring of dead threads */
Serial threadSerial; /* serial of next thread */
/* shield fields (<code/shield.c>) */

View file

@ -83,7 +83,7 @@ void (ShieldSuspend)(Arena arena)
AVER(arena->insideShield);
if (!arena->suspended) {
ThreadRingSuspend(ArenaThreadRing(arena));
ThreadRingSuspend(ArenaThreadRing(arena), ArenaDeadRing(arena));
arena->suspended = TRUE;
}
}
@ -263,7 +263,7 @@ void (ShieldLeave)(Arena arena)
/* Ensuring the mutator is running at this point
* guarantees inv.outside.running */
if (arena->suspended) {
ThreadRingResume(ArenaThreadRing(arena));
ThreadRingResume(ArenaThreadRing(arena), ArenaDeadRing(arena));
arena->suspended = FALSE;
}
arena->insideShield = FALSE;

View file

@ -47,13 +47,14 @@ extern void ThreadDeregister(Thread thread, Arena arena);
/* ThreadRingSuspend/Resume
*
* These functions suspend/resume the threads on the ring.
* If the current thread is among them, it is not suspended,
* nor is any attempt to resume it made.
* These functions suspend/resume the threads on the ring. If the
* current thread is among them, it is not suspended, nor is any
* attempt to resume it made. Threads that can't be suspended/resumed
* because they are dead are moved to deadRing.
*/
extern void ThreadRingSuspend(Ring threadRing);
extern void ThreadRingResume(Ring threadRing);
extern void ThreadRingSuspend(Ring threadRing, Ring deadRing);
extern void ThreadRingResume(Ring threadRing, Ring deadRing);
/* ThreadRingThread

View file

@ -86,14 +86,16 @@ void ThreadDeregister(Thread thread, Arena arena)
}
void ThreadRingSuspend(Ring threadRing)
void ThreadRingSuspend(Ring threadRing, Ring deadRing)
{
AVERT(Ring, threadRing);
AVERT(Ring, deadRing);
}
void ThreadRingResume(Ring threadRing)
void ThreadRingResume(Ring threadRing, Ring deadRing)
{
AVERT(Ring, threadRing);
AVERT(Ring, deadRing);
}
Thread ThreadRingThread(Ring threadRing)

View file

@ -12,10 +12,10 @@
*
* ASSUMPTIONS
*
* .error.resume: PThreadextResume is assumed to succeed unless the thread
* has been destroyed.
* .error.suspend: PThreadextSuspend is assumed to succeed unless the thread
* has been destroyed. In this case, the suspend context is set to NULL;
* .error.resume: PThreadextResume is assumed to succeed unless the
* thread has been terminated.
* .error.suspend: PThreadextSuspend is assumed to succeed unless the
* thread has been terminated.
*
* .stack.full-descend: assumes full descending stack.
* i.e. stack pointer points to the last allocated location;
@ -48,9 +48,10 @@ typedef struct mps_thr_s { /* PThreads thread structure */
Serial serial; /* from arena->threadSerial */
Arena arena; /* owning arena */
RingStruct arenaRing; /* threads attached to arena */
Bool alive; /* thread believed to be alive? */
PThreadextStruct thrextStruct; /* PThreads extension */
pthread_t id; /* Pthread object of thread */
MutatorFaultContext mfc; /* Context if thread is suspended */
MutatorFaultContext mfc; /* Context if suspended, NULL if not */
} ThreadStruct;
@ -62,6 +63,7 @@ Bool ThreadCheck(Thread thread)
CHECKU(Arena, thread->arena);
CHECKL(thread->serial < thread->arena->threadSerial);
CHECKD_NOSIG(Ring, &thread->arenaRing);
CHECKL(BoolCheck(thread->alive));
CHECKD(PThreadext, &thread->thrextStruct);
return TRUE;
}
@ -98,6 +100,7 @@ Res ThreadRegister(Thread *threadReturn, Arena arena)
thread->serial = arena->threadSerial;
++arena->threadSerial;
thread->arena = arena;
thread->alive = TRUE;
thread->mfc = NULL;
PThreadextInit(&thread->thrextStruct, thread->id);
@ -130,69 +133,80 @@ void ThreadDeregister(Thread thread, Arena arena)
}
/* mapThreadRing -- map over threads on ring calling a function on each one
* except the current thread
/* mapThreadRing -- map over threads on ring calling a function on
* each one except the current thread.
*
* Threads that are found to be dead (that is, if func returns FALSE)
* are moved to deadRing.
*/
static void mapThreadRing(Ring threadRing, void (*func)(Thread))
static void mapThreadRing(Ring threadRing, Ring deadRing, Res (*func)(Thread))
{
Ring node, next;
pthread_t self;
AVERT(Ring, threadRing);
AVERT(Ring, deadRing);
AVER(FUNCHECK(func));
self = pthread_self();
RING_FOR(node, threadRing, next) {
Thread thread = RING_ELT(Thread, arenaRing, node);
AVERT(Thread, thread);
if(! pthread_equal(self, thread->id)) /* .thread.id */
(*func)(thread);
AVER(thread->alive);
if (!pthread_equal(self, thread->id) /* .thread.id */
&& !(*func)(thread))
{
thread->alive = FALSE;
RingRemove(&thread->arenaRing);
RingAppend(deadRing, &thread->arenaRing);
}
}
}
/* ThreadRingSuspend -- suspend all threads on a ring, expect the current one */
/* ThreadRingSuspend -- suspend all threads on a ring, except the
* current one.
*/
static void threadSuspend(Thread thread)
static Bool threadSuspend(Thread thread)
{
/* .error.suspend */
/* In the error case (PThreadextSuspend returning ResFAIL), we */
/* assume the thread has been destroyed. */
/* In which case we simply continue. */
/* .error.suspend: if PThreadextSuspend fails, we assume the thread
* has been terminated. */
Res res;
AVER(thread->mfc == NULL);
res = PThreadextSuspend(&thread->thrextStruct, &thread->mfc);
if(res != ResOK)
thread->mfc = NULL;
AVER(res == ResOK);
AVER(thread->mfc != NULL);
return res == ResOK;
}
void ThreadRingSuspend(Ring threadRing)
void ThreadRingSuspend(Ring threadRing, Ring deadRing)
{
mapThreadRing(threadRing, threadSuspend);
mapThreadRing(threadRing, deadRing, threadSuspend);
}
/* ThreadRingResume -- resume all threads on a ring (expect the current one) */
static void threadResume(Thread thread)
static Bool threadResume(Thread thread)
{
/* .error.resume */
/* If the previous suspend failed (thread->mfc == NULL), */
/* or in the error case (PThreadextResume returning ResFAIL), */
/* assume the thread has been destroyed. */
/* In which case we simply continue. */
if(thread->mfc != NULL) {
(void)PThreadextResume(&thread->thrextStruct);
thread->mfc = NULL;
}
Res res;
/* .error.resume: If PThreadextResume fails, we assume the thread
* has been terminated. */
AVER(thread->mfc != NULL);
res = PThreadextResume(&thread->thrextStruct);
AVER(res == ResOK);
thread->mfc = NULL;
return res == ResOK;
}
void ThreadRingResume(Ring threadRing)
void ThreadRingResume(Ring threadRing, Ring deadRing)
{
mapThreadRing(threadRing, threadResume);
mapThreadRing(threadRing, deadRing, threadResume);
}
@ -231,20 +245,16 @@ Res ThreadScan(ScanState ss, Thread thread, void *stackBot)
self = pthread_self();
if(pthread_equal(self, thread->id)) {
/* scan this thread's stack */
AVER(thread->alive);
res = StackScan(ss, stackBot);
if(res != ResOK)
return res;
} else {
} else if (thread->alive) {
MutatorFaultContext mfc;
Addr *stackBase, *stackLimit, stackPtr;
mfc = thread->mfc;
if(mfc == NULL) {
/* .error.suspend */
/* We assume that the thread must have been destroyed. */
/* We ignore the situation by returning immediately. */
return ResOK;
}
AVER(mfc != NULL);
stackPtr = MutatorFaultContextSP(mfc);
/* .stack.align */
@ -280,6 +290,7 @@ Res ThreadDescribe(Thread thread, mps_lib_FILE *stream, Count depth)
"Thread $P ($U) {\n", (WriteFP)thread, (WriteFU)thread->serial,
" arena $P ($U)\n",
(WriteFP)thread->arena, (WriteFU)thread->arena->serial,
" alive $S\n", WriteFYesNo(thread->alive),
" id $U\n", (WriteFU)thread->id,
"} Thread $P ($U)\n", (WriteFP)thread, (WriteFU)thread->serial,
NULL);

View file

@ -109,6 +109,7 @@ Res ThreadRegister(Thread *threadReturn, Arena arena)
thread->serial = arena->threadSerial;
++arena->threadSerial;
thread->arena = arena;
thread->alive = TRUE;
AVERT(Thread, thread);
@ -138,60 +139,66 @@ void ThreadDeregister(Thread thread, Arena arena)
}
/* Map over threads on ring calling f on each one except the
* current thread.
/* mapThreadRing -- map over threads on ring calling a function on
* each one except the current thread.
*
* Threads that are found to be dead (that is, if func returns FALSE)
* are moved to deadRing.
*/
static void mapThreadRing(Ring ring, void (*f)(Thread thread))
static void mapThreadRing(Ring threadRing, Ring deadRing, Bool (*func)(Thread))
{
Ring node;
Ring node, next;
DWORD id;
AVERT(Ring, threadRing);
AVERT(Ring, deadRing);
AVER(FUNCHECK(func));
id = GetCurrentThreadId();
node = RingNext(ring);
while(node != ring) {
Ring next = RingNext(node);
Thread thread;
thread = RING_ELT(Thread, arenaRing, node);
RING_FOR(node, threadRing, next) {
Thread thread = RING_ELT(Thread, arenaRing, node);
AVERT(Thread, thread);
if(id != thread->id) /* .thread.id */
(*f)(thread);
node = next;
AVER(thread->alive);
if (id != thread->id /* .thread.id */
&& !(*func)(thread))
{
thread->alive = FALSE;
RingRemove(&thread->arenaRing);
RingAppend(deadRing, &thread->arenaRing);
}
}
}
static void suspend(Thread thread)
static Bool suspendThread(Thread thread)
{
/* .thread.handle.susp-res */
/* .error.suspend */
/* In the error case (SuspendThread returning 0xFFFFFFFF), we */
/* assume the thread has been destroyed (as part of process shutdown). */
/* In which case we simply continue. */
/* In the error case (SuspendThread returning -1), we */
/* assume the thread has been terminated. */
/* [GetLastError appears to return 5 when SuspendThread is called */
/* on a destroyed thread, but I'm not sufficiently confident of this */
/* on a terminated thread, but I'm not sufficiently confident of this */
/* to check -- drj 1998-04-09] */
(void)SuspendThread(thread->handle);
return SuspendThread(thread->handle) != (DWORD)-1;
}
void ThreadRingSuspend(Ring ring)
void ThreadRingSuspend(Ring threadRing, Ring deadRing)
{
mapThreadRing(ring, suspend);
mapThreadRing(threadRing, deadRing, suspendThread);
}
static void resume(Thread thread)
static Bool resumeThread(Thread thread)
{
/* .thread.handle.susp-res */
/* .error.resume */
/* In the error case (ResumeThread returning 0xFFFFFFFF), we */
/* assume the thread has been destroyed (as part of process shutdown). */
/* In which case we simply continue. */
(void)ResumeThread(thread->handle);
/* In the error case (ResumeThread returning -1), we */
/* assume the thread has been terminated. */
return ResumeThread(thread->handle) != (DWORD)-1;
}
void ThreadRingResume(Ring ring)
void ThreadRingResume(Ring threadRing, Ring deadRing)
{
mapThreadRing(ring, resume);
mapThreadRing(threadRing, deadRing, resumeThread);
}
@ -220,6 +227,7 @@ Res ThreadDescribe(Thread thread, mps_lib_FILE *stream, Count depth)
"Thread $P ($U) {\n", (WriteFP)thread, (WriteFU)thread->serial,
" arena $P ($U)\n",
(WriteFP)thread->arena, (WriteFU)thread->arena->serial,
" alive $S\n", WriteFYesNo(thread->alive),
" handle $W\n", (WriteFW)thread->handle,
" id $U\n", (WriteFU)thread->id,
"} Thread $P ($U)\n", (WriteFP)thread, (WriteFU)thread->serial,

View file

@ -26,6 +26,7 @@ typedef struct mps_thr_s { /* Win32 thread structure */
Serial serial; /* from arena->threadSerial */
Arena arena; /* owning arena */
RingStruct arenaRing; /* threads attached to arena */
Bool alive; /* thread believed to be alive? */
HANDLE handle; /* Handle of thread, see
* <code/thw3.c#thread.handle> */
DWORD id; /* Thread id of thread */

View file

@ -74,7 +74,13 @@ Res ThreadScan(ScanState ss, Thread thread, void *stackBot)
id = GetCurrentThreadId();
if(id != thread->id) { /* .thread.id */
if (id == thread->id) { /* .thread.id */
/* scan this thread's stack */
AVER(thread->alive);
res = StackScan(ss, stackBot);
if(res != ResOK)
return res;
} else if (thread->alive) {
CONTEXT context;
BOOL success;
Addr *stackBase, *stackLimit, stackPtr;
@ -116,11 +122,6 @@ Res ThreadScan(ScanState ss, Thread thread, void *stackBot)
(Addr *)((char *)&context + sizeof(CONTEXT)));
if(res != ResOK)
return res;
} else { /* scan this thread's stack */
res = StackScan(ss, stackBot);
if(res != ResOK)
return res;
}
return ResOK;

View file

@ -74,7 +74,13 @@ Res ThreadScan(ScanState ss, Thread thread, void *stackBot)
id = GetCurrentThreadId();
if(id != thread->id) { /* .thread.id */
if (id == thread->id) { /* .thread.id */
/* scan this thread's stack */
AVER(thread->alive);
res = StackScan(ss, stackBot);
if(res != ResOK)
return res;
} else if (thread->alive) {
CONTEXT context;
BOOL success;
Addr *stackBase, *stackLimit, stackPtr;
@ -116,11 +122,6 @@ Res ThreadScan(ScanState ss, Thread thread, void *stackBot)
(Addr *)((char *)&context + sizeof(CONTEXT)));
if(res != ResOK)
return res;
} else { /* scan this thread's stack */
res = StackScan(ss, stackBot);
if(res != ResOK)
return res;
}
return ResOK;

View file

@ -36,6 +36,7 @@ typedef struct mps_thr_s { /* OS X / Mach thread structure */
Serial serial; /* from arena->threadSerial */
Arena arena; /* owning arena */
RingStruct arenaRing; /* attaches to arena */
Bool alive; /* thread believed to be alive? */
thread_port_t port; /* thread kernel port */
} ThreadStruct;
@ -46,6 +47,7 @@ Bool ThreadCheck(Thread thread)
CHECKU(Arena, thread->arena);
CHECKL(thread->serial < thread->arena->threadSerial);
CHECKD_NOSIG(Ring, &thread->arenaRing);
CHECKL(BoolCheck(thread->alive));
CHECKL(MACH_PORT_VALID(thread->port));
return TRUE;
}
@ -78,6 +80,7 @@ Res ThreadRegister(Thread *threadReturn, Arena arena)
thread->serial = arena->threadSerial;
++arena->threadSerial;
thread->alive = TRUE;
thread->port = mach_thread_self();
thread->sig = ThreadSig;
AVERT(Thread, thread);
@ -108,62 +111,73 @@ void ThreadDeregister(Thread thread, Arena arena)
}
/* mapThreadRing -- map over threads on ring calling a function on each one
* except the current thread
/* mapThreadRing -- map over threads on ring calling a function on
* each one except the current thread.
*
* Threads that are found to be dead (that is, if func returns FALSE)
* are marked as dead and moved to deadRing.
*/
static void mapThreadRing(Ring threadRing, void (*func)(Thread))
static void mapThreadRing(Ring threadRing, Ring deadRing, Bool (*func)(Thread))
{
Ring node, next;
mach_port_t self;
AVERT(Ring, threadRing);
AVERT(Ring, deadRing);
AVER(FUNCHECK(func));
self = mach_thread_self();
AVER(MACH_PORT_VALID(self));
RING_FOR(node, threadRing, next) {
Thread thread = RING_ELT(Thread, arenaRing, node);
AVERT(Thread, thread);
if(thread->port != self)
(*func)(thread);
AVER(thread->alive);
if (thread->port != self
&& !(*func)(thread))
{
thread->alive = FALSE;
RingRemove(&thread->arenaRing);
RingAppend(deadRing, &thread->arenaRing);
}
}
}
static void threadSuspend(Thread thread)
static Bool threadSuspend(Thread thread)
{
kern_return_t kern_return;
kern_return = thread_suspend(thread->port);
/* No rendezvous is necessary: thread_suspend "prevents the thread
* from executing any more user-level instructions" */
AVER(kern_return == KERN_SUCCESS);
return kern_return == KERN_SUCCESS;
}
static void threadResume(Thread thread)
static Bool threadResume(Thread thread)
{
kern_return_t kern_return;
kern_return = thread_resume(thread->port);
/* Mach has no equivalent of EAGAIN. */
AVER(kern_return == KERN_SUCCESS);
return kern_return == KERN_SUCCESS;
}
/* ThreadRingSuspend -- suspend all threads on a ring, except the
* current one.
*/
void ThreadRingSuspend(Ring threadRing)
void ThreadRingSuspend(Ring threadRing, Ring deadRing)
{
AVERT(Ring, threadRing);
mapThreadRing(threadRing, threadSuspend);
mapThreadRing(threadRing, deadRing, threadSuspend);
}
/* ThreadRingResume -- resume all threads on a ring, except the
* current one.
*/
void ThreadRingResume(Ring threadRing)
void ThreadRingResume(Ring threadRing, Ring deadRing)
{
AVERT(Ring, threadRing);
mapThreadRing(threadRing, threadResume);
mapThreadRing(threadRing, deadRing, threadResume);
}
Thread ThreadRingThread(Ring threadRing)
@ -199,17 +213,18 @@ Res ThreadScan(ScanState ss, Thread thread, void *stackBot)
AVER(MACH_PORT_VALID(self));
if (thread->port == self) {
/* scan this thread's stack */
AVER(thread->alive);
res = StackScan(ss, stackBot);
if(res != ResOK)
return res;
} else {
} else if (thread->alive) {
MutatorFaultContextStruct mfcStruct;
THREAD_STATE_S threadState;
Addr *stackBase, *stackLimit, stackPtr;
mach_msg_type_number_t count;
kern_return_t kern_return;
/* Note: We could get the thread state and check the suspend cound in
/* Note: We could get the thread state and check the suspend count in
order to assert that the thread is suspended, but it's probably
unnecessary and is a lot of work to check a static condition. */
@ -257,6 +272,7 @@ Res ThreadDescribe(Thread thread, mps_lib_FILE *stream, Count depth)
"Thread $P ($U) {\n", (WriteFP)thread, (WriteFU)thread->serial,
" arena $P ($U)\n",
(WriteFP)thread->arena, (WriteFU)thread->arena->serial,
" alive $S\n", WriteFYesNo(thread->alive),
" port $U\n", (WriteFU)thread->port,
"} Thread $P ($U)\n", (WriteFP)thread, (WriteFU)thread->serial,
NULL);

View file

@ -54,6 +54,15 @@ which might provoke a collection. See request.dylan.160252_.)
.. _request.dylan.160252: https://info.ravenbrook.com/project/mps/import/2001-11-05/mmprevol/request/dylan/160252/
_`.req.thread.die`: It would be nice if the MPS coped with threads
that die while registered. (This makes it easier for a client program
to interface with foreign code that terminates threads without the
client program being given an opportunity to deregister them. See
request.dylan.160022_ and request.mps.160093_.)
.. _request.dylan.160022: https://info.ravenbrook.com/project/mps/import/2001-11-05/mmprevol/request/dylan/160022
.. _request.mps.160093: https://info.ravenbrook.com/project/mps/import/2001-11-05/mmprevol/request/mps/160093/
Design
------
@ -70,6 +79,22 @@ thread that might refer to, read from, or write to memory in
automatically managed pool classes is registered with the MPS. This is
documented in the manual under ``mps_thread_reg()``.
_`.sol.thread.term`: The thread manager cannot reliably detect that a
thread has terminated. The reason is that threading systems do not
guarantee behaviour in this case. For example, POSIX_ says, "A
conforming implementation is free to reuse a thread ID after its
lifetime has ended. If an application attempts to use a thread ID
whose lifetime has ended, the behavior is undefined." For this reason,
the documentation for ``mps_thread_dereg()`` specifies that it is an
error if a thread dies while registered.
.. _POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_02
_`.sol.thread.term.attempt`: Nonetheless, the thread manager makes a
"best effort" to continue running after detecting a terminated thread,
by moving the thread to a ring of dead threads, and avoiding scanning
it. This might allow a malfunctioning client program to limp along.
Interface
---------
@ -112,14 +137,16 @@ Otherwise, return a result code indicating the cause of the error.
_`.if.deregister`: Remove ``thread`` from the list of threads managed
by the arena and free it.
``void ThreadRingSuspend(Ring threadRing)``
``void ThreadRingSuspend(Ring threadRing, Ring deadRing)``
_`.if.ring.suspend`: Suspend all the threads on ``threadRing``, except
for the current thread.
for the current thread. If any threads are discovered to have
terminated, move them to ``deadRing``.
``void ThreadRingResume(Ring threadRing)``
``void ThreadRingResume(Ring threadRing, Ring deadRing)``
_`.if.ring.resume`: Resume all the threads on ``threadRing``.
_`.if.ring.resume`: Resume all the threads on ``threadRing``. If any
threads are discovered to have terminated, move them to ``deadRing``.
``Thread ThreadRingThread(Ring threadRing)``

View file

@ -100,6 +100,7 @@ Signal and exception handling issues
for co-operating: if you are in this situation, please :ref:`contact
us <contact>`.
.. index::
single: thread; interface
@ -142,6 +143,9 @@ Thread interface
It is recommended that all threads be registered with all
arenas.
It is an error if a thread terminates while it is registered. The
client program must call :c:func:`mps_thread_dereg` first.
.. c:function:: void mps_thread_dereg(mps_thr_t thr)