Hi (Angus), I found problem with map and libqb. Attached is simplest reproducer which I was able to create.
Result is following: Iter test.key1 = 0x5a2a290 (1) Freeing value Notify (nil) 0x5a2a290==15090== Invalid read of size 4 ==15090== at 0x400988: notify_fn (in /root/a.out) ==15090== by 0x4E47190: trie_notify (trie.c:297) ==15090== by 0x4E46C37: trie_node_destroy (trie.c:125) ==15090== by 0x4E46C90: trie_node_deref (trie.c:138) ==15090== by 0x4E4762E: trie_iter_next (trie.c:437) ==15090== by 0x4E44D30: qb_map_iter_next (map.c:82) ==15090== by 0x400B33: main (in /root/a.out) ==15090== Address 0x5a2a290 is 0 bytes inside a block of size 8 free'd ==15090== at 0x4C2695D: free (vg_replace_malloc.c:366) ==15090== by 0x400B20: main (in /root/a.out) ==15090== old value = 1 Iter test.key2 = 0x5a2a2e0 (2) Freeing value Notify (nil) 0x5a2a2e0old value = 2 ==15090== ==15090== HEAP SUMMARY: ==15090== in use at exit: 4,304 bytes in 25 blocks ==15090== total heap usage: 29 allocs, 4 frees, 4,800 bytes allocated ==15090== ==15090== LEAK SUMMARY: ==15090== definitely lost: 32 bytes in 1 blocks ==15090== indirectly lost: 4,272 bytes in 24 blocks ==15090== possibly lost: 0 bytes in 0 blocks ==15090== still reachable: 0 bytes in 0 blocks ==15090== suppressed: 0 bytes in 0 blocks ==15090== Rerun with --leak-check=full to see details of leaked memory ==15090== ==15090== For counts of detected and suppressed errors, rerun with: -v ==15090== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 6 from 6)
There should be NO invalid read of size ... Main problem seems to be with fact, that delete callback is called after free in iter, but in that time, pointer is already freed. Possible workaround may be to free value only in callback, but it can be very hard (impossible) to realize.
Regards, Honza
On Fri, Dec 09, 2011 at 05:24:06PM +0100, Jan Friesse wrote:
Hi (Angus), I found problem with map and libqb. Attached is simplest reproducer which I was able to create.
Thanks Honza, I'll look into it asap.
-Angus
Result is following: Iter test.key1 = 0x5a2a290 (1) Freeing value Notify (nil) 0x5a2a290==15090== Invalid read of size 4 ==15090== at 0x400988: notify_fn (in /root/a.out) ==15090== by 0x4E47190: trie_notify (trie.c:297) ==15090== by 0x4E46C37: trie_node_destroy (trie.c:125) ==15090== by 0x4E46C90: trie_node_deref (trie.c:138) ==15090== by 0x4E4762E: trie_iter_next (trie.c:437) ==15090== by 0x4E44D30: qb_map_iter_next (map.c:82) ==15090== by 0x400B33: main (in /root/a.out) ==15090== Address 0x5a2a290 is 0 bytes inside a block of size 8 free'd ==15090== at 0x4C2695D: free (vg_replace_malloc.c:366) ==15090== by 0x400B20: main (in /root/a.out) ==15090== old value = 1 Iter test.key2 = 0x5a2a2e0 (2) Freeing value Notify (nil) 0x5a2a2e0old value = 2 ==15090== ==15090== HEAP SUMMARY: ==15090== in use at exit: 4,304 bytes in 25 blocks ==15090== total heap usage: 29 allocs, 4 frees, 4,800 bytes allocated ==15090== ==15090== LEAK SUMMARY: ==15090== definitely lost: 32 bytes in 1 blocks ==15090== indirectly lost: 4,272 bytes in 24 blocks ==15090== possibly lost: 0 bytes in 0 blocks ==15090== still reachable: 0 bytes in 0 blocks ==15090== suppressed: 0 bytes in 0 blocks ==15090== Rerun with --leak-check=full to see details of leaked memory ==15090== ==15090== For counts of detected and suppressed errors, rerun with: -v ==15090== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 6 from 6)
There should be NO invalid read of size ... Main problem seems to be with fact, that delete callback is called after free in iter, but in that time, pointer is already freed. Possible workaround may be to free value only in callback, but it can be very hard (impossible) to realize.
Regards, Honza
/*
- Compile as gcc -lqb libqb_map_bug.c
*/ #include <assert.h> #include <stdlib.h> #include <stdio.h> #include <qb/qbdefs.h> #include <qb/qbutil.h> #include <qb/qbmap.h>
void notify_fn(uint32_t event, char* key, void* old_value, void* value, void* user_data) { fprintf(stderr, "Notify %p %p", value, old_value);
if (value != NULL) fprintf(stderr, "value = %u\n", *(int *)value); if (old_value != NULL) fprintf(stderr, "old value = %u\n", *(int *)old_value); }
int main(void) { qb_map_t *trie; int *i1, *i2; qb_map_iter_t *iter; const char *key; void *val;
trie = qb_trie_create(); assert(trie != NULL);
i1 = malloc(sizeof(i1)); assert(i1 != NULL); i2 = malloc(sizeof(i2)); assert(i2 != NULL); *i1 = 1; *i2 = 2; qb_map_put(trie, "test.key1", i1); qb_map_put(trie, "test.key2", i2);
qb_map_notify_add(trie, "test.", notify_fn, QB_MAP_NOTIFY_DELETED | QB_MAP_NOTIFY_REPLACED | QB_MAP_NOTIFY_INSERTED | QB_MAP_NOTIFY_RECURSIVE, NULL);
iter = qb_map_pref_iter_create(trie, "test."); while ((key = qb_map_iter_next(iter, &val)) != NULL) { fprintf(stderr,"Iter %s = %p (%u)\n", key, val, *(int*)val); qb_map_rm(trie, key); fprintf(stderr, "Freeing value\n"); free(val); }
return (0); }
quarterback-devel mailing list quarterback-devel@lists.fedorahosted.org https://fedorahosted.org/mailman/listinfo/quarterback-devel
On Fri, Dec 09, 2011 at 05:24:06PM +0100, Jan Friesse wrote:
Hi (Angus), I found problem with map and libqb. Attached is simplest reproducer which I was able to create.
Result is following: Iter test.key1 = 0x5a2a290 (1) Freeing value Notify (nil) 0x5a2a290==15090== Invalid read of size 4 ==15090== at 0x400988: notify_fn (in /root/a.out) ==15090== by 0x4E47190: trie_notify (trie.c:297) ==15090== by 0x4E46C37: trie_node_destroy (trie.c:125) ==15090== by 0x4E46C90: trie_node_deref (trie.c:138) ==15090== by 0x4E4762E: trie_iter_next (trie.c:437) ==15090== by 0x4E44D30: qb_map_iter_next (map.c:82) ==15090== by 0x400B33: main (in /root/a.out) ==15090== Address 0x5a2a290 is 0 bytes inside a block of size 8 free'd ==15090== at 0x4C2695D: free (vg_replace_malloc.c:366) ==15090== by 0x400B20: main (in /root/a.out) ==15090== old value = 1 Iter test.key2 = 0x5a2a2e0 (2) Freeing value Notify (nil) 0x5a2a2e0old value = 2 ==15090== ==15090== HEAP SUMMARY: ==15090== in use at exit: 4,304 bytes in 25 blocks ==15090== total heap usage: 29 allocs, 4 frees, 4,800 bytes allocated ==15090== ==15090== LEAK SUMMARY: ==15090== definitely lost: 32 bytes in 1 blocks ==15090== indirectly lost: 4,272 bytes in 24 blocks ==15090== possibly lost: 0 bytes in 0 blocks ==15090== still reachable: 0 bytes in 0 blocks ==15090== suppressed: 0 bytes in 0 blocks ==15090== Rerun with --leak-check=full to see details of leaked memory ==15090== ==15090== For counts of detected and suppressed errors, rerun with: -v ==15090== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 6 from 6)
There should be NO invalid read of size ... Main problem seems to be with fact, that delete callback is called after free in iter, but in that time, pointer is already freed. Possible workaround may be to free value only in callback,
Free'ing in the callback is the expected usage (not the workaround) and there is a reason for it.
1) The iterator needs to reference count the node so that if someone else deletes it while you have it, it doesn't cause a problem.
2) What if you replace a value, you might leak memory. So we have a callback to allow you to free in both cases (delete and replace).
but it can be very hard (impossible) to realize.
Why? Would it help to have a seperate callback to free mem?
-Angus
Regards, Honza
/*
- Compile as gcc -lqb libqb_map_bug.c
*/ #include <assert.h> #include <stdlib.h> #include <stdio.h> #include <qb/qbdefs.h> #include <qb/qbutil.h> #include <qb/qbmap.h>
void notify_fn(uint32_t event, char* key, void* old_value, void* value, void* user_data) { fprintf(stderr, "Notify %p %p", value, old_value);
if (value != NULL) fprintf(stderr, "value = %u\n", *(int *)value); if (old_value != NULL) fprintf(stderr, "old value = %u\n", *(int *)old_value); }
int main(void) { qb_map_t *trie; int *i1, *i2; qb_map_iter_t *iter; const char *key; void *val;
trie = qb_trie_create(); assert(trie != NULL);
i1 = malloc(sizeof(i1)); assert(i1 != NULL); i2 = malloc(sizeof(i2)); assert(i2 != NULL); *i1 = 1; *i2 = 2; qb_map_put(trie, "test.key1", i1); qb_map_put(trie, "test.key2", i2);
qb_map_notify_add(trie, "test.", notify_fn, QB_MAP_NOTIFY_DELETED | QB_MAP_NOTIFY_REPLACED | QB_MAP_NOTIFY_INSERTED | QB_MAP_NOTIFY_RECURSIVE, NULL);
iter = qb_map_pref_iter_create(trie, "test."); while ((key = qb_map_iter_next(iter, &val)) != NULL) { fprintf(stderr,"Iter %s = %p (%u)\n", key, val, *(int*)val); qb_map_rm(trie, key); fprintf(stderr, "Freeing value\n");
Don't free here, rather in notify_fn()
free(val);
}
return (0); }
quarterback-devel mailing list quarterback-devel@lists.fedorahosted.org https://fedorahosted.org/mailman/listinfo/quarterback-devel
Angus Salkeld napsal(a):
On Fri, Dec 09, 2011 at 05:24:06PM +0100, Jan Friesse wrote:
Hi (Angus), I found problem with map and libqb. Attached is simplest reproducer which I was able to create.
Result is following: Iter test.key1 = 0x5a2a290 (1) ... ==15090== For counts of detected and suppressed errors, rerun with: -v ==15090== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 6 from 6)
There should be NO invalid read of size ... Main problem seems to be with fact, that delete callback is called after free in iter, but in that time, pointer is already freed. Possible workaround may be to free value only in callback,
Free'ing in the callback is the expected usage (not the workaround) and there is a reason for it.
The iterator needs to reference count the node so that if someone else deletes it while you have it, it doesn't cause a problem.
I can understand technical reasons, but It's not documented.
What if you replace a value, you might leak memory. So we have a callback to allow you to free in both cases (delete and replace).
I know pointer before replace, so I can free it after replace with no problem.
but it can be very hard (impossible) to realize.
Why? Would it help to have a seperate callback to free mem?
It's simple. There is no documentation about iter/callback behavior and also, I can never be sure which callback will be called last. So ya, separate callback with semantic like "Now, I'm throwing away this pointer and I will no longer take evidence of that" seems to be perfect solution. Another solution may be documented behavior like "First/last registered callback is always called first/last", where preferred option is "First callback on NULL key with RECURSIVE event is always called last".
Honza
-Angus
...
On Mon, Dec 12, 2011 at 10:22:42AM +0100, Jan Friesse wrote:
Angus Salkeld napsal(a):
On Fri, Dec 09, 2011 at 05:24:06PM +0100, Jan Friesse wrote:
Hi (Angus), I found problem with map and libqb. Attached is simplest reproducer which I was able to create.
Result is following: Iter test.key1 = 0x5a2a290 (1) ... ==15090== For counts of detected and suppressed errors, rerun with: -v ==15090== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 6 from 6)
There should be NO invalid read of size ... Main problem seems to be with fact, that delete callback is called after free in iter, but in that time, pointer is already freed. Possible workaround may be to free value only in callback,
Free'ing in the callback is the expected usage (not the workaround) and there is a reason for it.
The iterator needs to reference count the node so that if someone else deletes it while you have it, it doesn't cause a problem.
I can understand technical reasons, but It's not documented.
Yes, the docs are not good enough in qbmap.h I'll work to fix that.
What if you replace a value, you might leak memory. So we have a callback to allow you to free in both cases (delete and replace).
I know pointer before replace, so I can free it after replace with no problem.
but it can be very hard (impossible) to realize.
Why? Would it help to have a seperate callback to free mem?
It's simple. There is no documentation about iter/callback behavior and also, I can never be sure which callback will be called last. So ya, separate callback with semantic like "Now, I'm throwing away this pointer and I will no longer take evidence of that" seems to be perfect solution. Another solution may be documented behavior like "First/last registered callback is always called first/last", where preferred option is "First callback on NULL key with RECURSIVE event is always called last".
Cool, I'll sort something out and ping you to see if it suits.
-Angus
Honza
-Angus
...
quarterback-devel mailing list quarterback-devel@lists.fedorahosted.org https://fedorahosted.org/mailman/listinfo/quarterback-devel
At the moment if you have multiple notifiers it becomes impossible to figure out when it is safe to free the values.
This new callback can only be added once and is called once, making it better suited to freeing memory. It is also called after the deleted and replaced notifiers.
Signed-off-by: Angus Salkeld asalkeld@redhat.com --- include/qb/qbmap.h | 1 + lib/hashtable.c | 23 ++++++++++++++++++- lib/map.c | 3 ++ lib/skiplist.c | 21 ++++++++++++++++- lib/trie.c | 31 ++++++++++++++++++++++++- tests/check_map.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 137 insertions(+), 4 deletions(-)
diff --git a/include/qb/qbmap.h b/include/qb/qbmap.h index dc32a56..18a08a1 100644 --- a/include/qb/qbmap.h +++ b/include/qb/qbmap.h @@ -48,6 +48,7 @@ typedef struct qb_map_iter qb_map_iter_t; #define QB_MAP_NOTIFY_REPLACED 2 #define QB_MAP_NOTIFY_INSERTED 4 #define QB_MAP_NOTIFY_RECURSIVE 8 +#define QB_MAP_NOTIFY_FREE 16
typedef void (*qb_map_notify_fn)(uint32_t event, char* key, diff --git a/lib/hashtable.c b/lib/hashtable.c index f6f2edf..492cc88 100644 --- a/lib/hashtable.c +++ b/lib/hashtable.c @@ -242,6 +242,12 @@ hashtable_notify(struct hash_table *t, struct hash_node *n, tn->callback(event, (char *)key, old_value, value, tn->user_data); } + if (((event & QB_MAP_NOTIFY_DELETED) || + (event & QB_MAP_NOTIFY_REPLACED)) && + (tn->events & QB_MAP_NOTIFY_FREE)) { + tn->callback(QB_MAP_NOTIFY_FREE, (char *)key, + old_value, value, tn->user_data); + } } }
@@ -254,6 +260,7 @@ hashtable_notify_add(qb_map_t * m, const char *key, struct hash_node *n; struct qb_list_head *head = NULL; struct qb_list_head *list; + int add_to_tail = QB_FALSE;
if (key) { n = hashtable_lookup(t, key); @@ -266,9 +273,18 @@ hashtable_notify_add(qb_map_t * m, const char *key, if (head == NULL) { return -ENOENT; } + if (events & QB_MAP_NOTIFY_FREE) { + add_to_tail = QB_TRUE; + } + for (list = head->next; list != head; list = list->next) { f = qb_list_entry(list, struct qb_map_notifier, list);
+ if (events & QB_MAP_NOTIFY_FREE && + f->events == events) { + /* only one free notifier */ + return -EEXIST; + } if (f->events == events && f->user_data == user_data && f->callback == fn) { @@ -284,7 +300,12 @@ hashtable_notify_add(qb_map_t * m, const char *key, f->user_data = user_data; f->callback = fn; qb_list_init(&f->list); - qb_list_add(&f->list, head); + + if (add_to_tail) { + qb_list_add_tail(&f->list, head); + } else { + qb_list_add(&f->list, head); + } return 0; }
diff --git a/lib/map.c b/lib/map.c index 2e28451..d3aa8f5 100644 --- a/lib/map.c +++ b/lib/map.c @@ -92,6 +92,9 @@ int32_t qb_map_notify_add(qb_map_t * m, const char *key, qb_map_notify_fn fn, int32_t events, void *user_data) { + if (key != NULL && events & QB_MAP_NOTIFY_FREE) { + return -EINVAL; + } if (m->notify_add) { return m->notify_add(m, key, fn, events, user_data); } else { diff --git a/lib/skiplist.c b/lib/skiplist.c index 045fa11..ff12c02 100644 --- a/lib/skiplist.c +++ b/lib/skiplist.c @@ -210,6 +210,12 @@ skiplist_notify(struct skiplist *l, struct skiplist_node *n, tn->callback(event, key, old_value, value, tn->user_data); } + if (((event & QB_MAP_NOTIFY_DELETED) || + (event & QB_MAP_NOTIFY_REPLACED)) && + (tn->events & QB_MAP_NOTIFY_FREE)) { + tn->callback(QB_MAP_NOTIFY_FREE, (char *)key, + old_value, value, tn->user_data); + } }
} @@ -242,17 +248,26 @@ skiplist_notify_add(qb_map_t * m, const char *key, struct qb_map_notifier *f; struct skiplist_node *n; struct qb_list_head *list; + int add_to_tail = QB_FALSE;
if (key) { n = skiplist_lookup(t, key); } else { n = t->header; } + if (events & QB_MAP_NOTIFY_FREE) { + add_to_tail = QB_TRUE; + } if (n) { for (list = n->notifier_head.next; list != &n->notifier_head; list = list->next) { f = qb_list_entry(list, struct qb_map_notifier, list);
+ if (events & QB_MAP_NOTIFY_FREE && + f->events == events) { + /* only one free notifier */ + return -EEXIST; + } if (f->events == events && f->callback == fn && f->user_data == user_data) { @@ -268,7 +283,11 @@ skiplist_notify_add(qb_map_t * m, const char *key, f->user_data = user_data; f->callback = fn; qb_list_init(&f->list); - qb_list_add(&f->list, &n->notifier_head); + if (add_to_tail) { + qb_list_add_tail(&f->list, &n->notifier_head); + } else { + qb_list_add(&f->list, &n->notifier_head); + } return 0; } return -EINVAL; diff --git a/lib/trie.c b/lib/trie.c index 3283d12..38dc2d1 100644 --- a/lib/trie.c +++ b/lib/trie.c @@ -122,6 +122,9 @@ keep_going: static void trie_node_destroy(struct trie *t, struct trie_node *n) { + if (t->header == n) { + return; + } trie_notify(n, QB_MAP_NOTIFY_DELETED, n->key, n->value, NULL);
n->key = NULL; @@ -131,6 +134,9 @@ trie_node_destroy(struct trie *t, struct trie_node *n) static void trie_node_deref(struct trie *t, struct trie_node *node) { + if (t->header == node) { + return; + } node->refcount--; if (node->refcount > 0) { return; @@ -297,6 +303,12 @@ trie_notify(struct trie_node *n, tn->callback(event, (char *)key, old_value, value, tn->user_data); } + if (((event & QB_MAP_NOTIFY_DELETED) || + (event & QB_MAP_NOTIFY_REPLACED)) && + (tn->events & QB_MAP_NOTIFY_FREE)) { + tn->callback(QB_MAP_NOTIFY_FREE, (char *)key, + old_value, value, tn->user_data); + } } c = c->parent; } while (c); @@ -310,6 +322,7 @@ trie_notify_add(qb_map_t * m, const char *key, struct qb_map_notifier *f; struct trie_node *n; struct qb_list_head *list; + int add_to_tail = QB_FALSE;
if (key) { n = trie_lookup(t, key, QB_TRUE); @@ -321,6 +334,11 @@ trie_notify_add(qb_map_t * m, const char *key, list != &n->notifier_head; list = list->next) { f = qb_list_entry(list, struct qb_map_notifier, list);
+ if (events & QB_MAP_NOTIFY_FREE && + f->events == events) { + /* only one free notifier */ + return -EEXIST; + } if (f->events == events && f->callback == fn && f->user_data == user_data) { @@ -336,10 +354,19 @@ trie_notify_add(qb_map_t * m, const char *key, f->user_data = user_data; f->callback = fn; qb_list_init(&f->list); - if (events & QB_MAP_NOTIFY_RECURSIVE) { - qb_list_add(&f->list, &n->notifier_head); + if (key) { + if (events & QB_MAP_NOTIFY_RECURSIVE) { + add_to_tail = QB_TRUE; + } } else { + if (events & QB_MAP_NOTIFY_FREE) { + add_to_tail = QB_TRUE; + } + } + if (add_to_tail) { qb_list_add_tail(&f->list, &n->notifier_head); + } else { + qb_list_add(&f->list, &n->notifier_head); } return 0; } diff --git a/tests/check_map.c b/tests/check_map.c index 815b321..011ad01 100644 --- a/tests/check_map.c +++ b/tests/check_map.c @@ -43,6 +43,8 @@ static char *notified_key = NULL; static void *notified_value = NULL; static void *notified_new_value = NULL; static void *notified_user_data = NULL; +static int32_t notified_event = 0; +static int32_t notified_event_prev = 0;
static void test_map_simple(qb_map_t *m, const char *name) @@ -190,6 +192,15 @@ my_map_notification(uint32_t event, notified_value = old_value; notified_new_value = value; notified_user_data = user_data; + notified_event_prev = notified_event; + notified_event = event; +} + +static void +my_map_notification_2(uint32_t event, + char* key, void* old_value, + void* value, void* user_data) +{ }
static void @@ -324,6 +335,51 @@ test_map_notifications_basic(qb_map_t *m) ck_assert_int_eq(i, -EEXIST); }
+/* test free'ing notifier + * + * input: + * only one can be added + * can only be added with NULL key (global) + * output: + * is the last notifier called (after deleted or replaced) + * recursive is implicit + */ +static void +test_map_notifications_free(qb_map_t *m) +{ + int32_t i; + i = qb_map_notify_add(m, "not global", my_map_notification, + QB_MAP_NOTIFY_FREE, m); + ck_assert_int_eq(i, -EINVAL); + i = qb_map_notify_add(m, NULL, my_map_notification, + QB_MAP_NOTIFY_FREE, m); + ck_assert_int_eq(i, 0); + i = qb_map_notify_add(m, NULL, my_map_notification_2, + QB_MAP_NOTIFY_FREE, m); + ck_assert_int_eq(i, -EEXIST); + i = qb_map_notify_del_2(m, NULL, my_map_notification, + QB_MAP_NOTIFY_FREE, m); + ck_assert_int_eq(i, 0); + i = qb_map_notify_add(m, NULL, my_map_notification, + (QB_MAP_NOTIFY_FREE | + QB_MAP_NOTIFY_REPLACED | + QB_MAP_NOTIFY_DELETED | + QB_MAP_NOTIFY_RECURSIVE), m); + ck_assert_int_eq(i, 0); + + qb_map_put(m, "garden", "grow"); + +/* update */ + qb_map_put(m, "garden", "green"); + ck_assert_int_eq(notified_event_prev, QB_MAP_NOTIFY_REPLACED); + ck_assert_int_eq(notified_event, QB_MAP_NOTIFY_FREE); + +/* delete */ + qb_map_rm(m, "garden"); + ck_assert_int_eq(notified_event_prev, QB_MAP_NOTIFY_DELETED); + ck_assert_int_eq(notified_event, QB_MAP_NOTIFY_FREE); +} + static void test_map_notifications_prefix(qb_map_t *m) { @@ -668,6 +724,8 @@ START_TEST(test_trie_notifications) test_map_notifications_basic(m); m = qb_trie_create(); test_map_notifications_prefix(m); + m = qb_trie_create(); + test_map_notifications_free(m); } END_TEST
@@ -676,6 +734,8 @@ START_TEST(test_hash_notifications) qb_map_t *m; m = qb_hashtable_create(256); test_map_notifications_basic(m); + m = qb_hashtable_create(256); + test_map_notifications_free(m); } END_TEST
@@ -684,6 +744,8 @@ START_TEST(test_skiplist_notifications) qb_map_t *m; m = qb_skiplist_create(); test_map_notifications_basic(m); + m = qb_skiplist_create(); + test_map_notifications_free(m); } END_TEST
Signed-off-by: Angus Salkeld asalkeld@redhat.com --- examples/.gitignore | 1 + examples/Makefile.am | 6 +++- examples/mapnotify.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 1 deletions(-) create mode 100644 examples/mapnotify.c
diff --git a/examples/.gitignore b/examples/.gitignore index bf73ae4..40990d8 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -3,3 +3,4 @@ tcpclient tcpserver ipcclient ipcserver +mapnotify diff --git a/examples/Makefile.am b/examples/Makefile.am index f7dee65..a04754b 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -21,7 +21,11 @@ MAINTAINERCLEANFILES = Makefiles.in EXTRA_DIST = CLEANFILES =
-noinst_PROGRAMS = simplelog tcpclient tcpserver ipcclient ipcserver +noinst_PROGRAMS = mapnotify simplelog tcpclient tcpserver ipcclient ipcserver + +mapnotify_SOURCES = mapnotify.c $(top_builddir)/include/qb/qbmap.h +mapnotify_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include +mapnotify_LDADD = -lrt $(top_builddir)/lib/libqb.la
simplelog_SOURCES = simplelog.c $(top_builddir)/include/qb/qblog.h simplelog_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include diff --git a/examples/mapnotify.c b/examples/mapnotify.c new file mode 100644 index 0000000..5411b77 --- /dev/null +++ b/examples/mapnotify.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2011 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Friesse jfriesse@redhat.com + * Angus Salkeld asalkeld@redhat.com + * + * libqb is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * libqb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libqb. If not, see http://www.gnu.org/licenses/. + */ +#include "os_base.h" + +#include <qb/qbdefs.h> +#include <qb/qbutil.h> +#include <qb/qbmap.h> + +static void +notify_fn(uint32_t event, char* key, void* old_value, void* value, void* user_data) +{ + if (event == QB_MAP_NOTIFY_FREE) { + fprintf(stderr, "Notify[FREE] %s [%d]\n", + key, *(int *)old_value); + free(old_value); + } else if (event == QB_MAP_NOTIFY_DELETED) { + fprintf(stderr, "Notify[DELETED] %s [%d]\n", + key, *(int *)old_value); + } else if (event == QB_MAP_NOTIFY_REPLACED) { + fprintf(stderr, "Notify[REPLACED] %s [%d] -> [%d]\n", + key, *(int *)old_value, *(int *)value); + } else { + fprintf(stderr, "Notify[%d] %s \n", event, key); + if (value != NULL) { + fprintf(stderr, " value = [%d]\n", *(int *)value); + } + if (old_value != NULL) { + fprintf(stderr, " old value = [%d]\n", *(int *)old_value); + } + } +} + +int +main(void) +{ + qb_map_t *trie; + int *i1, *i2, *i3; + qb_map_iter_t *iter; + const char *key; + void *val; + uint32_t revents = (QB_MAP_NOTIFY_DELETED | + QB_MAP_NOTIFY_REPLACED | + QB_MAP_NOTIFY_INSERTED | + QB_MAP_NOTIFY_RECURSIVE); + + trie = qb_trie_create(); + assert(trie != NULL); + + i1 = malloc(sizeof(int)); + assert(i1 != NULL); + *i1 = 1; + + i2 = malloc(sizeof(int)); + assert(i2 != NULL); + *i2 = 2; + + i3 = malloc(sizeof(int)); + assert(i3 != NULL); + *i3 = 3; + + qb_map_notify_add(trie, NULL, notify_fn, QB_MAP_NOTIFY_FREE, NULL); + + qb_map_put(trie, "test.key1", i1); + qb_map_put(trie, "test.key2", i2); + + qb_map_notify_add(trie, "test.", notify_fn, revents, NULL); + + qb_map_put(trie, "test.key1", i3); + + iter = qb_map_pref_iter_create(trie, "test."); + while ((key = qb_map_iter_next(iter, &val)) != NULL) { + fprintf(stderr,"Iter %s [%d]\n", key, *(int*)val); + qb_map_rm(trie, key); + } + qb_map_iter_free(iter); + qb_map_notify_del_2(trie, "test.", notify_fn, revents, NULL); + qb_map_destroy(trie); + + return (0); +}
quarterback-devel@lists.fedorahosted.org