/*- * Copyright 2016 Vsevolod Stakhov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #include "hash.h" #include "util.h" /** * LRU hashing */ static const guint expire_aggressive_count = 10; struct rspamd_lru_hash_s { guint maxsize; GDestroyNotify value_destroy; GDestroyNotify key_destroy; struct rspamd_min_heap *heap; GHashTable *tbl; }; static void rspamd_lru_destroy_node (gpointer value) { rspamd_lru_element_t *elt = (rspamd_lru_element_t *)value; if (elt) { if (elt->hash && elt->hash->key_destroy) { elt->hash->key_destroy (elt->key); } if (elt->hash && elt->hash->value_destroy) { elt->hash->value_destroy (elt->data); } g_slice_free1 (sizeof (*elt), elt); } } static inline guint rspamd_lru_priority (guint ttl, guint usages) { /* 1 day */ static const guint max_ttl = 3600 * 24; if (ttl > 0 && usages > 0) { return G_MAXUINT / (MIN (max_ttl, ttl) * usages); } return G_MAXUINT; } static rspamd_lru_element_t * rspamd_lru_create_node (rspamd_lru_hash_t *hash, gpointer key, gpointer value, time_t now, guint ttl) { rspamd_lru_element_t *node; node = g_slice_alloc (sizeof (rspamd_lru_element_t)); node->data = value; node->key = key; node->ttl = ttl; node->helt.pri = now; node->hash = hash; node->usages = 1; node->storage = now; node->helt.pri = rspamd_lru_priority (ttl, 1); return node; } rspamd_lru_hash_t * rspamd_lru_hash_new_full ( gint maxsize, GDestroyNotify key_destroy, GDestroyNotify value_destroy, GHashFunc hf, GEqualFunc cmpf) { rspamd_lru_hash_t *new; new = g_slice_alloc (sizeof (rspamd_lru_hash_t)); new->tbl = g_hash_table_new_full (hf, cmpf, NULL, rspamd_lru_destroy_node); new->heap = rspamd_min_heap_create (maxsize); new->maxsize = maxsize; new->value_destroy = value_destroy; new->key_destroy = key_destroy; return new; } rspamd_lru_hash_t * rspamd_lru_hash_new ( gint maxsize, GDestroyNotify key_destroy, GDestroyNotify value_destroy) { return rspamd_lru_hash_new_full (maxsize, key_destroy, value_destroy, rspamd_strcase_hash, rspamd_strcase_equal); } gpointer rspamd_lru_hash_lookup (rspamd_lru_hash_t *hash, gconstpointer key, time_t now) { rspamd_lru_element_t *res; res = g_hash_table_lookup (hash->tbl, key); if (res != NULL) { if (res->ttl != 0) { if (((guint)now) - res->storage > res->ttl) { rspamd_min_heap_remove_elt (hash->heap, &res->helt); g_hash_table_remove (hash->tbl, key); return NULL; } } rspamd_min_heap_update_elt (hash->heap, &res->helt, rspamd_lru_priority (res->ttl, ++res->usages)); return res->data; } return NULL; } void rspamd_lru_hash_insert (rspamd_lru_hash_t *hash, gpointer key, gpointer value, time_t now, guint ttl) { rspamd_lru_element_t *res; guint i; res = g_hash_table_lookup (hash->tbl, key); if (res != NULL) { rspamd_min_heap_remove_elt (hash->heap, &res->helt); g_hash_table_remove (hash->tbl, key); } else { if (hash->maxsize > 0 && g_hash_table_size (hash->tbl) >= hash->maxsize) { for (i = 0; i < MIN (hash->maxsize, expire_aggressive_count); i ++) { res = (rspamd_lru_element_t *)rspamd_min_heap_pop (hash->heap); if (res) { g_hash_table_remove (hash->tbl, res->key); } else { break; } } } } res = rspamd_lru_create_node (hash, key, value, now, ttl); g_hash_table_insert (hash->tbl, key, res); rspamd_min_heap_push (hash->heap, &res->helt); } void rspamd_lru_hash_destroy (rspamd_lru_hash_t *hash) { rspamd_min_heap_destroy (hash->heap); g_hash_table_unref (hash->tbl); g_slice_free1 (sizeof (rspamd_lru_hash_t), hash); } GHashTable * rspamd_lru_hash_get_htable (rspamd_lru_hash_t *hash) { return hash->tbl; }