--- /dev/null
+/*
+ * BIRD Library -- Garbage Collector
+ *
+ * (c) 2020 Maria Matejka <mq@jmq.cz>
+ * (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 <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+
+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; i<gc_callbacks_cnt; i++)
+ if (gc_callbacks[i])
+ CALL(gc_callbacks[i]->enter, 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; i<gc_callbacks_cnt; i++)
+ if (gc_callbacks[i])
+ CALL(gc_callbacks[i]->exit, 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; i<gc_callbacks_cnt; i++)
+ if (gc_callbacks[i])
+ CALL(gc_callbacks[i]->cleanup, 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<gc_callbacks_cnt; i++)
+ if (gc_callbacks[i] == gcs)
+ {
+ gc_callbacks[i] = NULL;
+ break;
+ }
+
+ pthread_mutex_unlock(&gc_mutex);
+}
--- /dev/null
+/*
+ * BIRD Library -- Garbage Collector for Threads
+ *
+ * (c) 2020 Maria Matejka <mq@jmq.cz>
+ * (c) 2020 CZ.NIC z.s.p.o.
+ *
+ * Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_GC_H_
+#define _BIRD_GC_H_
+
+#include "lib/birdlib.h"
+
+/* Call gc_enter() before any code where a shared structure
+ * may be accessed by this thread. Mostly this should be called
+ * immediately after a poll() returns. */
+void gc_enter(void);
+
+/* Call gc_exit() when no shared structure is being held
+ * by the current thread. Mostly this means calling this
+ * before calling poll() which may wait for a long time. */
+void gc_exit(void);
+
+/* Clean up all the data which has been freed in the oldest gc round that has been already exited.
+ * Returns 1 on success (there was something to clean), 0 when there is no gc round available to cleanup.
+ * It is recommended to run this outside any gc round. */
+_Bool gc_cleanup(void);
+
+/* GC callback type */
+struct gc_callback_set {
+ void (*enter)(u64, struct gc_callback_set *);
+ void (*exit)(u64, struct gc_callback_set *);
+ void (*cleanup)(u64, struct gc_callback_set *);
+};
+
+/* Register callbacks. These are called for each entered, exited and cleaned-up round.
+ * The caller must keep the structure. */
+void gc_register(struct gc_callback_set *);
+void gc_unregister(struct gc_callback_set *);
+
+#endif