Browse Source

Basic 'handle' implementation and tests.

This abstraction covers the case where one part of the program needs
to refer to another object that is allowed to disappear.
Nick Mathewson 8 years ago
parent
commit
e015f7c9cc
6 changed files with 253 additions and 1 deletions
  1. 153 0
      src/common/handles.h
  2. 1 0
      src/common/include.am
  3. 1 0
      src/test/include.am
  4. 2 0
      src/test/test.c
  5. 1 1
      src/test/test.h
  6. 95 0
      src/test/test_handles.c

+ 153 - 0
src/common/handles.h

@@ -0,0 +1,153 @@
+/* Copyright (c) 2016, 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 "tor_queue.h"
+#include "util.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 /* TOR_HANDLE_H */
+

+ 1 - 0
src/common/include.am

@@ -129,6 +129,7 @@ COMMONHEADERS = \
   src/common/crypto_pwbox.h			\
   src/common/crypto_s2k.h			\
   src/common/di_ops.h				\
+  src/common/handles.h				\
   src/common/memarea.h				\
   src/common/linux_syscalls.inc			\
   src/common/procmon.h				\

+ 1 - 0
src/test/include.am

@@ -86,6 +86,7 @@ src_test_test_SOURCES = \
 	src/test/test_guardfraction.c \
 	src/test/test_extorport.c \
 	src/test/test_hs.c \
+	src/test/test_handles.c \
 	src/test/test_introduce.c \
 	src/test/test_keypin.c \
 	src/test/test_link_handshake.c \

+ 2 - 0
src/test/test.c

@@ -1177,6 +1177,7 @@ extern struct testcase_t util_tests[];
 extern struct testcase_t util_format_tests[];
 extern struct testcase_t util_process_tests[];
 extern struct testcase_t dns_tests[];
+extern struct testcase_t handle_tests[];
 
 struct testgroup_t testgroups[] = {
   { "", test_array },
@@ -1231,6 +1232,7 @@ struct testgroup_t testgroups[] = {
   { "util/logging/", logging_tests },
   { "util/process/", util_process_tests },
   { "util/thread/", thread_tests },
+  { "util/handle/", handle_tests },
   { "dns/", dns_tests },
   END_OF_GROUPS
 };

+ 1 - 1
src/test/test.h

@@ -73,7 +73,7 @@
     {print_ = (I64_PRINTF_TYPE) value_;}, {}, TT_EXIT_TEST_FUNCTION)
 
 const char *get_fname(const char *name);
-crypto_pk_t *pk_generate(int idx);
+struct crypto_pk_t *pk_generate(int idx);
 
 #define US2_CONCAT_2__(a, b) a ## __ ## b
 #define US_CONCAT_2__(a, b) a ## _ ## b

+ 95 - 0
src/test/test_handles.c

@@ -0,0 +1,95 @@
+/* Copyright (c) 2016, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+#include "test.h"
+
+#include "util.h"
+#include "handles.h"
+
+typedef struct demo_t {
+  HANDLE_ENTRY(demo, demo_t);
+  int val;
+} demo_t;
+
+HANDLE_DECL(demo, demo_t, static);
+HANDLE_IMPL(demo, demo_t, static);
+
+static demo_t *
+demo_new(int val)
+{
+  demo_t *d = tor_malloc_zero(sizeof(demo_t));
+  d->val = val;
+  return d;
+}
+
+static void
+demo_free(demo_t *d)
+{
+  if (d == NULL)
+    return;
+  demo_handles_clear(d);
+  tor_free(d);
+}
+
+static void
+test_handle_basic(void *arg)
+{
+  (void) arg;
+  demo_t *d1 = NULL, *d2 = NULL;
+  demo_handle_t *wr1 = NULL, *wr2 = NULL, *wr3 = NULL, *wr4 = NULL;
+
+  d1 = demo_new(9000);
+  d2 = demo_new(9009);
+
+  wr1 = demo_handle_new(d1);
+  wr2 = demo_handle_new(d1);
+  wr3 = demo_handle_new(d1);
+  wr4 = demo_handle_new(d2);
+
+  tt_assert(wr1);
+  tt_assert(wr2);
+  tt_assert(wr3);
+  tt_assert(wr4);
+
+  tt_ptr_op(demo_handle_get(wr1), OP_EQ, d1);
+  tt_ptr_op(demo_handle_get(wr2), OP_EQ, d1);
+  tt_ptr_op(demo_handle_get(wr3), OP_EQ, d1);
+  tt_ptr_op(demo_handle_get(wr4), OP_EQ, d2);
+
+  demo_handle_free(wr1);
+  wr1 = NULL;
+  tt_ptr_op(demo_handle_get(wr2), OP_EQ, d1);
+  tt_ptr_op(demo_handle_get(wr3), OP_EQ, d1);
+  tt_ptr_op(demo_handle_get(wr4), OP_EQ, d2);
+
+  demo_free(d1);
+  d1 = NULL;
+  tt_ptr_op(demo_handle_get(wr2), OP_EQ, NULL);
+  tt_ptr_op(demo_handle_get(wr3), OP_EQ, NULL);
+  tt_ptr_op(demo_handle_get(wr4), OP_EQ, d2);
+
+  demo_handle_free(wr2);
+  wr2 = NULL;
+  tt_ptr_op(demo_handle_get(wr3), OP_EQ, NULL);
+  tt_ptr_op(demo_handle_get(wr4), OP_EQ, d2);
+
+  demo_handle_free(wr3);
+  wr3 = NULL;
+ done:
+  demo_handle_free(wr1);
+  demo_handle_free(wr2);
+  demo_handle_free(wr3);
+  demo_handle_free(wr4);
+  demo_free(d1);
+  demo_free(d2);
+}
+
+#define HANDLE_TEST(name, flags)                       \
+  { #name, test_handle_ ##name, (flags), NULL, NULL }
+
+struct testcase_t handle_tests[] = {
+  HANDLE_TEST(basic, 0),
+  END_OF_TESTCASES
+};
+