/** * Remove key from the table, freeing it if we have a key free routine. * * @return whether key was found and subsequently removed. */ bool aging_remove(aging_table_t *ag, const void *key) { struct aging_value *aval; void *ovalue; bool found; aging_check(ag); aging_synchronize(ag); if (!hikset_lookup_extended(ag->table, key, &ovalue)) { found = FALSE; goto done; } aval = ovalue; hikset_remove(ag->table, aval->key); aging_free(aval, ag); found = TRUE; done: aging_return(ag, found); }
/** * Create new aging container, where keys/values expire and need to be freed. * Values are either integers (cast to pointers) or refer to real objects. * * @param delay the aging delay, in seconds, for entries * @param hash the hashing function for the keys in the hash table * @param eq the equality function for the keys in the hash table * @param kvfree the key/value pair freeing callback, NULL if none. * * @return opaque handle to the container. */ aging_table_t * aging_make(int delay, hash_fn_t hash, eq_fn_t eq, free_keyval_fn_t kvfree) { aging_table_t *ag; WALLOC0(ag); ag->magic = AGING_MAGIC; ag->table = hikset_create_any( offsetof(struct aging_value, key), NULL == hash ? pointer_hash : hash, eq); ag->kvfree = kvfree; delay = MAX(delay, 1); delay = MIN(delay, INT_MAX / 1000); ag->delay = delay; elist_init(&ag->list, offsetof(struct aging_value, lk)); /* * If the callout queue does not run in the thread that is creating * this table, then concurrent accesses are bound to happen. * Therefore, make the table thread-safe. */ if (cq_main_thread_id() != thread_small_id()) aging_thread_safe(ag); ag->gc_ev = cq_periodic_main_add(AGING_CALLOUT, aging_gc, ag); aging_check(ag); return ag; }
/* * Release lock on aging table. * * The table must have been marked thread-safe already and locked by the * calling thread. */ void aging_unlock(aging_table_t *ag) { aging_check(ag); g_assert_log(ag->lock != NULL, "%s(): aging table %p not marked thread-safe", G_STRFUNC, ag); mutex_unlock(ag->lock); }
/** * Lookup value in table. */ void * aging_lookup(const aging_table_t *ag, gconstpointer key) { struct aging_value *aval; aging_check(ag); aval = g_hash_table_lookup(ag->table, key); return aval == NULL ? NULL : aval->value; }
/** * Return entry age in seconds, (time_delta_t) -1 if not found. */ time_delta_t aging_age(const aging_table_t *ag, gconstpointer key) { struct aging_value *aval; aging_check(ag); aval = g_hash_table_lookup(ag->table, key); return aval == NULL ? (time_delta_t) -1 : delta_time(tm_time(), aval->last_insert); }
/** * @return amount of entries held in aging table. */ size_t aging_count(const aging_table_t *ag) { size_t count; aging_check(ag); aging_synchronize(ag); count = hikset_count(ag->table); aging_return(ag, count); }
/** * Expire value entry. */ static void aging_expire(cqueue_t *unused_cq, void *obj) { struct aging_value *aval = obj; aging_table_t *ag = aval->ag; (void) unused_cq; aging_check(ag); aval->cq_ev = NULL; g_hash_table_remove(ag->table, aval->key); aging_free_kv(aval->key, aval, ag); }
/** * Lookup value in table. */ void * aging_lookup(const aging_table_t *ag, const void *key) { struct aging_value *aval; void *data; aging_check(ag); aging_synchronize(ag); aval = hikset_lookup(ag->table, key); data = aval == NULL ? NULL : aval->value; aging_return(ag, data); }
/** * Free keys and values from the aging table. */ static void aging_free(void *value, void *data) { struct aging_value *aval = value; aging_table_t *ag = data; aging_check(ag); assert_aging_locked(ag); if (ag->kvfree != NULL) (*ag->kvfree)(aval->key, aval->value); elist_remove(&ag->list, aval); WFREE(aval); }
/** * Create new aging container, where only keys expire and need to be freed. * Values are either integers (cast to pointers) or refer to parts of the keys. * * @param delay the aging delay, in seconds, for entries * @param hash the hashing function for the keys in the hash table * @param eq the equality function for the keys in the hash table * @param kvfree the key/value pair freeing callback, NULL if none. * * @return opaque handle to the container. */ aging_table_t * aging_make(int delay, GHashFunc hash, GEqualFunc eq, aging_free_t kvfree) { aging_table_t *ag; ag_create_callout_queue(); WALLOC(ag); ag->magic = AGING_MAGIC; ag->table = g_hash_table_new(hash, eq); ag->kvfree = kvfree; ag->delay = delay; aging_check(ag); return ag; }
/** * Return entry age in seconds, (time_delta_t) -1 if not found. */ time_delta_t aging_age(const aging_table_t *ag, const void *key) { struct aging_value *aval; time_delta_t age; aging_check(ag); aging_synchronize(ag); aval = hikset_lookup(ag->table, key); age = aval == NULL ? (time_delta_t) -1 : delta_time(tm_time(), aval->last_insert); aging_return(ag, age); }
/** * Free keys and values from the aging table. */ static void aging_free_kv(void *key, void *value, void *udata) { aging_table_t *ag = udata; struct aging_value *aval = value; aging_check(ag); g_assert(aval->ag == ag); g_assert(aval->key == key); if (ag->kvfree != NULL) (*ag->kvfree)(key, aval->value); cq_cancel(&aval->cq_ev); WFREE(aval); }
/** * Destroy container, freeing all keys and values, and nullify pointer. */ void aging_destroy(aging_table_t **ag_ptr) { aging_table_t *ag = *ag_ptr; if (ag) { aging_check(ag); g_hash_table_foreach(ag->table, aging_free_kv, ag); gm_hash_table_destroy_null(&ag->table); ag->magic = 0; WFREE(ag); ag_unref_callout_queue(); *ag_ptr = NULL; } }
/** * Mark newly created aging table as being thread-safe. * * This will make all external operations on the table thread-safe. */ void aging_thread_safe(aging_table_t *ag) { aging_check(ag); /* * Silently do nothing if the aging table was already made thread-safe. * Indeed, this is implicitly done when the callout queue is not running * in the thread that creates the aging table, since then we know that * concurrent calls can happen. */ if (NULL == ag->lock) { WALLOC0(ag->lock); mutex_init(ag->lock); } }
/** * Lookup value in table, and if found, revitalize entry, restoring the * initial lifetime the key/value pair had at insertion time. */ void * aging_lookup_revitalise(const aging_table_t *ag, gconstpointer key) { struct aging_value *aval; aging_check(ag); aval = g_hash_table_lookup(ag->table, key); if (aval != NULL) { g_assert(aval->cq_ev != NULL); aval->last_insert = tm_time(); cq_resched(aval->cq_ev, 1000 * aval->ttl); } return aval == NULL ? NULL : aval->value; }
/** * Add value to the table. * * If it was already present, its lifetime is reset to the aging delay. * * The key argument is freed immediately if there is a free routine for * keys and the key was present in the table. * * The previous value is freed and replaced by the new one if there is * an insertion conflict and the key pointers are different. */ void aging_insert(aging_table_t *ag, const void *key, void *value) { bool found; void *ovalue; time_t now = tm_time(); struct aging_value *aval; aging_check(ag); aging_synchronize(ag); found = hikset_lookup_extended(ag->table, key, &ovalue); if (found) { aval = ovalue; if (ag->kvfree != NULL) { /* * We discard the new and keep the old key instead. * That way, we don't have to update the hash table. */ (*ag->kvfree)(deconstify_pointer(key), aval->value); } /* * Value existed for this key, reset its lifetime by moving the * entry to the tail of the list. */ aval->value = value; aval->last_insert = now; elist_moveto_tail(&ag->list, aval); } else { WALLOC(aval); aval->value = value; aval->key = deconstify_pointer(key); aval->last_insert = now; hikset_insert(ag->table, aval); elist_append(&ag->list, aval); } aging_return_void(ag); }
/** * Lookup value in table, and if found, revitalize entry, restoring the * initial lifetime the key/value pair had at insertion time. */ void * aging_lookup_revitalise(aging_table_t *ag, const void *key) { struct aging_value *aval; void *data; aging_check(ag); aging_synchronize(ag); aval = hikset_lookup(ag->table, key); if (aval != NULL) { aval->last_insert = tm_time(); elist_moveto_tail(&ag->list, aval); } data = NULL == aval ? NULL : aval->value; aging_return(ag, data); }
/** * Periodic garbage collecting routine. */ static bool aging_gc(void *obj) { aging_table_t *ag = obj; time_t now = tm_time(); struct aging_value *aval; aging_check(ag); aging_synchronize(ag); g_assert(elist_count(&ag->list) == hikset_count(ag->table)); while (NULL != (aval = elist_head(&ag->list))) { if (delta_time(now, aval->last_insert) <= ag->delay) break; /* List is sorted, oldest items first */ hikset_remove(ag->table, aval->key); aging_free(aval, ag); } aging_return(ag, TRUE); /* Keep calling */ }
/** * Destroy container, freeing all keys and values, and nullify pointer. */ void aging_destroy(aging_table_t **ag_ptr) { aging_table_t *ag = *ag_ptr; if (ag) { aging_check(ag); aging_synchronize(ag); hikset_foreach(ag->table, aging_free, ag); hikset_free_null(&ag->table); cq_periodic_remove(&ag->gc_ev); if (ag->lock != NULL) { mutex_destroy(ag->lock); WFREE(ag->lock); } ag->magic = 0; WFREE(ag); *ag_ptr = NULL; } }