1 /* Copyright (C) 2012, 2013, 2014, 2019 Free Software Foundation, Inc.
2 *
3 * This library is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public License
5 * as published by the Free Software Foundation; either version 3 of
6 * the License, or (at your option) any later version.
7 *
8 * This library is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16 * 02110-1301 USA
17 */
18
19
20
21
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25
26 #include <unistd.h>
27 #include <fcntl.h>
28
29 #include <full-write.h>
30
31 #include "libguile/bdw-gc.h"
32 #include "libguile/_scm.h"
33 #include "libguile/finalizers.h"
34 #include "libguile/gc.h"
35 #include "libguile/threads.h"
36
37
38
39 static int automatic_finalization_p = 1;
40
41 static size_t finalization_count;
42
43 static SCM run_finalizers_subr;
44
45
46
47
48 void
scm_i_set_finalizer(void * obj,scm_t_finalizer_proc proc,void * data)49 scm_i_set_finalizer (void *obj, scm_t_finalizer_proc proc, void *data)
50 {
51 GC_finalization_proc prev;
52 void *prev_data;
53 GC_REGISTER_FINALIZER_NO_ORDER (obj, proc, data, &prev, &prev_data);
54 }
55
56 struct scm_t_chained_finalizer
57 {
58 int resuscitating_p;
59 scm_t_finalizer_proc proc;
60 void *data;
61 scm_t_finalizer_proc prev;
62 void *prev_data;
63 };
64
65 static void
chained_finalizer(void * obj,void * data)66 chained_finalizer (void *obj, void *data)
67 {
68 struct scm_t_chained_finalizer *chained_data = data;
69 if (chained_data->resuscitating_p)
70 {
71 if (chained_data->prev)
72 scm_i_set_finalizer (obj, chained_data->prev, chained_data->prev_data);
73 chained_data->proc (obj, chained_data->data);
74 }
75 else
76 {
77 chained_data->proc (obj, chained_data->data);
78 if (chained_data->prev)
79 chained_data->prev (obj, chained_data->prev_data);
80 }
81 }
82
83 void
scm_i_add_resuscitator(void * obj,scm_t_finalizer_proc proc,void * data)84 scm_i_add_resuscitator (void *obj, scm_t_finalizer_proc proc, void *data)
85 {
86 struct scm_t_chained_finalizer *chained_data;
87 chained_data = scm_gc_malloc (sizeof (*chained_data), "chained finalizer");
88 chained_data->resuscitating_p = 1;
89 chained_data->proc = proc;
90 chained_data->data = data;
91 GC_REGISTER_FINALIZER_NO_ORDER (obj, chained_finalizer, chained_data,
92 &chained_data->prev,
93 &chained_data->prev_data);
94 }
95
96 static void
shuffle_resuscitators_to_front(struct scm_t_chained_finalizer * cd)97 shuffle_resuscitators_to_front (struct scm_t_chained_finalizer *cd)
98 {
99 while (cd->prev == chained_finalizer)
100 {
101 struct scm_t_chained_finalizer *prev = cd->prev_data;
102 scm_t_finalizer_proc proc = cd->proc;
103 void *data = cd->data;
104
105 if (!prev->resuscitating_p)
106 break;
107
108 cd->resuscitating_p = 1;
109 cd->proc = prev->proc;
110 cd->data = prev->data;
111
112 prev->resuscitating_p = 0;
113 prev->proc = proc;
114 prev->data = data;
115
116 cd = prev;
117 }
118 }
119
120 void
scm_i_add_finalizer(void * obj,scm_t_finalizer_proc proc,void * data)121 scm_i_add_finalizer (void *obj, scm_t_finalizer_proc proc, void *data)
122 {
123 struct scm_t_chained_finalizer *chained_data;
124 chained_data = scm_gc_malloc (sizeof (*chained_data), "chained finalizer");
125 chained_data->resuscitating_p = 0;
126 chained_data->proc = proc;
127 chained_data->data = data;
128 GC_REGISTER_FINALIZER_NO_ORDER (obj, chained_finalizer, chained_data,
129 &chained_data->prev,
130 &chained_data->prev_data);
131 shuffle_resuscitators_to_front (chained_data);
132 }
133
134
135
136
137 static SCM
run_finalizers_async_thunk(void)138 run_finalizers_async_thunk (void)
139 {
140 scm_run_finalizers ();
141 return SCM_UNSPECIFIED;
142 }
143
144
145 /* The function queue_finalizer_async is run by the GC when there are
146 * objects to finalize. It will enqueue an asynchronous call to
147 * GC_invoke_finalizers() at the next SCM_TICK in this thread.
148 */
149 static void
queue_finalizer_async(void)150 queue_finalizer_async (void)
151 {
152 scm_i_thread *t = SCM_I_CURRENT_THREAD;
153
154 /* Could be that the current thread is is NULL when we're allocating
155 in threads.c:guilify_self_1. In that case, rely on the
156 GC_invoke_finalizers call there after the thread spins up. */
157 if (!t) return;
158
159 scm_system_async_mark_for_thread (run_finalizers_subr, t->handle);
160 }
161
162
163
164
165 #if SCM_USE_PTHREAD_THREADS
166
167 static int finalization_pipe[2];
168 static scm_i_pthread_mutex_t finalization_thread_lock =
169 SCM_I_PTHREAD_MUTEX_INITIALIZER;
170 static pthread_t finalization_thread;
171 static int finalization_thread_is_running = 0;
172
173 static void
notify_finalizers_to_run(void)174 notify_finalizers_to_run (void)
175 {
176 char byte = 0;
177 full_write (finalization_pipe[1], &byte, 1);
178 }
179
180 static void
notify_about_to_fork(void)181 notify_about_to_fork (void)
182 {
183 char byte = 1;
184 full_write (finalization_pipe[1], &byte, 1);
185 }
186
187 struct finalization_pipe_data
188 {
189 char byte;
190 ssize_t n;
191 int err;
192 };
193
194 static void*
read_finalization_pipe_data(void * data)195 read_finalization_pipe_data (void *data)
196 {
197 struct finalization_pipe_data *fdata = data;
198
199 fdata->n = read (finalization_pipe[0], &fdata->byte, 1);
200 fdata->err = errno;
201
202 return NULL;
203 }
204
205 static void*
finalization_thread_proc(void * unused)206 finalization_thread_proc (void *unused)
207 {
208 while (1)
209 {
210 struct finalization_pipe_data data;
211
212 scm_without_guile (read_finalization_pipe_data, &data);
213
214 if (data.n <= 0)
215 {
216 if (data.err != EINTR)
217 {
218 perror ("error in finalization thread");
219 return NULL;
220 }
221 }
222 else
223 {
224 switch (data.byte)
225 {
226 case 0:
227 scm_run_finalizers ();
228 break;
229 case 1:
230 return NULL;
231 default:
232 abort ();
233 }
234 }
235 }
236 }
237
238 static void*
run_finalization_thread(void * arg)239 run_finalization_thread (void *arg)
240 {
241 return scm_with_guile (finalization_thread_proc, arg);
242 }
243
244 static void
start_finalization_thread(void)245 start_finalization_thread (void)
246 {
247 scm_i_pthread_mutex_lock (&finalization_thread_lock);
248 if (!finalization_thread_is_running)
249 {
250 /* Use the raw pthread API and scm_with_guile, because we don't want
251 to block on any lock that scm_spawn_thread might want to take,
252 and we don't want to inherit the dynamic state (fluids) of the
253 caller. */
254 if (pthread_create (&finalization_thread, NULL,
255 run_finalization_thread, NULL))
256 perror ("error creating finalization thread");
257 else
258 finalization_thread_is_running = 1;
259 }
260 scm_i_pthread_mutex_unlock (&finalization_thread_lock);
261 }
262
263 static void
stop_finalization_thread(void)264 stop_finalization_thread (void)
265 {
266 scm_i_pthread_mutex_lock (&finalization_thread_lock);
267 if (finalization_thread_is_running)
268 {
269 notify_about_to_fork ();
270 if (pthread_join (finalization_thread, NULL))
271 perror ("joining finalization thread");
272 finalization_thread_is_running = 0;
273 }
274 scm_i_pthread_mutex_unlock (&finalization_thread_lock);
275 }
276
277 static void
spawn_finalizer_thread(void)278 spawn_finalizer_thread (void)
279 {
280 GC_set_finalizer_notifier (notify_finalizers_to_run);
281 start_finalization_thread ();
282 }
283
284 #endif /* SCM_USE_PTHREAD_THREADS */
285
286
287
288
289 void
scm_i_finalizer_pre_fork(void)290 scm_i_finalizer_pre_fork (void)
291 {
292 #if SCM_USE_PTHREAD_THREADS
293 if (automatic_finalization_p)
294 {
295 stop_finalization_thread ();
296 GC_set_finalizer_notifier (spawn_finalizer_thread);
297 }
298 #endif
299 }
300
301
302
303
304 static void
async_gc_finalizer(void * ptr,void * data)305 async_gc_finalizer (void *ptr, void *data)
306 {
307 void **obj = ptr;
308 void (*callback) (void) = obj[0];
309
310 callback ();
311
312 scm_i_set_finalizer (ptr, async_gc_finalizer, data);
313 }
314
315 /* Arrange to call CALLBACK asynchronously after each GC. The callback
316 will be invoked from a finalizer, which may be from an async or from
317 another thread.
318
319 As an implementation detail, the way this works is that we allocate a
320 fresh object and put the callback in the object. We know that this
321 object should get collected the next time GC is run, so we attach a
322 finalizer to it to trigger the callback.
323
324 Once the callback runs, we re-attach a finalizer to that fresh object
325 to prepare for the next GC, and the process repeats indefinitely.
326
327 We could use the scm_after_gc_hook, but using a finalizer has the
328 advantage of potentially running in another thread, decreasing pause
329 time.
330
331 Note that libgc currently has a heuristic that adding 500 finalizable
332 objects will cause GC to collect rather than expand the heap,
333 drastically reducing performance on workloads that actually need to
334 expand the heap. Therefore scm_i_register_async_gc_callback is
335 inappropriate for using on unbounded numbers of callbacks. */
336 void
scm_i_register_async_gc_callback(void (* callback)(void))337 scm_i_register_async_gc_callback (void (*callback) (void))
338 {
339 void **obj = GC_MALLOC_ATOMIC (sizeof (void*));
340
341 obj[0] = (void*)callback;
342
343 scm_i_set_finalizer (obj, async_gc_finalizer, NULL);
344 }
345
346
347 int
scm_set_automatic_finalization_enabled(int enabled_p)348 scm_set_automatic_finalization_enabled (int enabled_p)
349 {
350 int was_enabled_p = automatic_finalization_p;
351
352 if (enabled_p == was_enabled_p)
353 return was_enabled_p;
354
355 if (!scm_initialized_p)
356 {
357 automatic_finalization_p = enabled_p;
358 return was_enabled_p;
359 }
360
361 if (enabled_p)
362 {
363 #if SCM_USE_PTHREAD_THREADS
364 if (pipe2 (finalization_pipe, O_CLOEXEC) != 0)
365 scm_syserror (NULL);
366 GC_set_finalizer_notifier (spawn_finalizer_thread);
367 #else
368 GC_set_finalizer_notifier (queue_finalizer_async);
369 #endif
370 }
371 else
372 {
373 GC_set_finalizer_notifier (0);
374
375 #if SCM_USE_PTHREAD_THREADS
376 stop_finalization_thread ();
377 close (finalization_pipe[0]);
378 close (finalization_pipe[1]);
379 finalization_pipe[0] = -1;
380 finalization_pipe[1] = -1;
381 #endif
382 }
383
384 automatic_finalization_p = enabled_p;
385
386 return was_enabled_p;
387 }
388
389 int
scm_run_finalizers(void)390 scm_run_finalizers (void)
391 {
392 int finalized = GC_invoke_finalizers ();
393
394 finalization_count += finalized;
395
396 return finalized;
397 }
398
399
400
401
402 void
scm_init_finalizers(void)403 scm_init_finalizers (void)
404 {
405 /* When the async is to run, the cdr of the pair gets set to the
406 asyncs queue of the current thread. */
407 run_finalizers_subr = scm_c_make_gsubr ("%run-finalizers", 0, 0, 0,
408 run_finalizers_async_thunk);
409
410 if (automatic_finalization_p)
411 GC_set_finalizer_notifier (queue_finalizer_async);
412 }
413
414 void
scm_init_finalizer_thread(void)415 scm_init_finalizer_thread (void)
416 {
417 #if SCM_USE_PTHREAD_THREADS
418 if (automatic_finalization_p)
419 {
420 if (pipe2 (finalization_pipe, O_CLOEXEC) != 0)
421 scm_syserror (NULL);
422 GC_set_finalizer_notifier (spawn_finalizer_thread);
423 }
424 #endif
425 }
426