Skip to content

Commit

Permalink
[GC] Harden the GC against fork.
Browse files Browse the repository at this point in the history
  • Loading branch information
deadalnix committed Oct 25, 2024
1 parent a081eae commit 92bc808
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 0 deletions.
3 changes: 3 additions & 0 deletions sdlib/core/stdc/pthread.d
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ int pthread_create(pthread_t* thread, const pthread_attr_t* attr,
int pthread_cancel(pthread_t thread);
int pthread_join(pthread_t th, void** thread_return);

int pthread_atfork(void function() prepare, void function() parent,
void function() child);

version(linux) {
int pthread_getattr_np(pthread_t __th, pthread_attr_t* __attr);
int pthread_attr_getstack(const pthread_attr_t* __attr, void** __stackaddr,
Expand Down
12 changes: 12 additions & 0 deletions sdlib/d/gc/collector.d
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ private:
}
}

void collectorPrepareForFork() {
gCollectorState.mutex.lock();
}

void collectorPostForkParent() {
gCollectorState.mutex.unlock();
}

void collectorPostForkChild() {
gCollectorState.mutex.__clear();
}

private:
struct CollectorState {
private:
Expand Down
41 changes: 41 additions & 0 deletions sdlib/d/gc/fork.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module d.gc.fork;

import core.stdc.pthread;

void setupFork() {
if (pthread_atfork(prepare, parent, child) != 0) {
import core.stdc.stdlib, core.stdc.stdio;
printf("pthread_atfork failed!");
exit(1);
}
}

void prepare() {
/**
* Before forking, we want to take all the locks.
* This ensures that no other thread holds on GC
* resources while forking, and would find itself
* unable to release them in the child.
* The order in which locks are taken is important
* as taking them in the wrong order will cause
* deadlocks.
*
* FIXME: At the moment, we only take the lock for
* the collection process. This ensures we
* can use the fork/exec pattern safely, but
* it will nto leave the GC in a usable state
* in the child.
*/
import d.gc.collector;
collectorPrepareForFork();
}

void parent() {
import d.gc.collector;
collectorPostForkParent();
}

void child() {
import d.gc.collector;
collectorPostForkChild();
}
3 changes: 3 additions & 0 deletions sdlib/d/gc/thread.d
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ void createProcess() {
enterBusyState();
scope(exit) exitBusyState();

import d.gc.fork;
setupFork();

import d.gc.signal;
setupSignals();

Expand Down
11 changes: 11 additions & 0 deletions sdlib/d/sync/mutex.d
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ public:
}
}

/**
* /!\: This will reset the state of the mutex.
* If it was locked, it is now unlocked.
* If there were thread witing for it, they are probably
* lost forever.
* This method is almost certainly not what you want to use.
*/
void __clear() shared {
word.store(0);
}

private:
enum Handoff {
None,
Expand Down

0 comments on commit 92bc808

Please sign in to comment.