Skip to content

Commit 88b32a0

Browse files
authored
Merge pull request dlang#21289 from schveiguy/addgcthreadlifetime
Add hooks to the GC to manage thread lifetimes
2 parents 08a71cd + 280e915 commit 88b32a0

10 files changed

Lines changed: 147 additions & 12 deletions

File tree

druntime/src/core/gc/config.d

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import core.stdc.stdio : printf;
1212

1313
__gshared Config config;
1414

15+
private __gshared bool _initialized;
16+
1517
struct Config
1618
{
1719
bool disable; // start disabled
@@ -31,7 +33,9 @@ struct Config
3133

3234
bool initialize()
3335
{
34-
return initConfigOptions(this, "gcopt");
36+
if (!_initialized)
37+
_initialized = initConfigOptions(this, "gcopt");
38+
return _initialized;
3539
}
3640

3741
void help() @nogc nothrow

druntime/src/core/gc/gcinterface.d

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
*/
1414
module core.gc.gcinterface;
1515

16+
import core.thread.threadbase : ThreadBase;
17+
1618
static import core.memory;
1719
alias BlkAttr = core.memory.GC.BlkAttr;
1820
alias BlkInfo = core.memory.GC.BlkInfo;
@@ -270,4 +272,32 @@ interface GC
270272
* Returns: true if successful.
271273
*/
272274
bool shrinkArrayUsed(void[] slice, size_t existingUsed, bool atomic = false) nothrow;
275+
276+
/**
277+
* Prepare a thread for use with the GC after the GC is initialized. Note
278+
* that you can register an initThread function to call before the GC is
279+
* initialized, see core.gc.registry.
280+
*
281+
* This function is always called *from* the thread as it is being started,
282+
* before any static initializers. The thread object parameter may not yet
283+
* be registered as `ThreadBase.getThis()`, as this is called before that
284+
* happens.
285+
*/
286+
void initThread(ThreadBase thread) nothrow @nogc;
287+
288+
/**
289+
* Clean up any GC related data from the thread before it exits. There is
290+
* no equivalent of this function as a hook before the GC is initialized.
291+
* That is, the thread init function called before the GC is initialized
292+
* (from the registry hook) should assume this function may not be called
293+
* on termination if the GC is never initialized.
294+
*
295+
* Most times, this is called from the thread that is the given thread
296+
* reference, but it's possible the `thread` parameter is not the same as
297+
* the current thread.
298+
*
299+
* There is no guarantee the thread is still registered as
300+
* `ThreadBase.getThis()`.
301+
*/
302+
void cleanupThread(ThreadBase thread) nothrow @nogc;
273303
}

druntime/src/core/gc/registry.d

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
module core.gc.registry;
99

1010
import core.gc.gcinterface : GC;
11+
import core.thread.threadbase : ThreadBase;
1112

1213
/*@nogc nothrow:*/
1314

@@ -21,28 +22,49 @@ import core.gc.gcinterface : GC;
2122
*/
2223
alias GCFactory = GC function();
2324

