chiark / gitweb /
tree-wide: beautify remaining copyright statements
[elogind.git] / src / basic / hashmap.c
index bcef42e11096af23ac6998beb8c624fcd88f377f..5dff27425585cd8edb1882196e32df4ddb3d47d8 100644 (file)
@@ -1,21 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright 2010 Lennart Poettering
-  Copyright 2014 Michal Schmidt
-
-  systemd 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.
-
-  systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
+  Copyright © 2014 Michal Schmidt
 ***/
 
 #include <errno.h>
 
 #include "alloc-util.h"
 #include "hashmap.h"
+#include "fileio.h"
 #include "macro.h"
 #include "mempool.h"
 #include "process-util.h"
 #include "random-util.h"
 #include "set.h"
 #include "siphash24.h"
+#include "string-util.h"
 #include "strv.h"
 #include "util.h"
 
@@ -226,6 +213,8 @@ struct HashmapBase {
         unsigned n_direct_entries:3; /* Number of entries in direct storage.
                                       * Only valid if !has_indirect. */
         bool from_pool:1;            /* whether was allocated from mempool */
+        bool dirty:1;                /* whether dirtied since last iterated_cache_get() */
+        bool cached:1;               /* whether this hashmap is being cached */
         HASHMAP_DEBUG_FIELDS         /* optional hashmap_debug_info */
 };
 
@@ -245,6 +234,17 @@ struct Set {
         struct HashmapBase b;
 };
 
+typedef struct CacheMem {
+        const void **ptr;
+        size_t n_populated, n_allocated;
+        bool active:1;
+} CacheMem;
+
+struct IteratedCache {
+        HashmapBase *hashmap;
+        CacheMem keys, values;
+};
+
 DEFINE_MEMPOOL(hashmap_pool,         Hashmap,        8);
 DEFINE_MEMPOOL(ordered_hashmap_pool, OrderedHashmap, 8);
 /* No need for a separate Set pool */
@@ -278,6 +278,28 @@ static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = {
         },
 };
 
+#if VALGRIND
+__attribute__((destructor)) static void cleanup_pools(void) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        /* Be nice to valgrind */
+
+        /* The pool is only allocated by the main thread, but the memory can
+         * be passed to other threads. Let's clean up if we are the main thread
+         * and no other threads are live. */
+        if (!is_main_thread())
+                return;
+
+        r = get_proc_field("/proc/self/status", "Threads", WHITESPACE, &t);
+        if (r < 0 || !streq(t, "1"))
+                return;
+
+        mempool_drop(&hashmap_pool);
+        mempool_drop(&ordered_hashmap_pool);
+}
+#endif
+
 static unsigned n_buckets(HashmapBase *h) {
         return h->has_indirect ? h->indirect.n_buckets
                                : hashmap_type_info[h->type].n_direct_buckets;
@@ -326,6 +348,11 @@ static unsigned base_bucket_hash(HashmapBase *h, const void *p) {
 }
 #define bucket_hash(h, p) base_bucket_hash(HASHMAP_BASE(h), p)
 
+static inline void base_set_dirty(HashmapBase *h) {
+        h->dirty = true;
+}
+#define hashmap_set_dirty(h) base_set_dirty(HASHMAP_BASE(h))
+
 static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) {
         static uint8_t current[HASH_KEY_SIZE];
         static bool current_initialized = false;
@@ -543,6 +570,7 @@ static void base_remove_entry(HashmapBase *h, unsigned idx) {
 
         bucket_mark_free(h, prev);
         n_entries_dec(h);
+        base_set_dirty(h);
 }
 #define remove_entry(h, idx) base_remove_entry(HASHMAP_BASE(h), idx)
 
@@ -712,6 +740,25 @@ bool set_iterate(Set *s, Iterator *i, void **value) {
              (idx != IDX_NIL); \
              (idx) = hashmap_iterate_entry((h), &(i)))
 
+IteratedCache *internal_hashmap_iterated_cache_new(HashmapBase *h) {
+        IteratedCache *cache;
+
+        assert(h);
+        assert(!h->cached);
+
+        if (h->cached)
+                return NULL;
+
+        cache = new0(IteratedCache, 1);
+        if (!cache)
+                return NULL;
+
+        cache->hashmap = h;
+        h->cached = true;
+
+        return cache;
+}
+
 static void reset_direct_storage(HashmapBase *h) {
         const struct hashmap_type_info *hi = &hashmap_type_info[h->type];
         void *p;
@@ -872,6 +919,8 @@ void internal_hashmap_clear(HashmapBase *h) {
                 OrderedHashmap *lh = (OrderedHashmap*) h;
                 lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL;
         }
+
+        base_set_dirty(h);
 }
 
 void internal_hashmap_clear_free(HashmapBase *h) {
@@ -1016,6 +1065,8 @@ static int hashmap_base_put_boldly(HashmapBase *h, unsigned idx,
         h->debug.max_entries = MAX(h->debug.max_entries, n_entries(h));
 #endif
 
+        base_set_dirty(h);
+
         return 1;
 }
 #define hashmap_put_boldly(h, idx, swap, may_resize) \
@@ -1252,6 +1303,8 @@ int hashmap_replace(Hashmap *h, const void *key, void *value) {
 #endif
                 e->b.key = key;
                 e->value = value;
+                hashmap_set_dirty(h);
+
                 return 0;
         }
 
@@ -1274,6 +1327,8 @@ int hashmap_update(Hashmap *h, const void *key, void *value) {
 
         e = plain_bucket_at(h, idx);
         e->value = value;
+        hashmap_set_dirty(h);
+
         return 0;
 }
 
@@ -1830,3 +1885,95 @@ int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags
         }
 }
 #endif // 0
