Browse Source

talloc_link; a replacement for talloc's references.

Rusty Russell 16 years ago
parent
commit
4e5111fb40
4 changed files with 404 additions and 0 deletions
  1. 146 0
      ccan/talloc_link/_info
  2. 95 0
      ccan/talloc_link/talloc_link.c
  3. 47 0
      ccan/talloc_link/talloc_link.h
  4. 116 0
      ccan/talloc_link/test/run.c

+ 146 - 0
ccan/talloc_link/_info

@@ -0,0 +1,146 @@
+#include <stdio.h>
+#include <string.h>
+#include "config.h"
+
+/**
+ * talloc_link - link helper for talloc
+ *
+ * Talloc references can be confusing and buggy.  In the cases where an object
+ * needs multiple parents, all parents need to be aware of the situation; thus
+ * talloc_link is a helper where all "parents" talloc_link an object they
+ * agree to share ownership of.
+ *
+ * Example:
+ *	// Silly program which keeps a cache of uppercased strings.
+ *	// The cache wants to keep strings around even after they may have
+ *	// been "freed" by the caller.
+ *	#include <stdio.h>
+ *	#include <err.h>
+ *	#include <string.h>
+ *	#include <ctype.h>
+ *	#include <ccan/talloc/talloc.h>
+ *	#include <ccan/talloc_link/talloc_link.h>
+ *
+ *	struct upcache {
+ *		const char *str;
+ *		const char *upstr;
+ *	};
+ *
+ *	static struct upcache *cache;
+ *	static unsigned int cache_hits = 0;
+ *	#define CACHE_SIZE 4
+ *	void init_upcase(void)
+ *	{
+ *		cache = talloc_zero_array(NULL, struct upcache, CACHE_SIZE);
+ *	}
+ *
+ *	static struct upcache *lookup_upcase(const char *str)
+ *	{
+ *		unsigned int i;
+ *		for (i = 0; i < CACHE_SIZE; i++)
+ *			if (cache[i].str && !strcmp(cache[i].str, str)) {
+ *				cache_hits++;
+ *				return &cache[i];
+ *			}
+ *		return NULL;
+ *	}
+ *
+ *	static struct upcache *new_upcase(const char *str)
+ *	{
+ *		unsigned int i;
+ *		char *upstr;
+ *
+ *		upstr = talloc_linked(cache, talloc_strdup(NULL, str));
+ *		if (!upstr)
+ *			return NULL;
+ *
+ *		i = random() % CACHE_SIZE;
+ *
+ *		// Throw out old: works fine if cache[i].upstr is NULL.
+ *		talloc_delink(cache, cache[i].upstr);
+ *
+ *		// Replace with new.
+ *		cache[i].str = str;
+ *		cache[i].upstr = upstr;
+ *		while (*upstr) {
+ *			*upstr = toupper(*upstr);
+ *			upstr++;
+ *		}
+ *		return &cache[i];
+ *	}
+ *
+ *	// If you want to keep the result, talloc_link it.
+ *	const char *get_upcase(const char *str)
+ *	{
+ *		struct upcache *uc = lookup_upcase(str);
+ *		if (!uc)
+ *			uc = new_upcase(str);
+ *		if (!uc)
+ *			return NULL;
+ *		return uc->upstr;
+ *	}
+ *
+ *	void exit_upcase(void)
+ *	{
+ *		talloc_free(cache);
+ *		printf("Cache hits: %u\n", cache_hits);
+ *	}
+ *
+ *	int main(int argc, char *argv[])
+ *	{
+ *		unsigned int i;
+ *		const char **values;
+ *
+ *		// Will dump any memory leaks to stderr on exit.
+ *		talloc_enable_leak_report();
+ *
+ *		// Initialize cache.
+ *		init_upcase();
+ *
+ *		// Throw values in.
+ *		values = talloc_array(NULL, const char *, argc);
+ *		for (i = 1; i < argc; i++) {
+ *			values[i-1] = talloc_link(values, get_upcase(argv[i]));
+ *			if (!values[i-1])
+ *				err(1, "Out of memory");
+ *		}
+ *		// This will free all the values, but cache will still work.
+ *		talloc_free(values);
+ *
+ *		// Repeat!
+ *		values = talloc_array(NULL, const char *, argc);
+ *		for (i = 1; i < argc; i++) {
+ *			values[i-1] = talloc_link(values, get_upcase(argv[i]));
+ *			if (!values[i-1])
+ *				err(1, "Out of memory");
+ *		}
+ *
+ *		// This will remove cache links, but we still have a link.
+ *		exit_upcase();
+ *
+ *		// Show values, so we output something.
+ *		for (i = 0; i < argc - 1; i++)
+ *			printf("%s ", values[i]);
+ *		printf("\n");
+ *
+ *		// This will finally free the upcase strings (last link).
+ *		talloc_free(values);
+ *
+ *		return 0;
+ *	}
+ *
+ * Licence: GPL (2 or any later version)
+ */
+int main(int argc, char *argv[])
+{
+	if (argc != 2)
+		return 1;
+
+	if (strcmp(argv[1], "depends") == 0) {
+		printf("ccan/talloc\n");
+		printf("ccan/list\n");
+		return 0;
+	}
+
+	return 1;
+}

+ 95 - 0
ccan/talloc_link/talloc_link.c

@@ -0,0 +1,95 @@
+#include <ccan/list/list.h>
+#include <ccan/talloc/talloc.h>
+#include <ccan/talloc_link/talloc_link.h>
+#include <assert.h>
+
+/* Fake parent, if they care. */
+static void *talloc_links = NULL;
+
+/* This is the parent of the linked object, so we can implement delink. */
+struct talloc_linked {
+	struct list_head links;
+	const void *obj;
+};
+
+/* This is a child of the linker, but not a parent of ref. */
+struct talloc_link {
+	struct list_node list;
+	struct talloc_linked *linked;
+};
+
+static int destroy_link(struct talloc_link *link)
+{
+	list_del(&link->list);
+	if (list_empty(&link->linked->links))
+		talloc_free(link->linked);
+	return 0;
+}
+
+static bool add_link(const void *ctx, struct talloc_linked *linked)
+{
+	struct talloc_link *link = talloc(ctx, struct talloc_link);
+	if (!link)
+		return false;
+
+	link->linked = linked;
+	list_add(&linked->links, &link->list);
+	talloc_set_destructor(link, destroy_link);
+	return true;
+}
+
+void *_talloc_linked(const void *ctx, const void *newobj)
+{
+	struct talloc_linked *linked;
+
+	if (talloc_parent(newobj)) {
+		/* Assume leak reporting is on: create dummy parent. */
+		if (!talloc_links)
+			talloc_links = talloc_named_const(NULL, 0,
+							  "talloc_links");
+		/* This should now have same pseudo-NULL parent. */
+		assert(talloc_parent(newobj) == talloc_parent(talloc_links));
+	}
+
+	linked = talloc(talloc_links, struct talloc_linked);
+	if (!linked) {
+		talloc_free(newobj);
+		return NULL;
+	}
+	list_head_init(&linked->links);
+	linked->obj = talloc_steal(linked, newobj);
+
+	if (!add_link(ctx, linked)) {
+		talloc_free(linked);
+		return NULL;
+	}
+
+	return (void *)newobj;
+}
+
+void *_talloc_link(const void *ctx, const void *obj)
+{
+	struct talloc_linked *linked;
+
+	linked = talloc_get_type(talloc_parent(obj), struct talloc_linked);
+	assert(!list_empty(&linked->links));
+	return add_link(ctx, linked) ? (void *)obj : NULL;
+}
+
+void talloc_delink(const void *ctx, const void *obj)
+{
+	struct talloc_linked *linked;
+	struct talloc_link *i;
+
+	if (!obj)
+		return;
+
+	linked = talloc_get_type(talloc_parent(obj), struct talloc_linked);
+	list_for_each(&linked->links, i, list) {
+		if (talloc_is_parent(i, ctx)) {
+			talloc_free(i);
+			return;
+		}
+	}
+	abort();
+}

+ 47 - 0
ccan/talloc_link/talloc_link.h

@@ -0,0 +1,47 @@
+#ifndef TALLOC_LINK_H
+#define TALLOC_LINK_H
+#include <ccan/talloc/talloc.h>
+
+/**
+ * talloc_linked - set up an object with an initial link.
+ * @ctx - the context to initially link to
+ * @newobj - the newly allocated object (with a NULL parent)
+ *
+ * The object will be freed when @ctx is freed (or talloc_delink(ctx,
+ * newobj) is called), unless more links are added using
+ * talloc_link().
+ *
+ * For convenient chaining, it returns @newobj on success, or frees
+ * @newobj and returns NULL.
+ */
+#define talloc_linked(ctx, newobj) \
+	((_TALLOC_TYPEOF(newobj))_talloc_linked((ctx), (newobj)))
+
+/**
+ * talloc_link - add another link to a linkable object.
+ * @ctx - the context to link to
+ * @obj - the object previously made linkable with talloc_linked().
+ *
+ * The @obj will only be freed when all contexts linked to it are
+ * freed (or talloc_delink()ed).
+ *
+ * Returns @obj, or NULL on failure (out of memory).
+ */
+#define talloc_link(ctx, obj) \
+	((_TALLOC_TYPEOF(obj))_talloc_link((ctx), (obj)))
+
+/**
+ * talloc_delink - explicitly remove a link from a linkable object.
+ * @ctx - the context previously used for talloc_link/talloc_linked
+ * @obj - the object previously used for talloc_link/talloc_linked
+ *
+ * Explicitly remove a link: normally it is implied by freeing @ctx.
+ * Removing the last link frees the object.
+ */
+void talloc_delink(const void *ctx, const void *linked);
+
+/* Internal helpers. */
+void *_talloc_link(const void *ctx, const void *linked);
+void *_talloc_linked(const void *ctx, const void *linked);
+
+#endif /* TALLOC_LINK_H */

