aboutsummaryrefslogtreecommitdiff
path: root/src/lib/container/handles.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/container/handles.h')
-rw-r--r--src/lib/container/handles.h153
1 files changed, 153 insertions, 0 deletions
diff --git a/src/lib/container/handles.h b/src/lib/container/handles.h
new file mode 100644
index 0000000000..ca7c94559e
--- /dev/null
+++ b/src/lib/container/handles.h
@@ -0,0 +1,153 @@
+/* Copyright (c) 2016-2019, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file handles.h
+ * \brief Macros for C weak-handle implementation.
+ *
+ * A 'handle' is a pointer to an object that is allowed to go away while
+ * the handle stays alive. When you dereference the handle, you might get
+ * the object, or you might get "NULL".
+ *
+ * Use this pattern when an object has a single obvious lifespan, so you don't
+ * want to use reference counting, but when other objects might need to refer
+ * to the first object without caring about its lifetime.
+ *
+ * To enable a type to have handles, add a HANDLE_ENTRY() field in its
+ * definition, as in:
+ *
+ * struct walrus {
+ * HANDLE_ENTRY(wlr, walrus);
+ * // ...
+ * };
+ *
+ * And invoke HANDLE_DECL(wlr, walrus, [static]) to declare the handle
+ * manipulation functions (typically in a header):
+ *
+ * // opaque handle to walrus.
+ * typedef struct wlr_handle_t wlr_handle_t;
+ *
+ * // make a new handle
+ * struct wlr_handle_t *wlr_handle_new(struct walrus *);
+ *
+ * // release a handle
+ * void wlr_handle_free(wlr_handle_t *);
+ *
+ * // return the pointed-to walrus, or NULL.
+ * struct walrus *wlr_handle_get(wlr_handle_t *).
+ *
+ * // call this function when you're about to free the walrus;
+ * // it invalidates all handles. (IF YOU DON'T, YOU WILL HAVE
+ * // DANGLING REFERENCES)
+ * void wlr_handles_clear(struct walrus *);
+ *
+ * Finally, use HANDLE_IMPL() to define the above functions in some
+ * appropriate C file: HANDLE_IMPL(wlr, walrus, [static])
+ *
+ **/
+
+#ifndef TOR_HANDLE_H
+#define TOR_HANDLE_H
+
+#include "orconfig.h"
+
+#include "lib/log/util_bug.h"
+#include "lib/malloc/malloc.h"
+
+#define HANDLE_ENTRY(name, structname) \
+ struct name ## _handle_head_t *handle_head
+
+#define HANDLE_DECL(name, structname, linkage) \
+ typedef struct name ## _handle_t name ## _handle_t; \
+ linkage name ## _handle_t *name ## _handle_new(struct structname *object); \
+ linkage void name ## _handle_free_(name ## _handle_t *); \
+ linkage struct structname *name ## _handle_get(name ## _handle_t *); \
+ linkage void name ## _handles_clear(struct structname *object);
+
+/*
+ * Implementation notes: there are lots of possible implementations here. We
+ * could keep a linked list of handles, each with a backpointer to the object,
+ * and set all of their backpointers to NULL when the object is freed. Or we
+ * could have the clear function invalidate the object, but not actually let
+ * the object get freed until the all the handles went away. We could even
+ * have a hash-table mapping unique identifiers to objects, and have each
+ * handle be a copy of the unique identifier. (We'll want to build that last
+ * one eventually if we want cross-process handles.)
+ *
+ * But instead we're opting for a single independent 'head' that knows how
+ * many handles there are, and where the object is (or isn't). This makes
+ * all of our functions O(1), and most as fast as a single pointer access.
+ *
+ * The handles themselves are opaque structures holding a pointer to the head.
+ * We could instead have each foo_handle_t* be identical to foo_handle_head_t
+ * *, and save some allocations ... but doing so would make handle leaks
+ * harder to debug. As it stands, every handle leak is a memory leak, and
+ * existing memory debugging tools should help with those. We can revisit
+ * this decision if handles are too slow.
+ */
+
+#define HANDLE_IMPL(name, structname, linkage) \
+ /* The 'head' object for a handle-accessible type. This object */ \
+ /* persists for as long as the object, or any handles, exist. */ \
+ typedef struct name ## _handle_head_t { \
+ struct structname *object; /* pointed-to object, or NULL */ \
+ unsigned int references; /* number of existing handles */ \
+ } name ## _handle_head_t; \
+ \
+ struct name ## _handle_t { \
+ struct name ## _handle_head_t *head; /* reference to the 'head'. */ \
+ }; \
+ \
+ linkage struct name ## _handle_t * \
+ name ## _handle_new(struct structname *object) \
+ { \
+ tor_assert(object); \
+ name ## _handle_head_t *head = object->handle_head; \
+ if (PREDICT_UNLIKELY(head == NULL)) { \
+ head = object->handle_head = tor_malloc_zero(sizeof(*head)); \
+ head->object = object; \
+ } \
+ name ## _handle_t *new_ref = tor_malloc_zero(sizeof(*new_ref)); \
+ new_ref->head = head; \
+ ++head->references; \
+ return new_ref; \
+ } \
+ \
+ linkage void \
+ name ## _handle_free_(struct name ## _handle_t *ref) \
+ { \
+ if (! ref) return; \
+ name ## _handle_head_t *head = ref->head; \
+ tor_assert(head); \
+ --head->references; \
+ tor_free(ref); \
+ if (head->object == NULL && head->references == 0) { \
+ tor_free(head); \
+ return; \
+ } \
+ } \
+ \
+ linkage struct structname * \
+ name ## _handle_get(struct name ## _handle_t *ref) \
+ { \
+ tor_assert(ref); \
+ name ## _handle_head_t *head = ref->head; \
+ tor_assert(head); \
+ return head->object; \
+ } \
+ \
+ linkage void \
+ name ## _handles_clear(struct structname *object) \
+ { \
+ tor_assert(object); \
+ name ## _handle_head_t *head = object->handle_head; \
+ if (! head) \
+ return; \
+ object->handle_head = NULL; \
+ head->object = NULL; \
+ if (head->references == 0) { \
+ tor_free(head); \
+ } \
+ }
+
+#endif /* !defined(TOR_HANDLE_H) */