+
+/* expand the cachemem if needed, return true if newly (re)activated. */
+static int cachemem_maintain(CacheMem *mem, unsigned size) {
+        assert(mem);
+
+        if (!GREEDY_REALLOC(mem->ptr, mem->n_allocated, size)) {
+                if (size > 0)
+                        return -ENOMEM;
+        }
+
+        if (!mem->active) {
+                mem->active = true;
+                return true;
+        }
+
+        return false;
+}
+
+int iterated_cache_get(IteratedCache *cache, const void ***res_keys, const void ***res_values, unsigned *res_n_entries) {
+        bool sync_keys = false, sync_values = false;
+        unsigned size;
+        int r;
+
+        assert(cache);
+        assert(cache->hashmap);
+
+        size = n_entries(cache->hashmap);
+
+        if (res_keys) {
+                r = cachemem_maintain(&cache->keys, size);
+                if (r < 0)
+                        return r;
+
+                sync_keys = r;
+        } else
+                cache->keys.active = false;
+
+        if (res_values) {
+                r = cachemem_maintain(&cache->values, size);
+                if (r < 0)
+                        return r;
+
+                sync_values = r;
+        } else
+                cache->values.active = false;
+
+        if (cache->hashmap->dirty) {
+                if (cache->keys.active)
+                        sync_keys = true;
+                if (cache->values.active)
+                        sync_values = true;
+
+                cache->hashmap->dirty = false;
+        }
+
+        if (sync_keys || sync_values) {
+                unsigned i, idx;
+                Iterator iter;
+
+                i = 0;
+                HASHMAP_FOREACH_IDX(idx, cache->hashmap, iter) {
+                        struct hashmap_base_entry *e;
+
+                        e = bucket_at(cache->hashmap, idx);
+
+                        if (sync_keys)
+                                cache->keys.ptr[i] = e->key;
+                        if (sync_values)
+                                cache->values.ptr[i] = entry_value(cache->hashmap, e);
+                        i++;
+                }
+        }
+
+        if (res_keys)
+                *res_keys = cache->keys.ptr;
+        if (res_values)
+                *res_values = cache->values.ptr;
+        if (res_n_entries)
+                *res_n_entries = size;
+
+        return 0;
+}
+
+IteratedCache *iterated_cache_free(IteratedCache *cache) {
+        if (cache) {
+                free(cache->keys.ptr);
+                free(cache->values.ptr);
+                free(cache);
+        }
+
+        return NULL;
+}