+ 116 - 0
ccan/talloc_link/test/run.c

@@ -0,0 +1,116 @@
+#include "talloc_link/talloc_link.h"
+#include "tap/tap.h"
+#include "talloc_link/talloc_link.c"
+#include <stdlib.h>
+#include <err.h>
+
+static unsigned int destroy_count = 0;
+static int destroy_obj(void *obj)
+{
+	destroy_count++;
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	void *obj, *p1, *p2, *p3;
+
+	plan_tests(16);
+
+	talloc_enable_leak_report();
+
+	/* Single parent case. */
+	p1 = talloc(NULL, char);
+	obj = talloc_linked(p1, talloc(NULL, char));
+	talloc_set_destructor(obj, destroy_obj);
+
+	ok(destroy_count == 0, "destroy_count = %u", destroy_count);
+	talloc_free(p1);
+	ok(destroy_count == 1, "destroy_count = %u", destroy_count);
+
+	/* Dual parent case. */
+	p1 = talloc(NULL, char);
+	obj = talloc_linked(p1, talloc(NULL, char));
+	talloc_set_destructor(obj, destroy_obj);
+	p2 = talloc(NULL, char);
+	talloc_link(p2, obj);
+
+	talloc_free(p1);
+	ok(destroy_count == 1, "destroy_count = %u", destroy_count);
+	talloc_free(p2);
+	ok(destroy_count == 2, "destroy_count = %u", destroy_count);
+	
+	/* Triple parent case. */
+	p1 = talloc(NULL, char);
+	obj = talloc_linked(p1, talloc(NULL, char));
+	talloc_set_destructor(obj, destroy_obj);
+
+	p2 = talloc(NULL, char);
+	p3 = talloc(NULL, char);
+
+	talloc_link(p2, obj);
+	talloc_link(p3, obj);
+
+	talloc_free(p1);
+	ok(destroy_count == 2, "destroy_count = %u", destroy_count);
+	talloc_free(p2);
+	ok(destroy_count == 2, "destroy_count = %u", destroy_count);
+	talloc_free(p3);
+	ok(destroy_count == 3, "destroy_count = %u", destroy_count);
+
+	/* Single delink case. */
+	p1 = talloc(NULL, char);
+	obj = talloc_linked(p1, talloc(NULL, char));
+	talloc_set_destructor(obj, destroy_obj);
+
+	ok(destroy_count == 3, "destroy_count = %u", destroy_count);
+	talloc_delink(p1, obj);
+	ok(destroy_count == 4, "destroy_count = %u", destroy_count);
+	talloc_free(p1);
+
+	/* Double delink case. */
+	p1 = talloc(NULL, char);
+	obj = talloc_linked(p1, talloc(NULL, char));
+	talloc_set_destructor(obj, destroy_obj);
+
+	p2 = talloc(NULL, char);
+	talloc_link(p2, obj);
+
+	talloc_delink(p1, obj);
+	ok(destroy_count == 4, "destroy_count = %u", destroy_count);
+	talloc_delink(p2, obj);
+	ok(destroy_count == 5, "destroy_count = %u", destroy_count);
+	talloc_free(p1);
+	talloc_free(p2);
+
+	/* Delink and free. */
+	p1 = talloc(NULL, char);
+	obj = talloc_linked(p1, talloc(NULL, char));
+	talloc_set_destructor(obj, destroy_obj);
+	p2 = talloc(NULL, char);
+	talloc_link(p2, obj);
+
+	talloc_delink(p1, obj);
+	ok(destroy_count == 5, "destroy_count = %u", destroy_count);
+	talloc_free(p2);
+	ok(destroy_count == 6, "destroy_count = %u", destroy_count);
+	talloc_free(p1);
+
+	/* Free and delink. */
+	p1 = talloc(NULL, char);
+	obj = talloc_linked(p1, talloc(NULL, char));
+	talloc_set_destructor(obj, destroy_obj);
+	p2 = talloc(NULL, char);
+	talloc_link(p2, obj);
+
+	talloc_free(p1);
+	ok(destroy_count == 6, "destroy_count = %u", destroy_count);
+	talloc_delink(p2, obj);
+	ok(destroy_count == 7, "destroy_count = %u", destroy_count);
+	talloc_free(p2);
+
+	/* No leaks? */
+	ok1(talloc_total_size(NULL) == 0);
+
+	return exit_status();
+}