Commit c210db0d authored by Nick Mathewson's avatar Nick Mathewson 🎨
Browse files

Enhance pqueue so we can remove items from the middle.

This changes the pqueue API by requiring an additional int in every
structure that we store in a pqueue to hold the index of that structure
within the heap.
parent d086c9a7
...@@ -605,6 +605,38 @@ smartlist_uniq_strings(smartlist_t *sl) ...@@ -605,6 +605,38 @@ smartlist_uniq_strings(smartlist_t *sl)
/* Heap-based priority queue implementation for O(lg N) insert and remove. /* Heap-based priority queue implementation for O(lg N) insert and remove.
* Recall that the heap property is that, for every index I, h[I] < * Recall that the heap property is that, for every index I, h[I] <
* H[LEFT_CHILD[I]] and h[I] < H[RIGHT_CHILD[I]]. * H[LEFT_CHILD[I]] and h[I] < H[RIGHT_CHILD[I]].
*
* For us to remove items other than the topmost item, each item must store
* its own index within the heap. When calling the pqueue functions, tell
* them about the offset of the field that stores the index within the item.
*
* Example:
*
* typedef struct timer_t {
* struct timeval tv;
* int heap_index;
* } timer_t;
*
* static int compare(const void *p1, const void *p2) {
* const timer_t *t1 = p1, *t2 = p2;
* if (t1->tv.tv_sec < t2->tv.tv_sec) {
* return -1;
* } else if (t1->tv.tv_sec > t2->tv.tv_sec) {
* return 1;
* } else {
* return t1->tv.tv_usec - t2->tv_usec;
* }
* }
*
* void timer_heap_insert(smartlist_t *heap, timer_t *timer) {
* smartlist_pqueue_add(heap, compare, STRUCT_OFFSET(timer_t, heap_index),
* timer);
* }
*
* void timer_heap_pop(smartlist_t *heap) {
* return smartlist_pqueue_pop(heap, compare,
* STRUCT_OFFSET(timer_t, heap_index));
* }
*/ */
/* For a 1-indexed array, we would use LEFT_CHILD[x] = 2*x and RIGHT_CHILD[x] /* For a 1-indexed array, we would use LEFT_CHILD[x] = 2*x and RIGHT_CHILD[x]
...@@ -616,12 +648,22 @@ smartlist_uniq_strings(smartlist_t *sl) ...@@ -616,12 +648,22 @@ smartlist_uniq_strings(smartlist_t *sl)
#define RIGHT_CHILD(i) ( 2*(i) + 2 ) #define RIGHT_CHILD(i) ( 2*(i) + 2 )
#define PARENT(i) ( ((i)-1) / 2 ) #define PARENT(i) ( ((i)-1) / 2 )
#define IDXP(p) ((int*)STRUCT_VAR_P(p, idx_field_offset))
#define UPDATE_IDX(i) do { \
void *updated = sl->list[i]; \
*IDXP(updated) = i; \
} while (0)
#define IDX_OF_ITEM(p) (*IDXP(p))
/** Helper. <b>sl</b> may have at most one violation of the heap property: /** Helper. <b>sl</b> may have at most one violation of the heap property:
* the item at <b>idx</b> may be greater than one or both of its children. * the item at <b>idx</b> may be greater than one or both of its children.
* Restore the heap property. */ * Restore the heap property. */
static INLINE void static INLINE void
smartlist_heapify(smartlist_t *sl, smartlist_heapify(smartlist_t *sl,
int (*compare)(const void *a, const void *b), int (*compare)(const void *a, const void *b),
int idx_field_offset,
int idx) int idx)
{ {
while (1) { while (1) {
...@@ -644,21 +686,28 @@ smartlist_heapify(smartlist_t *sl, ...@@ -644,21 +686,28 @@ smartlist_heapify(smartlist_t *sl,
void *tmp = sl->list[idx]; void *tmp = sl->list[idx];
sl->list[idx] = sl->list[best_idx]; sl->list[idx] = sl->list[best_idx];
sl->list[best_idx] = tmp; sl->list[best_idx] = tmp;
UPDATE_IDX(idx);
UPDATE_IDX(best_idx);
idx = best_idx; idx = best_idx;
} }
} }
} }
/** Insert <b>item</b> into the heap stored in <b>sl</b>, where order /** Insert <b>item</b> into the heap stored in <b>sl</b>, where order is
* is determined by <b>compare</b>. */ * determined by <b>compare</b> and the offset of the item in the heap is
* stored in an int-typed field at position <b>idx_field_offset</b> within
* item.
*/
void void
smartlist_pqueue_add(smartlist_t *sl, smartlist_pqueue_add(smartlist_t *sl,
int (*compare)(const void *a, const void *b), int (*compare)(const void *a, const void *b),
int idx_field_offset,
void *item) void *item)
{ {
int idx; int idx;
smartlist_add(sl,item); smartlist_add(sl,item);
UPDATE_IDX(sl->num_used-1);
for (idx = sl->num_used - 1; idx; ) { for (idx = sl->num_used - 1; idx; ) {
int parent = PARENT(idx); int parent = PARENT(idx);
...@@ -666,6 +715,8 @@ smartlist_pqueue_add(smartlist_t *sl, ...@@ -666,6 +715,8 @@ smartlist_pqueue_add(smartlist_t *sl,
void *tmp = sl->list[parent]; void *tmp = sl->list[parent];
sl->list[parent] = sl->list[idx]; sl->list[parent] = sl->list[idx];
sl->list[idx] = tmp; sl->list[idx] = tmp;
UPDATE_IDX(parent);
UPDATE_IDX(idx);
idx = parent; idx = parent;
} else { } else {
return; return;
...@@ -674,32 +725,63 @@ smartlist_pqueue_add(smartlist_t *sl, ...@@ -674,32 +725,63 @@ smartlist_pqueue_add(smartlist_t *sl,
} }
/** Remove and return the top-priority item from the heap stored in <b>sl</b>, /** Remove and return the top-priority item from the heap stored in <b>sl</b>,
* where order is determined by <b>compare</b>. <b>sl</b> must not be * where order is determined by <b>compare</b> and the item's position in is
* empty. */ * stored at position <b>idx_field_offset</b> within the item. <b>sl</b> must
* not be empty. */
void * void *
smartlist_pqueue_pop(smartlist_t *sl, smartlist_pqueue_pop(smartlist_t *sl,
int (*compare)(const void *a, const void *b)) int (*compare)(const void *a, const void *b),
int idx_field_offset)
{ {
void *top; void *top;
tor_assert(sl->num_used); tor_assert(sl->num_used);
top = sl->list[0]; top = sl->list[0];
*IDXP(top)=-1;
if (--sl->num_used) { if (--sl->num_used) {
sl->list[0] = sl->list[sl->num_used]; sl->list[0] = sl->list[sl->num_used];
smartlist_heapify(sl, compare, 0); UPDATE_IDX(0);
smartlist_heapify(sl, compare, idx_field_offset, 0);
} }
return top; return top;
} }
/** Remove the item <b>item</b> from the heap stored in <b>sl</b>,
* where order is determined by <b>compare</b> and the item's position in is
* stored at position <b>idx_field_offset</b> within the item. <b>sl</b> must
* not be empty. */
void
smartlist_pqueue_remove(smartlist_t *sl,
int (*compare)(const void *a, const void *b),
int idx_field_offset,
void *item)
{
int idx = IDX_OF_ITEM(item);
tor_assert(idx >= 0);
tor_assert(sl->list[idx] == item);
--sl->num_used;
*IDXP(item) = -1;
if (idx == sl->num_used) {
return;
} else {
sl->list[idx] = sl->list[sl->num_used];
UPDATE_IDX(idx);
smartlist_heapify(sl, compare, idx_field_offset, idx);
}
}
/** Assert that the heap property is correctly maintained by the heap stored /** Assert that the heap property is correctly maintained by the heap stored
* in <b>sl</b>, where order is determined by <b>compare</b>. */ * in <b>sl</b>, where order is determined by <b>compare</b>. */
void void
smartlist_pqueue_assert_ok(smartlist_t *sl, smartlist_pqueue_assert_ok(smartlist_t *sl,
int (*compare)(const void *a, const void *b)) int (*compare)(const void *a, const void *b),
int idx_field_offset)
{ {
int i; int i;
for (i = sl->num_used - 1; i > 0; --i) { for (i = sl->num_used - 1; i >= 0; --i) {
if (i>0)
tor_assert(compare(sl->list[PARENT(i)], sl->list[i]) <= 0); tor_assert(compare(sl->list[PARENT(i)], sl->list[i]) <= 0);
tor_assert(IDX_OF_ITEM(sl->list[i]) == i);
} }
} }
......
...@@ -118,11 +118,18 @@ int smartlist_bsearch_idx(const smartlist_t *sl, const void *key, ...@@ -118,11 +118,18 @@ int smartlist_bsearch_idx(const smartlist_t *sl, const void *key,
void smartlist_pqueue_add(smartlist_t *sl, void smartlist_pqueue_add(smartlist_t *sl,
int (*compare)(const void *a, const void *b), int (*compare)(const void *a, const void *b),
int idx_field_offset,
void *item); void *item);
void *smartlist_pqueue_pop(smartlist_t *sl, void *smartlist_pqueue_pop(smartlist_t *sl,
int (*compare)(const void *a, const void *b)); int (*compare)(const void *a, const void *b),
int idx_field_offset);
void smartlist_pqueue_remove(smartlist_t *sl,
int (*compare)(const void *a, const void *b),
int idx_field_offset,
void *item);
void smartlist_pqueue_assert_ok(smartlist_t *sl, void smartlist_pqueue_assert_ok(smartlist_t *sl,
int (*compare)(const void *a, const void *b)); int (*compare)(const void *a, const void *b),
int idx_field_offset);
#define SPLIT_SKIP_SPACE 0x01 #define SPLIT_SKIP_SPACE 0x01
#define SPLIT_IGNORE_BLANK 0x02 #define SPLIT_IGNORE_BLANK 0x02
......
...@@ -128,6 +128,8 @@ typedef struct cached_resolve_t { ...@@ -128,6 +128,8 @@ typedef struct cached_resolve_t {
uint32_t ttl; /**< What TTL did the nameserver tell us? */ uint32_t ttl; /**< What TTL did the nameserver tell us? */
/** Connections that want to know when we get an answer for this resolve. */ /** Connections that want to know when we get an answer for this resolve. */
pending_connection_t *pending_connections; pending_connection_t *pending_connections;
/** Position of this element in the heap*/
int minheap_idx;
} cached_resolve_t; } cached_resolve_t;
static void purge_expired_resolves(time_t now); static void purge_expired_resolves(time_t now);
...@@ -344,6 +346,7 @@ set_expiry(cached_resolve_t *resolve, time_t expires) ...@@ -344,6 +346,7 @@ set_expiry(cached_resolve_t *resolve, time_t expires)
resolve->expire = expires; resolve->expire = expires;
smartlist_pqueue_add(cached_resolve_pqueue, smartlist_pqueue_add(cached_resolve_pqueue,
_compare_cached_resolves_by_expiry, _compare_cached_resolves_by_expiry,
STRUCT_OFFSET(cached_resolve_t, minheap_idx),
resolve); resolve);
} }
...@@ -389,7 +392,8 @@ purge_expired_resolves(time_t now) ...@@ -389,7 +392,8 @@ purge_expired_resolves(time_t now)
if (resolve->expire > now) if (resolve->expire > now)
break; break;
smartlist_pqueue_pop(cached_resolve_pqueue, smartlist_pqueue_pop(cached_resolve_pqueue,
_compare_cached_resolves_by_expiry); _compare_cached_resolves_by_expiry,
STRUCT_OFFSET(cached_resolve_t, minheap_idx));
if (resolve->state == CACHE_STATE_PENDING) { if (resolve->state == CACHE_STATE_PENDING) {
log_debug(LD_EXIT, log_debug(LD_EXIT,
...@@ -751,6 +755,7 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve, ...@@ -751,6 +755,7 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve,
resolve = tor_malloc_zero(sizeof(cached_resolve_t)); resolve = tor_malloc_zero(sizeof(cached_resolve_t));
resolve->magic = CACHED_RESOLVE_MAGIC; resolve->magic = CACHED_RESOLVE_MAGIC;
resolve->state = CACHE_STATE_PENDING; resolve->state = CACHE_STATE_PENDING;
resolve->minheap_idx = -1;
resolve->is_reverse = is_reverse; resolve->is_reverse = is_reverse;
strlcpy(resolve->address, exitconn->_base.address, sizeof(resolve->address)); strlcpy(resolve->address, exitconn->_base.address, sizeof(resolve->address));
...@@ -1734,7 +1739,8 @@ _assert_cache_ok(void) ...@@ -1734,7 +1739,8 @@ _assert_cache_ok(void)
return; return;
smartlist_pqueue_assert_ok(cached_resolve_pqueue, smartlist_pqueue_assert_ok(cached_resolve_pqueue,
_compare_cached_resolves_by_expiry); _compare_cached_resolves_by_expiry,
STRUCT_OFFSET(cached_resolve_t, minheap_idx));
SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res, SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res,
{ {
......
...@@ -515,11 +515,17 @@ test_container_digestset(void) ...@@ -515,11 +515,17 @@ test_container_digestset(void)
smartlist_free(included); smartlist_free(included);
} }
/** Helper: return a tristate based on comparing two strings. */ typedef struct pq_entry_t {
const char *val;
int idx;
} pq_entry_t;
/** Helper: return a tristate based on comparing two pq_entry_t values. */
static int static int
_compare_strings_for_pqueue(const void *s1, const void *s2) _compare_strings_for_pqueue(const void *p1, const void *p2)
{ {
return strcmp((const char*)s1, (const char*)s2); const pq_entry_t *e1=p1, *e2=p2;
return strcmp(e1->val, e2->val);
} }
/** Run unit tests for heap-based priority queue functions. */ /** Run unit tests for heap-based priority queue functions. */
...@@ -528,50 +534,90 @@ test_container_pqueue(void) ...@@ -528,50 +534,90 @@ test_container_pqueue(void)
{ {
smartlist_t *sl = smartlist_create(); smartlist_t *sl = smartlist_create();
int (*cmp)(const void *, const void*); int (*cmp)(const void *, const void*);
#define OK() smartlist_pqueue_assert_ok(sl, cmp) const int offset = STRUCT_OFFSET(pq_entry_t, idx);
#define ENTRY(s) pq_entry_t s = { #s, -1 }
ENTRY(cows);
ENTRY(zebras);
ENTRY(fish);
ENTRY(frogs);
ENTRY(apples);
ENTRY(squid);
ENTRY(daschunds);
ENTRY(eggplants);
ENTRY(weissbier);
ENTRY(lobsters);
ENTRY(roquefort);
ENTRY(chinchillas);
ENTRY(fireflies);
#define OK() smartlist_pqueue_assert_ok(sl, cmp, offset)
cmp = _compare_strings_for_pqueue; cmp = _compare_strings_for_pqueue;
smartlist_pqueue_add(sl, cmp, offset, &cows);
smartlist_pqueue_add(sl, cmp, (char*)"cows"); smartlist_pqueue_add(sl, cmp, offset, &zebras);
smartlist_pqueue_add(sl, cmp, (char*)"zebras"); smartlist_pqueue_add(sl, cmp, offset, &fish);
smartlist_pqueue_add(sl, cmp, (char*)"fish"); smartlist_pqueue_add(sl, cmp, offset, &frogs);
smartlist_pqueue_add(sl, cmp, (char*)"frogs"); smartlist_pqueue_add(sl, cmp, offset, &apples);
smartlist_pqueue_add(sl, cmp, (char*)"apples"); smartlist_pqueue_add(sl, cmp, offset, &squid);
smartlist_pqueue_add(sl, cmp, (char*)"squid"); smartlist_pqueue_add(sl, cmp, offset, &daschunds);
smartlist_pqueue_add(sl, cmp, (char*)"daschunds"); smartlist_pqueue_add(sl, cmp, offset, &eggplants);
smartlist_pqueue_add(sl, cmp, (char*)"eggplants"); smartlist_pqueue_add(sl, cmp, offset, &weissbier);
smartlist_pqueue_add(sl, cmp, (char*)"weissbier"); smartlist_pqueue_add(sl, cmp, offset, &lobsters);
smartlist_pqueue_add(sl, cmp, (char*)"lobsters"); smartlist_pqueue_add(sl, cmp, offset, &roquefort);
smartlist_pqueue_add(sl, cmp, (char*)"roquefort");
OK(); OK();
test_eq(smartlist_len(sl), 11); test_eq(smartlist_len(sl), 11);
test_streq(smartlist_get(sl, 0), "apples"); test_eq_ptr(smartlist_get(sl, 0), &apples);
test_streq(smartlist_pqueue_pop(sl, cmp), "apples"); test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &apples);
test_eq(smartlist_len(sl), 10); test_eq(smartlist_len(sl), 10);
OK(); OK();
test_streq(smartlist_pqueue_pop(sl, cmp), "cows"); test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &cows);
test_streq(smartlist_pqueue_pop(sl, cmp), "daschunds"); test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &daschunds);
smartlist_pqueue_add(sl, cmp, (char*)"chinchillas"); smartlist_pqueue_add(sl, cmp, offset, &chinchillas);
OK();
smartlist_pqueue_add(sl, cmp, offset, &fireflies);
OK();
test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &chinchillas);
test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &eggplants);
test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &fireflies);
OK();
test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &fish);
test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &frogs);
test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &lobsters);
test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &roquefort);
OK();
test_eq(smartlist_len(sl), 3);
test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &squid);
test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &weissbier);
test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &zebras);
test_eq(smartlist_len(sl), 0);
OK(); OK();
smartlist_pqueue_add(sl, cmp, (char*)"fireflies");
/* Now test remove. */
smartlist_pqueue_add(sl, cmp, offset, &cows);
smartlist_pqueue_add(sl, cmp, offset, &fish);
smartlist_pqueue_add(sl, cmp, offset, &frogs);
smartlist_pqueue_add(sl, cmp, offset, &apples);
smartlist_pqueue_add(sl, cmp, offset, &squid);
smartlist_pqueue_add(sl, cmp, offset, &zebras);
test_eq(smartlist_len(sl), 6);
OK(); OK();
test_streq(smartlist_pqueue_pop(sl, cmp), "chinchillas"); smartlist_pqueue_remove(sl, cmp, offset, &zebras);
test_streq(smartlist_pqueue_pop(sl, cmp), "eggplants"); test_eq(smartlist_len(sl), 5);
test_streq(smartlist_pqueue_pop(sl, cmp), "fireflies");
OK(); OK();
test_streq(smartlist_pqueue_pop(sl, cmp), "fish"); smartlist_pqueue_remove(sl, cmp, offset, &cows);
test_streq(smartlist_pqueue_pop(sl, cmp), "frogs"); test_eq(smartlist_len(sl), 4);
test_streq(smartlist_pqueue_pop(sl, cmp), "lobsters");
test_streq(smartlist_pqueue_pop(sl, cmp), "roquefort");
OK(); OK();
smartlist_pqueue_remove(sl, cmp, offset, &apples);
test_eq(smartlist_len(sl), 3); test_eq(smartlist_len(sl), 3);
test_streq(smartlist_pqueue_pop(sl, cmp), "squid"); OK();
test_streq(smartlist_pqueue_pop(sl, cmp), "weissbier"); test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &fish);
test_streq(smartlist_pqueue_pop(sl, cmp), "zebras"); test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &frogs);
test_eq_ptr(smartlist_pqueue_pop(sl, cmp, offset), &squid);
test_eq(smartlist_len(sl), 0); test_eq(smartlist_len(sl), 0);
OK(); OK();
#undef OK #undef OK
done: done:
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment