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