25+
/**
26+
* A function that will initialize a thread before the GC has been initialized.
27+
* Once the GC is initialized, the interface method GC.initThread for each new
28+
* thread.
29+
*/
30+
alias GCThreadInitFunction = void function(ThreadBase base) nothrow @nogc;
31+
2432
/**
2533
* Register a GC factory under the given `name`. This function must be called
2634
* from a C constructor before druntime is initialized.
2735
*
2836
* To use the registered GC, it's name must be specified gcopt runtime option,
2937
* e.g. by passing $(TT, --DRT-gcopt=gc:my_gc_name) as application argument.
3038
*
39+
* The thread init function will be called *only before* the GC has been
40+
* initialized to the registered GC. It is called as the first step in starting
41+
* the new thread before the thread is registered with the runtime as a running
42+
* thread. This allows any specific thread data that is needed for running the
43+
* GC to be registered with the thread object.
44+
*
45+
* After the GC is initialized, the GC interface function `initThread` is
46+
* called instead. This function should expect the possibility that the
47+
* reciprocal `cleanupThread` method may not be called if the GC is never
48+
* initialized.
49+
*
3150
* Params:
3251
* name = name of the GC implementation; should be unique
3352
* factory = function to instantiate the implementation
53+
* threadInit = function to call from a new thread *before* registration in
54+
* the list of running threads, to set up any GC-specific data.
3455
* Note: The registry does not perform synchronization, as registration is
3556
* assumed to be executed serially, as is the case for C constructors.
3657
* See_Also:
3758
* $(LINK2 https://dlang.org/spec/garbage.html#gc_config, Configuring the Garbage Collector)
3859
*/
39-
void registerGCFactory(string name, GCFactory factory) nothrow @nogc
60+
void registerGCFactory(string name, GCFactory factory,
61+
GCThreadInitFunction threadInit = null) nothrow @nogc
4062
{
4163
import core.stdc.stdlib : realloc;
4264

4365
auto ptr = cast(Entry*)realloc(entries.ptr, (entries.length + 1) * Entry.sizeof);
4466
entries = ptr[0 .. entries.length + 1];
45-
entries[$ - 1] = Entry(name, factory);
67+
entries[$ - 1] = Entry(name, factory, threadInit);
4668
}
4769

4870
/**
@@ -70,6 +92,22 @@ GC createGCInstance(string name)
7092
return null;
7193
}
7294

95+
/**
96+
* Get the thread init function used for the selected GC.
97+
*
98+
* Note, this must be called before `createGCInstance`. It is typically called by
99+
* `rt_init`.
100+
*/
101+
GCThreadInitFunction threadInit(string name) nothrow @nogc
102+
{
103+
foreach (entry; entries)
104+
{
105+
if (entry.name == name)
106+
return entry.threadInit;
107+
}
108+
return null;
109+
}
110+
73111
// list of all registerd GCs
74112
const(Entry[]) registeredGCFactories(scope int dummy=0) nothrow @nogc
75113
{
@@ -82,6 +120,7 @@ struct Entry
82120
{
83121
string name;
84122
GCFactory factory;
123+
GCThreadInitFunction threadInit;
85124
}
86125

87126
__gshared Entry[] entries;

druntime/src/core/internal/gc/blkcache.d

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,16 @@ else
6363
}
6464

6565
// free the allocation on thread exit.
66-
@standalone static ~this()
66+
void cleanupBlkCache(void* storage) nothrow @nogc
6767
{
68-
if (__blkcache_storage)
68+
if (storage)
6969
{
70+
// check if this is the same thread as the current running thread, and
71+
// if so, make sure we don't leave a dangling pointer.
72+
if (__blkcache_storage is storage)
73+
__blkcache_storage = null;
7074
import core.stdc.stdlib : free;
71-
import core.thread.threadbase;
72-
auto tBase = ThreadBase.getThis();
73-
if (tBase !is null)
74-
tBase.tlsGCData = null;
75-
free(__blkcache_storage);
76-
__blkcache_storage = null;
75+
free(storage);
7776
}
7877
}
7978

druntime/src/core/internal/gc/impl/conservative/gc.d

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,6 +1637,14 @@ class ConservativeGC : GC
16371637

16381638
return existingCapacity - offset;
16391639
}
1640+
1641+
void initThread(ThreadBase t) nothrow @nogc { }
1642+
1643+
void cleanupThread(ThreadBase t) nothrow @nogc
1644+
{
1645+
cleanupBlkCache(t.tlsGCData);
1646+
t.tlsGCData = null;
1647+
}
16401648
}
16411649

16421650

druntime/src/core/internal/gc/impl/manual/gc.d

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import core.gc.gcinterface;
2323

2424
import core.internal.container.array;
2525

26+
import core.thread.threadbase : ThreadBase;
27+
2628
import cstdlib = core.stdc.stdlib : calloc, free, malloc, realloc;
2729
static import core.memory;
2830

