Rusty Russell
7 years ago
6 changed files with 391 additions and 0 deletions
@ -0,0 +1 @@ |
|||||
|
../../../licenses/BSD-MIT |
@ -0,0 +1,139 @@ |
|||||
|
#include "config.h" |
||||
|
#include <stdio.h> |
||||
|
#include <string.h> |
||||
|
|
||||
|
/** |
||||
|
* tal/link - link helper for tal |
||||
|
* |
||||
|
* Tal does not support talloc-style references. In the cases where |
||||
|
* an object needs multiple parents, all parents need to be aware of |
||||
|
* the situation; thus tal/link is a helper where all "parents" |
||||
|
* tal_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. |
||||
|
* // Given "hello" outputs "1 cache hits HELLO \n" |
||||
|
* // Given "hello hello there" outputs "4 cache hits HELLO HELLO THERE \n" |
||||
|
* #include <stdio.h> |
||||
|
* #include <err.h> |
||||
|
* #include <string.h> |
||||
|
* #include <ctype.h> |
||||
|
* #include <ccan/tal/link/link.h> |
||||
|
* #include <ccan/tal/str/str.h> |
||||
|
* |
||||
|
* struct upcache { |
||||
|
* const char *str; |
||||
|
* const char *upstr; |
||||
|
* }; |
||||
|
* |
||||
|
* static struct upcache *cache; |
||||
|
* static unsigned int cache_hits = 0; |
||||
|
* #define CACHE_SIZE 4 |
||||
|
* static void init_upcase(void) |
||||
|
* { |
||||
|
* cache = tal_arrz(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 = tal_linkable(tal_strdup(NULL, str)); |
||||
|
* i = random() % CACHE_SIZE; |
||||
|
* |
||||
|
* // Throw out old: works fine if cache[i].upstr is NULL. |
||||
|
* tal_delink(cache, cache[i].upstr); |
||||
|
* |
||||
|
* // Replace with new. |
||||
|
* cache[i].str = str; |
||||
|
* cache[i].upstr = tal_link(cache, upstr); |
||||
|
* while (*upstr) { |
||||
|
* *upstr = toupper(*upstr); |
||||
|
* upstr++; |
||||
|
* } |
||||
|
* return &cache[i]; |
||||
|
* } |
||||
|
* |
||||
|
* // If you want to keep the result, tal_link it. |
||||
|
* static 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; |
||||
|
* } |
||||
|
* |
||||
|
* static void exit_upcase(void) |
||||
|
* { |
||||
|
* tal_free(cache); |
||||
|
* printf("%u cache hits ", cache_hits); |
||||
|
* } |
||||
|
* |
||||
|
* int main(int argc, char *argv[]) |
||||
|
* { |
||||
|
* int i; |
||||
|
* const char **values; |
||||
|
* |
||||
|
* // Initialize cache. |
||||
|
* init_upcase(); |
||||
|
* |
||||
|
* // Throw values in. |
||||
|
* values = tal_arr(NULL, const char *, argc); |
||||
|
* for (i = 1; i < argc; i++) |
||||
|
* values[i-1] = tal_link(values, get_upcase(argv[i])); |
||||
|
* |
||||
|
* // This will free all the values, but cache will still work. |
||||
|
* tal_free(values); |
||||
|
* |
||||
|
* // Repeat! |
||||
|
* values = tal_arr(NULL, const char *, argc); |
||||
|
* for (i = 1; i < argc; i++) |
||||
|
* values[i-1] = tal_link(values, get_upcase(argv[i])); |
||||
|
* |
||||
|
* // 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). |
||||
|
* tal_free(values); |
||||
|
* |
||||
|
* return 0; |
||||
|
* } |
||||
|
* |
||||
|
* License: BSD-MIT |
||||
|
* Author: Rusty Russell <rusty@rustcorp.com.au> |
||||
|
*/ |
||||
|
int main(int argc, char *argv[]) |
||||
|
{ |
||||
|
if (argc != 2) |
||||
|
return 1; |
||||
|
|
||||
|
if (strcmp(argv[1], "depends") == 0) { |
||||
|
printf("ccan/container_of\n"); |
||||
|
printf("ccan/list\n"); |
||||
|
printf("ccan/tal\n"); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
return 1; |
||||
|
} |
@ -0,0 +1,105 @@ |
|||||
|
/* Licensed under BSD-MIT - see LICENSE file for details */ |
||||
|
#include <ccan/tal/link/link.h> |
||||
|
#include <ccan/container_of/container_of.h> |
||||
|
#include <ccan/list/list.h> |
||||
|
#include <assert.h> |
||||
|
|
||||
|
/* Our linkable parent. */ |
||||
|
struct linkable { |
||||
|
struct list_head links; |
||||
|
}; |
||||
|
|
||||
|
struct link { |
||||
|
struct list_node list; |
||||
|
}; |
||||
|
|
||||
|
static void linkable_notifier(tal_t *linkable, |
||||
|
enum tal_notify_type type, |
||||
|
void *info UNNEEDED) |
||||
|
{ |
||||
|
struct linkable *l = tal_parent(linkable); |
||||
|
assert(type == TAL_NOTIFY_STEAL || type == TAL_NOTIFY_FREE); |
||||
|
|
||||
|
/* We let you free it if you haven't linked it yet. */ |
||||
|
if (type == TAL_NOTIFY_FREE && list_empty(&l->links)) { |
||||
|
tal_free(l); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
/* Don't try to steal or free this: it has multiple links! */ |
||||
|
abort(); |
||||
|
} |
||||
|
|
||||
|
void *tal_linkable_(tal_t *newobj) |
||||
|
{ |
||||
|
struct linkable *l; |
||||
|
|
||||
|
/* Must be a fresh object. */ |
||||
|
assert(!tal_parent(newobj)); |
||||
|
|
||||
|
l = tal(NULL, struct linkable); |
||||
|
if (!l) |
||||
|
goto fail; |
||||
|
list_head_init(&l->links); |
||||
|
|
||||
|
if (!tal_steal(l, newobj)) |
||||
|
goto fail; |
||||
|
|
||||
|
if (!tal_add_notifier(newobj, TAL_NOTIFY_STEAL|TAL_NOTIFY_FREE, |
||||
|
linkable_notifier)) { |
||||
|
tal_steal(NULL, newobj); |
||||
|
goto fail; |
||||
|
} |
||||
|
|
||||
|
return (void *)newobj; |
||||
|
|
||||
|
fail: |
||||
|
tal_free(l); |
||||
|
return NULL; |
||||
|
} |
||||
|
|
||||
|
static void destroy_link(struct link *lnk) |
||||
|
{ |
||||
|
struct linkable *l; |
||||
|
|
||||
|
/* Only true if we're first in list! */ |
||||
|
l = container_of(lnk->list.prev, struct linkable, links.n); |
||||
|
|
||||
|
list_del(&lnk->list); |
||||
|
|
||||
|
if (list_empty(&l->links)) |
||||
|
tal_free(l); |
||||
|
} |
||||
|
|
||||
|
void *tal_link_(const tal_t *ctx, const tal_t *link) |
||||
|
{ |
||||
|
struct linkable *l = tal_parent(link); |
||||
|
struct link *lnk = tal(ctx, struct link); |
||||
|
|
||||
|
if (!lnk) |
||||
|
return NULL; |
||||
|
if (!tal_add_destructor(lnk, destroy_link)) { |
||||
|
tal_free(lnk); |
||||
|
return NULL; |
||||
|
} |
||||
|
list_add(&l->links, &lnk->list); |
||||
|
return (void *)link; |
||||
|
} |
||||
|
|
||||
|
void tal_delink_(const tal_t *ctx, const tal_t *link) |
||||
|
{ |
||||
|
struct linkable *l = tal_parent(link); |
||||
|
struct link *i; |
||||
|
|
||||
|
if (!link) |
||||
|
return; |
||||
|
|
||||
|
/* FIXME: slow, but hopefully unusual. */ |
||||
|
list_for_each(&l->links, i, list) { |
||||
|
if (tal_parent(i) == ctx) { |
||||
|
tal_free(i); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
abort(); |
||||
|
} |
@ -0,0 +1,69 @@ |
|||||
|
/* Licensed under BSD-MIT - see LICENSE file for details */ |
||||
|
#ifndef TAL_LINK_H |
||||
|
#define TAL_LINK_H |
||||
|
#include "config.h" |
||||
|
#include <ccan/tal/tal.h> |
||||
|
|
||||
|
/**
|
||||
|
* tal_linkable - set up a tal object to be linkable. |
||||
|
* @newobj - the newly allocated object (with a NULL parent) |
||||
|
* |
||||
|
* The object will be freed when @newobj is freed or the last tal_link() |
||||
|
* is tal_delink'ed. |
||||
|
* |
||||
|
* Returns @newobj or NULL (if an allocation fails). |
||||
|
* |
||||
|
* Example: |
||||
|
* int *shared_count; |
||||
|
* |
||||
|
* shared_count = tal_linkable(talz(NULL, int)); |
||||
|
* assert(shared_count); |
||||
|
*/ |
||||
|
#define tal_linkable(newobj) \ |
||||
|
(tal_typeof(newobj) tal_linkable_((newobj))) |
||||
|
|
||||
|
/**
|
||||
|
* tal_link - add a(nother) link to a linkable object. |
||||
|
* @ctx - the context to link to (parent of the resulting link) |
||||
|
* @obj - the object previously made linkable with tal_linked(). |
||||
|
* |
||||
|
* If @ctx is non-NULL, the link will be a child of @ctx, and this freed |
||||
|
* when @ctx is. |
||||
|
* |
||||
|
* Returns NULL on failure (out of memory). |
||||
|
* |
||||
|
* Example: |
||||
|
* void *my_ctx = NULL; |
||||
|
* |
||||
|
* tal_link(my_ctx, shared_count); |
||||
|
*/ |
||||
|
#if HAVE_STATEMENT_EXPR |
||||
|
/* Weird macro avoids gcc's 'warning: value computed is not used'. */ |
||||
|
#define tal_link(ctx, obj) \ |
||||
|
({ tal_typeof(obj) tal_link_((ctx), (obj)); }) |
||||
|
#else |
||||
|
#define tal_link(ctx, obj) \ |
||||
|
(tal_typeof(obj) tal_link_((ctx), (obj))) |
||||
|
#endif |
||||
|
|
||||
|
/**
|
||||
|
* tal_delink - explicitly remove a link from a linkable object. |
||||
|
* @ctx - the context to link to (parent of the resulting link) |
||||
|
* @obj - the object previously made linkable with tal_linked(). |
||||
|
* |
||||
|
* Explicitly remove a link: normally it is implied by freeing @ctx. |
||||
|
* Removing the last link frees the object. If @obj is NULL, nothing |
||||
|
* is done. |
||||
|
* |
||||
|
* Example: |
||||
|
* tal_delink(my_ctx, shared_count); |
||||
|
*/ |
||||
|
#define tal_delink(ctx, obj) \ |
||||
|
tal_delink_((ctx), (obj)) |
||||
|
|
||||
|
/* Internal helpers. */ |
||||
|
void *tal_linkable_(tal_t *newobj); |
||||
|
void *tal_link_(const tal_t *ctx, const tal_t *dest); |
||||
|
void tal_delink_(const tal_t *ctx, const tal_t *dest); |
||||
|
|
||||
|
#endif /* TAL_LINK_H */ |
@ -0,0 +1,73 @@ |
|||||
|
#include <ccan/tal/link/link.c> |
||||
|
#include <ccan/tap/tap.h> |
||||
|
#include <stdlib.h> |
||||
|
#include <err.h> |
||||
|
|
||||
|
static unsigned int destroy_count = 0; |
||||
|
static void destroy_obj(void *obj UNNEEDED) |
||||
|
{ |
||||
|
destroy_count++; |
||||
|
} |
||||
|
|
||||
|
int main(void) |
||||
|
{ |
||||
|
char *linkable, *p1, *p2, *p3; |
||||
|
void **voidpp; |
||||
|
|
||||
|
plan_tests(23); |
||||
|
|
||||
|
linkable = tal(NULL, char); |
||||
|
ok1(tal_linkable(linkable) == linkable); |
||||
|
ok1(tal_add_destructor(linkable, destroy_obj)); |
||||
|
/* First, free it immediately. */ |
||||
|
tal_free(linkable); |
||||
|
ok1(destroy_count == 1); |
||||
|
|
||||
|
/* Now create and remove a single link. */ |
||||
|
linkable = tal_linkable(tal(NULL, char)); |
||||
|
ok1(tal_add_destructor(linkable, destroy_obj)); |
||||
|
ok1(p1 = tal_link(NULL, linkable)); |
||||
|
ok1(p1 == linkable); |
||||
|
tal_delink(NULL, linkable); |
||||
|
ok1(destroy_count == 2); |
||||
|
|
||||
|
/* Two links.*/ |
||||
|
linkable = tal_linkable(tal(NULL, char)); |
||||
|
ok1(tal_add_destructor(linkable, destroy_obj)); |
||||
|
ok1(p1 = tal_link(NULL, linkable)); |
||||
|
ok1(p1 == linkable); |
||||
|
ok1(p2 = tal_link(NULL, linkable)); |
||||
|
ok1(p2 == linkable); |
||||
|
tal_delink(NULL, linkable); |
||||
|
tal_delink(NULL, linkable); |
||||
|
ok1(destroy_count == 3); |
||||
|
|
||||
|
/* Three links.*/ |
||||
|
linkable = tal_linkable(tal(NULL, char)); |
||||
|
ok1(tal_add_destructor(linkable, destroy_obj)); |
||||
|
ok1(p1 = tal_link(NULL, linkable)); |
||||
|
ok1(p1 == linkable); |
||||
|
ok1(p2 = tal_link(NULL, linkable)); |
||||
|
ok1(p2 == linkable); |
||||
|
ok1(p3 = tal_link(NULL, linkable)); |
||||
|
ok1(p3 == linkable); |
||||
|
tal_delink(NULL, linkable); |
||||
|
tal_delink(NULL, linkable); |
||||
|
tal_delink(NULL, linkable); |
||||
|
ok1(destroy_count == 4); |
||||
|
|
||||
|
/* Now, indirectly. */ |
||||
|
voidpp = tal(NULL, void *); |
||||
|
linkable = tal_linkable(tal(NULL, char)); |
||||
|
ok1(tal_add_destructor(linkable, destroy_obj)); |
||||
|
/* Suppress gratuitous warning with tests_compile_without_features */ |
||||
|
#if HAVE_STATEMENT_EXPR |
||||
|
tal_link(voidpp, linkable); |
||||
|
#else |
||||
|
(void)tal_link(voidpp, linkable); |
||||
|
#endif |
||||
|
tal_free(voidpp); |
||||
|
ok1(destroy_count == 5); |
||||
|
|
||||
|
return exit_status(); |
||||
|
} |
Loading…
Reference in new issue