[tor-commits] [tor/master] Basic 'handle' implementation and tests.

nickm at torproject.org nickm at torproject.org
Tue Apr 19 18:08:19 UTC 2016


commit e015f7c9ccbbb6d857e23ff419528729e44fff93
Author: Nick Mathewson <nickm at torproject.org>
Date:   Thu Feb 11 18:34:29 2016 -0500

    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.
---
 src/common/handles.h    | 153 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/common/include.am   |   1 +
 src/test/include.am     |   1 +
 src/test/test.c         |   2 +
 src/test/test.h         |   2 +-
 src/test/test_handles.c |  95 ++++++++++++++++++++++++++++++
 6 files changed, 253 insertions(+), 1 deletion(-)

diff --git a/src/common/handles.h b/src/common/handles.h
new file mode 100644
index 0000000..1ee2322
--- /dev/null
+++ b/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 */
+
diff --git a/src/common/include.am b/src/common/include.am
index 5afb30d..04994c7 100644
--- a/src/common/include.am
+++ b/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				\
diff --git a/src/test/include.am b/src/test/include.am
index 7d80fdf..a3543dd 100644
--- a/src/test/include.am
+++ b/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 \
diff --git a/src/test/test.c b/src/test/test.c
index ed167a3..3519dca 100644
--- a/src/test/test.c
+++ b/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
 };
diff --git a/src/test/test.h b/src/test/test.h
index e618ce1..153b7ca 100644
--- a/src/test/test.h
+++ b/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
diff --git a/src/test/test_handles.c b/src/test/test_handles.c
new file mode 100644
index 0000000..8aaae13
--- /dev/null
+++ b/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
+};
+





More information about the tor-commits mailing list