@@ -287,4 +289,12 @@ class ManualGC : GC
287289
{
288290
return false;
289291
}
292+
293+
void initThread(ThreadBase t) nothrow @nogc
294+
{
295+
}
296+
297+
void cleanupThread(ThreadBase t) nothrow @nogc
298+
{
299+
}
290300
}

druntime/src/core/internal/gc/impl/proto/gc.d

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ module core.internal.gc.impl.proto.gc;
33

44
import core.gc.gcinterface;
55

6+
import core.gc.registry : GCThreadInitFunction, threadInit;
7+
8+
import core.thread.threadbase : ThreadBase;
9+
610
import core.internal.container.array;
711

812
import cstdlib = core.stdc.stdlib : calloc, free, malloc, realloc;
@@ -28,11 +32,15 @@ private
2832
extern (C) void gc_addRoot(const void* p ) nothrow @nogc;
2933
}
3034

35+
3136
class ProtoGC : GC
3237
{
3338
Array!Root roots;
3439
Array!Range ranges;
3540

41+
// stored on first use, which should be called whenever rt_init is called.
42+
private GCThreadInitFunction _initThreadFn;
43+
3644
// Call this function when initializing the real GC
3745
// upon ProtoGC term. This function should be called
3846
// after the real GC is in place.
@@ -261,4 +269,23 @@ class ProtoGC : GC
261269
{
262270
return false;
263271
}
272+
273+
void initThread(ThreadBase thread) nothrow
274+
{
275+
if (_initThreadFn is null)
276+
{
277+
static void defaultThreadInit(ThreadBase base) nothrow @nogc { }
278+
import core.gc.config;
279+
config.initialize();
280+
_initThreadFn = .threadInit(config.gc);
281+
if (_initThreadFn is null)
282+
_initThreadFn = &defaultThreadInit;
283+
}
284+
285+
_initThreadFn(thread);
286+
}
287+
288+
void cleanupThread(ThreadBase thread)
289+
{
290+
}
264291
}

druntime/src/core/internal/gc/proxy.d

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ extern (C)
266266
return instance.shrinkArrayUsed( slice, existingUsed, atomic );
267267
}
268268

269-
GC gc_getProxy() nothrow
269+
GC gc_getProxy() nothrow @nogc
270270
{
271271
return instance;
272272
}

druntime/src/core/thread/threadbase.d

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ class ThreadBase
133133
package void tlsRTdataInit() nothrow @nogc
134134
{
135135
m_tlsrtdata = rt_tlsgc_init();
136+
137+
// Let the selected GC initialize anything it needs.
138+
import core.internal.gc.proxy : gc_getProxy;
139+
gc_getProxy().initThread(this);
136140
}
137141

138142
package void initDataStorage() nothrow
@@ -146,6 +150,10 @@ class ThreadBase
146150

147151
package void destroyDataStorage() nothrow @nogc
148152
{
153+
// allow the GC to clean up any resources it allocated for this thread.
154+
import core.internal.gc.proxy : gc_getProxy;
155+
gc_getProxy().cleanupThread(this);
156+
149157
rt_tlsgc_destroy(m_tlsrtdata);
150158
m_tlsrtdata = null;
151159
}

druntime/test/init_fini/src/custom_gc.d

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import core.stdc.stdlib : calloc, malloc, realloc;
44

55
static import core.memory;
66

7+
import core.thread.threadbase : ThreadBase;
8+
79
extern (C) __gshared string[] rt_options = ["gcopt=gc:malloc"];
810

911
extern (C) pragma(crt_constructor) void register_mygc()
@@ -209,6 +211,14 @@ nothrow @nogc:
209211
return false;
210212
}
211213

214+
void initThread(ThreadBase thread) nothrow @nogc
215+
{
216+
}
217+
218+
void cleanupThread(ThreadBase thread) nothrow @nogc
219+
{
220+
}
221+
212222
private:
213223
// doesn't care for alignment
214224
static void* sentinelAdd(void* p, size_t value)

0 commit comments

Comments
 (0)