forked from JuliaLang/julia
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsafepoint.c
225 lines (211 loc) · 6.86 KB
/
safepoint.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// This file is a part of Julia. License is MIT: https://julialang.org/license
#include "julia.h"
#include "julia_internal.h"
#include "threading.h"
#ifndef _OS_WINDOWS_
#include <sys/mman.h>
#if defined(_OS_DARWIN_) && !defined(MAP_ANONYMOUS)
#define MAP_ANONYMOUS MAP_ANON
#endif
#endif
#include "julia_assert.h"
#ifdef __cplusplus
extern "C" {
#endif
// 0: no sigint is pending
// 1: at least one sigint is pending, only the sigint page is enabled.
// 2: at least one sigint is pending, both safepoint pages are enabled.
JL_DLLEXPORT sig_atomic_t jl_signal_pending = 0;
volatile uint32_t jl_gc_running = 0;
char *jl_safepoint_pages = NULL;
// The number of safepoints enabled on the three pages.
// The first page, is the SIGINT page, only used by the master thread.
// The second page, is the GC page for the master thread, this is where
// the `safepoint` tls pointer points to for the master thread.
// The third page is the GC page for the other threads. The thread's
// `safepoint` tls pointer points the beginning of this page + `sizeof(size_t)`
// so that both safepoint load and pending signal load falls in this page.
// The initialization of the `safepoint` pointer is done `ti_initthread`
// in `threading.c`.
uint8_t jl_safepoint_enable_cnt[3] = {0, 0, 0};
// This lock should be acquired before enabling/disabling the safepoint
// or accessing one of the following variables:
//
// * jl_gc_running
// * jl_signal_pending
// * jl_safepoint_enable_cnt
//
// Additionally accessing `jl_gc_running` should use acquire/release
// load/store so that threads waiting for the GC doesn't have to also
// fight on the safepoint lock...
//
// Acquiring and releasing this lock should use the `jl_mutex_*_nogc` functions
jl_mutex_t safepoint_lock;
static void jl_safepoint_enable(int idx)
{
// safepoint_lock should be held
assert(0 <= idx && idx < 3);
if (jl_safepoint_enable_cnt[idx]++ != 0) {
// We expect this to be enabled at most twice
// one for the GC, one for SIGINT.
// Update this if this is not the case anymore in the future.
assert(jl_safepoint_enable_cnt[idx] <= 2);
return;
}
// Now that we are requested to mprotect the page and it wasn't already.
char *pageaddr = jl_safepoint_pages + jl_page_size * idx;
#ifdef _OS_WINDOWS_
DWORD old_prot;
VirtualProtect(pageaddr, jl_page_size, PAGE_NOACCESS, &old_prot);
#else
mprotect(pageaddr, jl_page_size, PROT_NONE);
#endif
}
static void jl_safepoint_disable(int idx)
{
// safepoint_lock should be held
assert(0 <= idx && idx < 3);
if (--jl_safepoint_enable_cnt[idx] != 0) {
assert(jl_safepoint_enable_cnt[idx] > 0);
return;
}
// Now that we are requested to un-mprotect the page and no one else
// want it to be kept protected.
char *pageaddr = jl_safepoint_pages + jl_page_size * idx;
#ifdef _OS_WINDOWS_
DWORD old_prot;
VirtualProtect(pageaddr, jl_page_size, PAGE_READONLY, &old_prot);
#else
mprotect(pageaddr, jl_page_size, PROT_READ);
#endif
}
void jl_safepoint_init(void)
{
// jl_page_size isn't available yet.
size_t pgsz = jl_getpagesize();
#ifdef _OS_WINDOWS_
char *addr = (char*)VirtualAlloc(NULL, pgsz * 3, MEM_COMMIT, PAGE_READONLY);
#else
char *addr = (char*)mmap(0, pgsz * 3, PROT_READ,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED)
addr = NULL;
#endif
if (addr == NULL) {
jl_printf(JL_STDERR, "could not allocate GC synchronization page\n");
gc_debug_critical_error();
abort();
}
// The signal page is for the gc safepoint.
// The page before it is the sigint pending flag.
jl_safepoint_pages = addr;
}
int jl_safepoint_start_gc(void)
{
if (jl_n_threads == 1) {
jl_gc_running = 1;
return 1;
}
// The thread should have set this already
assert(jl_get_ptls_states()->gc_state == JL_GC_STATE_WAITING);
jl_mutex_lock_nogc(&safepoint_lock);
// In case multiple threads enter the GC at the same time, only allow
// one of them to actually run the collection. We can't just let the
// master thread do the GC since it might be running unmanaged code
// and can take arbitrarily long time before hitting a safe point.
if (jl_atomic_compare_exchange(&jl_gc_running, 0, 1) != 0) {
jl_mutex_unlock_nogc(&safepoint_lock);
jl_safepoint_wait_gc();
return 0;
}
jl_safepoint_enable(1);
jl_safepoint_enable(2);
jl_mutex_unlock_nogc(&safepoint_lock);
return 1;
}
void jl_safepoint_end_gc(void)
{
assert(jl_gc_running);
if (jl_n_threads == 1) {
jl_gc_running = 0;
return;
}
jl_mutex_lock_nogc(&safepoint_lock);
// Need to reset the page protection before resetting the flag since
// the thread will trigger a segfault immediately after returning from
// the signal handler.
jl_safepoint_disable(2);
jl_safepoint_disable(1);
jl_atomic_store_release(&jl_gc_running, 0);
# ifdef __APPLE__
// This wakes up other threads on mac.
jl_mach_gc_end();
# endif
jl_mutex_unlock_nogc(&safepoint_lock);
}
void jl_safepoint_wait_gc(void)
{
// The thread should have set this is already
assert(jl_get_ptls_states()->gc_state != 0);
// Use normal volatile load in the loop.
// Use a acquire load to make sure the GC result is visible on this thread.
while (jl_gc_running || jl_atomic_load_acquire(&jl_gc_running)) {
jl_cpu_pause(); // yield?
}
}
void jl_safepoint_enable_sigint(void)
{
jl_mutex_lock_nogc(&safepoint_lock);
// Make sure both safepoints are enabled exactly once for SIGINT.
switch (jl_signal_pending) {
default:
assert(0 && "Shouldn't happen.");
case 0:
// Enable SIGINT page
jl_safepoint_enable(0);
// fall through
case 1:
// SIGINT page is enabled, enable GC page
jl_safepoint_enable(1);
// fall through
case 2:
jl_signal_pending = 2;
}
jl_mutex_unlock_nogc(&safepoint_lock);
}
void jl_safepoint_defer_sigint(void)
{
jl_mutex_lock_nogc(&safepoint_lock);
// Make sure the GC safepoint is disabled for SIGINT.
if (jl_signal_pending == 2) {
jl_safepoint_disable(1);
jl_signal_pending = 1;
}
jl_mutex_unlock_nogc(&safepoint_lock);
}
int jl_safepoint_consume_sigint(void)
{
int has_signal = 0;
jl_mutex_lock_nogc(&safepoint_lock);
// Make sure both safepoints are disabled for SIGINT.
switch (jl_signal_pending) {
default:
assert(0 && "Shouldn't happen.");
case 2:
// Disable gc page
jl_safepoint_disable(1);
// fall through
case 1:
// GC page is disabled, disable SIGINT page
jl_safepoint_disable(0);
has_signal = 1;
// fall through
case 0:
jl_signal_pending = 0;
}
jl_mutex_unlock_nogc(&safepoint_lock);
return has_signal;
}
#ifdef __cplusplus
}
#endif