/* * BIRD Library -- Garbage Collector * * (c) 2020 Maria Matejka * (c) 2020 CZ.NIC z.s.p.o. * * Can be freely distributed and used under the terms of the GNU GPL. */ #include "lib/gc.h" #include "lib/resource.h" #include #include #include static pthread_mutex_t gc_mutex = PTHREAD_MUTEX_INITIALIZER; static u64 gc_last_round = 0, gc_oldest_round = 1, gc_oldest_running_round = 1; _Thread_local u64 gc_current_round = 0; #define DEFAULT_CONCURRENT_GC_ROUNDS 32 static u64 *gc_end = NULL; static u64 gc_end_size = 0; static u64 gc_offset = 0; #define RTOI(round) ((round) - gc_offset) #define ITOR(index) ((index) + gc_offset) #define DEFAULT_GC_CALLBACKS 4 struct gc_callback_set **gc_callbacks = NULL; static uint gc_callbacks_cnt = 0, gc_callbacks_size = 0; #define GDBG(what) debug(what " last:%d oldest:%d oldest_running:%d current:%d\n", \ gc_last_round, gc_oldest_round, gc_oldest_running_round, gc_current_round) void gc_enter(void) { /* Everything is done locked. */ pthread_mutex_lock(&gc_mutex); GDBG("gc_enter in"); ASSERT(gc_current_round == 0); /* No round keeper? Just create some. */ if (!gc_end) { gc_end = xmalloc(sizeof(u64) * DEFAULT_CONCURRENT_GC_ROUNDS); gc_end_size = DEFAULT_CONCURRENT_GC_ROUNDS; } /* Update current round ID */ gc_current_round = ++gc_last_round; /* We're at the end of the array */ if (RTOI(gc_current_round) >= gc_end_size) { /* How much space is in the beginning? */ u64 skip = RTOI(gc_oldest_round); if (skip >= gc_end_size/2) { /* Enough. Move to the beginning. */ memcpy(gc_end, gc_end + skip, (gc_current_round - gc_oldest_round) * sizeof(u64)); gc_offset += skip; } else /* Not enough. Realloc. */ gc_end = xrealloc(gc_end, (gc_end_size *= 2) * sizeof(u64)); } u64 index = RTOI(gc_current_round); gc_end[index] = 0; /* Run hooks */ for (uint i=0; ienter, gc_current_round, gc_callbacks[i]); GDBG("gc_enter out"); /* We're done now. Unlock. */ pthread_mutex_unlock(&gc_mutex); } void gc_exit(void) { /* Everything is done locked. */ pthread_mutex_lock(&gc_mutex); GDBG("gc_exit in"); ASSERT(gc_current_round <= gc_last_round); ASSERT(gc_current_round >= gc_oldest_round); u64 index = RTOI(gc_current_round); gc_end[index] = gc_last_round; /* Run hooks */ for (uint i=0; iexit, gc_current_round, gc_callbacks[i]); gc_current_round = 0; GDBG("gc_exit out"); /* Done. Unlock. */ pthread_mutex_unlock(&gc_mutex); /* Do some cleanup */ uint max = 4; while (max-- && gc_cleanup()) ; } _Bool gc_cleanup(void) { pthread_mutex_lock(&gc_mutex); GDBG("gc_cleanup in"); /* There is nothing to clean up */ if (gc_last_round < gc_oldest_round) goto fail; /* The oldest round is still running */ u64 oldest_round_end = gc_end[RTOI(gc_oldest_round)]; if (oldest_round_end == 0) goto fail; if (gc_oldest_running_round < gc_oldest_round) gc_oldest_running_round = gc_oldest_round + 1; /* Some overlapping round is still running */ while (gc_oldest_running_round <= oldest_round_end) if (gc_end[RTOI(gc_oldest_running_round++)] == 0) goto fail; /* Run hooks */ for (uint i=0; icleanup, gc_oldest_round, gc_callbacks[i]); gc_oldest_round++; GDBG("gc_exit out cleaned up"); pthread_mutex_unlock(&gc_mutex); return 1; fail: GDBG("gc_exit out nothing to clean"); pthread_mutex_unlock(&gc_mutex); return 0; } void gc_register(struct gc_callback_set *gcs) { pthread_mutex_lock(&gc_mutex); if (!gc_callbacks) gc_callbacks = xmalloc(sizeof(struct gc_callback_set *) * (gc_callbacks_size = DEFAULT_GC_CALLBACKS)); if (gc_callbacks_cnt == gc_callbacks_size) gc_callbacks = xrealloc(gc_callbacks, sizeof(struct gc_callback_set *) * (gc_callbacks_size *= 2)); gc_callbacks[gc_callbacks_cnt++] = gcs; pthread_mutex_unlock(&gc_mutex); } void gc_unregister(struct gc_callback_set *gcs) { pthread_mutex_lock(&gc_mutex); for (uint i=0; i