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