changeset 945:501f076f2e74 HEAD

Rewrote hash table code, works with less memory now. Also some memory allocation fixes to thread extension code.
author Timo Sirainen <tss@iki.fi>
date Sat, 11 Jan 2003 17:29:46 +0200
parents 4f38538aa4a1
children 3681ff89f0e2
files COPYING src/auth/cookie.c src/auth/userinfo-passwd-file.c src/lib-index/maildir/maildir-sync.c src/lib-storage/mail-thread.c src/lib/hash.c src/lib/hash.h src/login/auth-connection.c src/login/client.c src/master/login-process.c src/master/main.c
diffstat 11 files changed, 430 insertions(+), 269 deletions(-) [+]
line wrap: on
line diff
--- a/COPYING	Sat Jan 11 17:09:35 2003 +0200
+++ b/COPYING	Sat Jan 11 17:29:46 2003 +0200
@@ -5,7 +5,7 @@
 
 src/lib/
   - md5.c : Public Domain
-  - base64.c, mkgmtime.c : BSD-like (read it)
-  - hash.c, primes.c, printf-upper-bound.c, tree.c : LGPL v2
+  - base64.c, utc-mktime.c : BSD-like (read it)
+  - primes.c, printf-upper-bound.c : LGPL v2
 
 src/lib-storage/mail-thread.c : merge sorting code is with MIT license.
--- a/src/auth/cookie.c	Sat Jan 11 17:09:35 2003 +0200
+++ b/src/auth/cookie.c	Sat Jan 11 17:29:46 2003 +0200
@@ -155,7 +155,8 @@
 	oldest_cookie = NULL;
 	next_cookie = &oldest_cookie;
 
-	cookies = hash_create(default_pool, 100, cookie_hash, cookie_cmp);
+	cookies = hash_create(default_pool, default_pool, 100,
+			      cookie_hash, cookie_cmp);
 	to = timeout_add(10000, cookie_timeout, NULL);
 }
 
--- a/src/auth/userinfo-passwd-file.c	Sat Jan 11 17:09:35 2003 +0200
+++ b/src/auth/userinfo-passwd-file.c	Sat Jan 11 17:29:46 2003 +0200
@@ -350,7 +350,8 @@
 	pw->path = p_strdup(pool, path);
 	pw->stamp = st.st_mtime;
 	pw->fd = fd;
-	pw->users = hash_create(pool, 100, str_hash, (HashCompareFunc) strcmp);
+	pw->users = hash_create(default_pool, pool, 100,
+				str_hash, (HashCompareFunc) strcmp);
 
 	passwd_file_parse_file(pw);
 	return pw;
--- a/src/lib-index/maildir/maildir-sync.c	Sat Jan 11 17:09:35 2003 +0200
+++ b/src/lib-index/maildir/maildir-sync.c	Sat Jan 11 17:29:46 2003 +0200
@@ -213,9 +213,9 @@
 		return index_file_set_syscall_error(index, dir, "opendir()");
 
 	count = index->header->messages_count + 16;
-	pool = pool_alloconly_create("Maildir sync", nearest_power(count*30));
-	files = hash_create(pool, index->header->messages_count*2, str_hash,
-			    (HashCompareFunc) strcmp);
+	pool = pool_alloconly_create("Maildir sync", count*20);
+	files = hash_create(default_pool, pool, index->header->messages_count*2,
+			    str_hash, (HashCompareFunc) strcmp);
 
 	while ((d = readdir(dirp)) != NULL) {
 		if (d->d_name[0] == '.')
--- a/src/lib-storage/mail-thread.c	Sat Jan 11 17:09:35 2003 +0200
+++ b/src/lib-storage/mail-thread.c	Sat Jan 11 17:29:46 2003 +0200
@@ -67,7 +67,7 @@
 
 struct mail_thread_context {
 	pool_t pool;
-	pool_t str_pool; /* for node->msgid and root_info->base_subject */
+	pool_t temp_pool;
 
 	struct hash_table *msgid_hash;
 	struct hash_table *subject_hash;
@@ -94,12 +94,11 @@
 
 	ctx = p_new(pool, struct mail_thread_context, 1);
 	ctx->pool = pool;
-	ctx->str_pool =
-		pool_alloconly_create("mail_thread_context strings",
-				      sizeof(struct node) *
-				      APPROX_MSG_COUNT * APPROX_MSGID_SIZE);
-	ctx->msgid_hash = hash_create(default_pool,
-				      APPROX_MSGID_SIZE*2, str_hash,
+	ctx->temp_pool = pool_alloconly_create("mail_thread_context temp",
+					       APPROX_MSG_COUNT *
+					       APPROX_MSGID_SIZE);
+	ctx->msgid_hash = hash_create(default_pool, ctx->temp_pool,
+				      APPROX_MSG_COUNT*2, str_hash,
 				      (HashCompareFunc)strcmp);
 	ctx->callbacks = callbacks;
 	ctx->callback_context = callback_context;
@@ -113,7 +112,8 @@
 		hash_destroy(ctx->msgid_hash);
 	if (ctx->subject_hash != NULL)
 		hash_destroy(ctx->subject_hash);
-	pool_unref(ctx->str_pool);
+
+	pool_unref(ctx->temp_pool);
 	pool_unref(ctx->pool);
 }
 
@@ -132,7 +132,7 @@
 	struct node *node;
 
 	node = p_new(ctx->pool, struct node, 1);
-	node->u.msgid = p_strdup(ctx->str_pool, msgid);
+	node->u.msgid = p_strdup(ctx->temp_pool, msgid);
 
 	hash_insert(ctx->msgid_hash, node->u.msgid, node);
 	return node;
@@ -558,7 +558,7 @@
 		return;
 
 	if (!hash_lookup_full(ctx->subject_hash, subject, &key, &value)) {
-		hash_subject = p_strdup(ctx->str_pool, subject);
+		hash_subject = p_strdup(ctx->temp_pool, subject);
 		hash_insert(ctx->subject_hash, hash_subject, node);
 	} else {
 		hash_subject = key;
@@ -582,9 +582,8 @@
 
 	cb = ctx->callbacks;
 	ctx->subject_hash =
-		hash_create(default_pool, ctx->root_count * 2, str_hash,
-			    (HashCompareFunc)strcmp);
-
+		hash_create(default_pool, ctx->temp_pool, ctx->root_count * 2,
+			    str_hash, (HashCompareFunc)strcmp);
 	for (node = ctx->root_nodes; node != NULL; node = node->next) {
 		if (!NODE_IS_DUMMY(node))
 			id = node->id;
@@ -834,17 +833,19 @@
 {
 	struct node *node;
 
-	/* drop the memory allocated for message-IDs, reuse their memory
-	   for base subjects */
-	p_clear(ctx->str_pool);
-
 	/* (2) save root nodes and drop the msgids */
 	hash_foreach(ctx->msgid_hash, save_root_cb, ctx);
+
+	/* drop the memory allocated for message-IDs and msgid_hash,
+	   reuse their memory for base subjects */
 	hash_destroy(ctx->msgid_hash);
 	ctx->msgid_hash = NULL;
 
+	p_clear(ctx->temp_pool);
+
 	if (ctx->root_nodes == NULL) {
 		/* no messages */
+		mail_thread_deinit(ctx);
 		return;
 	}
 
--- a/src/lib/hash.c	Sat Jan 11 17:09:35 2003 +0200
+++ b/src/lib/hash.c	Sat Jan 11 17:29:46 2003 +0200
@@ -1,30 +1,27 @@
-/* GLIB - Library of useful routines for C programming
- * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- */
+/*
+    Copyright (c) 2003 Timo Sirainen
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
 
-/*
- * Modified by the GLib Team and others 1997-1999.  See the AUTHORS
- * file for a list of people on the GLib Team.  See the ChangeLog
- * files for a list of changes.  These files are distributed with
- * GLib at ftp://ftp.gtk.org/pub/gtk/. 
- */
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
 
-/* several modifications Copyright (C) 2002 by Timo Sirainen */
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/* @UNSAFE: whole file */
 
 #include "lib.h"
 #include "hash.h"
@@ -32,143 +29,180 @@
 
 #include <ctype.h>
 
-#define HASH_TABLE_MIN_SIZE 11
-#define HASH_TABLE_MAX_SIZE 13845163
+#define HASH_TABLE_MIN_SIZE 109
+#define COLLISIONS_MIN_SIZE 37
 
 struct hash_node {
 	void *key;
 	void *value;
+};
 
-	int destroyed;
-	struct hash_node *next;
+struct collision_node {
+	struct hash_node node;
+	struct collision_node *next;
 };
 
 struct hash_table {
-	pool_t pool;
+	pool_t table_pool, node_pool;
+
+	int frozen;
+	size_t nodes_count, removed_count;
+#ifdef DEBUG
+	size_t collisions_count;
+#endif
 
-	unsigned int size;
-	unsigned int nodes_count, nodes_destroyed;
-	int frozen;
-	struct hash_node **nodes;
+	size_t size;
+	struct hash_node *nodes;
+
+	size_t collisions_size;
+	struct collision_node *collisions;
+
+	struct collision_node *free_cnodes;
 
 	HashFunc hash_func;
 	HashCompareFunc key_compare_func;
 };
 
-static void hash_cleanup(struct hash_table *table);
 static int hash_resize(struct hash_table *table);
 
 static int foreach_stop;
 
+static int direct_cmp(const void *p1, const void *p2)
+{
+	return p1 == p2 ? 0 : 1;
+}
+
 static unsigned int direct_hash(const void *p)
 {
 	/* NOTE: may truncate the value, but that doesn't matter. */
 	return POINTER_CAST_TO(p, unsigned int);
 }
 
-static struct hash_node *hash_node_create(pool_t pool, void *key, void *value)
-{
-	struct hash_node *node;
-
-        node = p_new(pool, struct hash_node, 1);
-	node->key = key;
-	node->value = value;
-
-	return node;
-}
-
-static void hash_nodes_destroy(struct hash_table *table, struct hash_node *node)
-{
-	struct hash_node *next;
-
-	while (node != NULL) {
-		next = node->next;
-                p_free(table->pool, node);
-                node = next;
-	}
-}
-
-struct hash_table *hash_create(pool_t pool, unsigned int initial_size,
-			       HashFunc hash_func,
+struct hash_table *hash_create(pool_t table_pool, pool_t node_pool,
+			       size_t initial_size, HashFunc hash_func,
 			       HashCompareFunc key_compare_func)
 {
 	struct hash_table *table;
 
-        i_assert(pool != NULL);
-
-	table = p_new(pool, struct hash_table, 1);
-        table->pool = pool;
-	table->size = CLAMP(primes_closest(initial_size),
-			    HASH_TABLE_MIN_SIZE,
-			    HASH_TABLE_MAX_SIZE);
+	table = p_new(node_pool, struct hash_table, 1);
+        table->table_pool = table_pool;
+        table->node_pool = node_pool;
+	table->size = I_MAX(primes_closest(initial_size),
+			    HASH_TABLE_MIN_SIZE);
 
 	table->hash_func = hash_func != NULL ? hash_func : direct_hash;
-	table->key_compare_func = key_compare_func;
-	table->nodes = p_new(pool, struct hash_node *, table->size);
+	table->key_compare_func = key_compare_func == NULL ?
+		direct_cmp : key_compare_func;
+	table->nodes = p_new(table_pool, struct hash_node, table->size);
+	table->collisions_size = I_MAX(table->size / 10, COLLISIONS_MIN_SIZE);
+	table->collisions = p_new(table_pool, struct collision_node,
+				  table->collisions_size);
 
 	return table;
 }
 
+static void free_cnode(struct hash_table *table, struct collision_node *cnode)
+{
+	if (!table->node_pool->alloconly_pool)
+		p_free(table->node_pool, cnode);
+	else {
+		cnode->next = table->free_cnodes;
+		table->free_cnodes = cnode;
+	}
+}
+
+static void destroy_collision(struct hash_table *table,
+			      struct collision_node *cnode)
+{
+	struct collision_node *next;
+
+	while (cnode != NULL) {
+		next = cnode->next;
+		p_free(table->node_pool, cnode);
+		cnode = next;
+	}
+}
+
+static void hash_destroy_collision_nodes(struct hash_table *table)
+{
+	size_t i;
+
+	for (i = 0; i < table->collisions_size; i++) {
+		if (table->collisions[i].next != NULL)
+			destroy_collision(table, table->collisions[i].next);
+	}
+}
+
 void hash_destroy(struct hash_table *table)
 {
-	unsigned int i;
-
-	if (table == NULL)
-                return;
+	if (!table->node_pool->alloconly_pool) {
+		hash_destroy_collision_nodes(table);
+		destroy_collision(table, table->free_cnodes);
+	}
 
-	for (i = 0; i < table->size; i++)
-		hash_nodes_destroy(table, table->nodes[i]);
-
-	p_free(table->pool, table->nodes);
-	p_free(table->pool, table);
+	p_free(table->table_pool, table->nodes);
+	p_free(table->table_pool, table->collisions);
+	p_free(table->node_pool, table);
 }
 
 void hash_clear(struct hash_table *table)
 {
-	unsigned int i;
-
-	i_assert(table != NULL);
+	if (!table->node_pool->alloconly_pool)
+		hash_destroy_collision_nodes(table);
 
-	for (i = 0; i < table->size; i++) {
-		hash_nodes_destroy(table, table->nodes[i]);
-		table->nodes[i] = NULL;
-	}
+	memset(table->nodes, 0, sizeof(struct hash_node) * table->size);
+	memset(table->collisions, 0,
+	       sizeof(struct collision_node) * table->collisions_size);
+
+	table->nodes_count = 0;
+	table->removed_count = 0;
+#ifdef DEBUG
+	table->collisions_count = 0;
+#endif
 }
 
-static inline struct hash_node **
-hash_lookup_node(struct hash_table *table, const void *key)
+static struct hash_node *
+hash_lookup_collision(struct hash_table *table,
+		      const void *key, unsigned int hash)
 {
-	struct hash_node **node;
+	struct collision_node *cnode;
 
-	node = &table->nodes[table->hash_func(key) % table->size];
+	cnode = &table->collisions[hash % table->collisions_size];
+	do {
+		if (cnode->node.key != NULL) {
+			if (table->key_compare_func(cnode->node.key, key) == 0)
+				return &cnode->node;
+		}
+		cnode = cnode->next;
+	} while (cnode != NULL);
 
-	/* Hash table lookup needs to be fast.
-	   We therefore remove the extra conditional of testing
-	   whether to call the key_compare_func or not from
-	   the inner loop. */
-	if (table->key_compare_func) {
-		while (*node != NULL) {
-                        if (!(*node)->destroyed &&
-			    table->key_compare_func((*node)->key, key) == 0)
-                                break;
-			node = &(*node)->next;
-		}
+	return NULL;
+}
+
+static struct hash_node *
+hash_lookup_node(struct hash_table *table, const void *key, unsigned int hash)
+{
+	struct hash_node *node;
+
+	node = &table->nodes[hash % table->size];
+
+	if (node->key == NULL) {
+		if (table->removed_count == 0)
+			return NULL;
 	} else {
-		while (*node != NULL && (*node)->key != key)
-			node = &(*node)->next;
+		if (table->key_compare_func(node->key, key) == 0)
+			return node;
 	}
 
-	return node;
+	return hash_lookup_collision(table, key, hash);
 }
 
 void *hash_lookup(struct hash_table *table, const void *key)
 {
 	struct hash_node *node;
 
-	i_assert(table != NULL);
-
-	node = *hash_lookup_node(table, key);
-	return node != NULL && !node->destroyed ? node->value : NULL;
+	node = hash_lookup_node(table, key, table->hash_func(key));
+	return node != NULL ? node->value : NULL;
 }
 
 int hash_lookup_full(struct hash_table *table, const void *lookup_key,
@@ -176,10 +210,9 @@
 {
 	struct hash_node *node;
 
-	i_assert(table != NULL);
-
-	node = *hash_lookup_node(table, lookup_key);
-	if (node == NULL || node->destroyed)
+	node = hash_lookup_node(table, lookup_key,
+				table->hash_func(lookup_key));
+	if (node == NULL)
 		return FALSE;
 
 	if (orig_key != NULL)
@@ -189,104 +222,227 @@
 	return TRUE;
 }
 
-static void hash_insert_full(struct hash_table *table, void *key, void *value,
-			     int replace_key)
+static struct hash_node *
+hash_insert_node(struct hash_table *table, void *key, void *value,
+		 int check_existing)
 {
-	struct hash_node **node;
+	struct hash_node *node;
+	struct collision_node *cnode, *prev;
+	unsigned int hash;
+
+	i_assert(key != NULL);
+
+	hash = table->hash_func(key);
 
-	i_assert(table != NULL);
+	if (check_existing && table->removed_count > 0) {
+		/* there may be holes, have to check everything */
+		node = hash_lookup_node(table, key, hash);
+		if (node != NULL)
+			return node;
+
+                check_existing = FALSE;
+	}
 
-	node = hash_lookup_node(table, key);
-	if (*node == NULL) {
-		*node = hash_node_create(table->pool, key, value);
-
+	/* a) primary hash */
+	node = &table->nodes[hash % table->size];
+	if (node->key == NULL) {
 		table->nodes_count++;
-		if (!table->frozen)
-			hash_resize(table);
-	} else {
-		if (replace_key || (*node)->destroyed) {
-			(*node)->key = key;
-			(*node)->destroyed = FALSE;
+
+		node->key = key;
+		node->value = value;
+		return node;
+	}
+
+	if (check_existing) {
+		if (table->key_compare_func(node->key, key) == 0)
+			return node;
+	}
+
+	/* b) collisions list */
+	prev = NULL;
+	cnode = &table->collisions[hash % table->collisions_size];
+
+	do {
+		if (cnode->node.key == NULL)
+			break;
+
+		if (check_existing) {
+			if (table->key_compare_func(cnode->node.key, key) == 0)
+				return node;
 		}
 
-		(*node)->value = value;
+		prev = cnode;
+		cnode = cnode->next;
+	} while (cnode != NULL);
+
+	if (cnode == NULL) {
+		if (table->frozen == 0 && hash_resize(table)) {
+			/* resized table, try again */
+			return hash_insert_node(table, key, value, FALSE);
+		}
+
+		if (table->free_cnodes == NULL) {
+			cnode = p_new(table->node_pool,
+				      struct collision_node, 1);
+		} else {
+			cnode = table->free_cnodes;
+			table->free_cnodes = cnode->next;
+			cnode->next = NULL;
+		}
+		prev->next = cnode;
 	}
+
+	cnode->node.key = key;
+	cnode->node.value = value;;
+
+#ifdef DEBUG
+	table->collisions_count++;
+#endif
+	table->nodes_count++;
+	return &cnode->node;
 }
 
 void hash_insert(struct hash_table *table, void *key, void *value)
 {
-	hash_insert_full(table, key, value, TRUE);
+	struct hash_node *node;
+
+	node = hash_insert_node(table, key, value, TRUE);
+	node->key = key;
 }
 
 void hash_update(struct hash_table *table, void *key, void *value)
 {
-	hash_insert_full(table, key, value, FALSE);
+	(void)hash_insert_node(table, key, value, TRUE);
+}
+
+static void hash_compress(struct hash_table *table,
+			  unsigned int collision_idx,
+			  unsigned int hash)
+{
+	struct collision_node *croot, *cnode, *next;
+	struct hash_node *node;
+
+	/* remove deleted nodes from the list */
+	croot = cnode = &table->collisions[collision_idx];
+	while (cnode->next != NULL) {
+		next = cnode->next;
+
+		if (next->node.key == NULL) {
+			cnode->next = next->next;
+			free_cnode(table, next);
+#ifdef DEBUG
+			table->collisions_count--;
+#endif
+		}
+	}
+
+	do {
+		/* if root is marked as deleted, replace it with first node
+		   from list */
+		if (croot->node.key == NULL) {
+			next = croot->next;
+			if (next == NULL) {
+				/* no collisions left - nothing to do */
+				return;
+			}
+
+			memcpy(&croot->node, &next->node, sizeof(croot->node));
+			croot->next = next->next;
+			free_cnode(table, next);
+#ifdef DEBUG
+			table->collisions_count--;
+#endif
+		}
+
+		/* see if node in primary table was deleted */
+		if (hash == 0)
+			hash = table->hash_func(croot->node.key);
+		node = &table->nodes[hash % table->size];
+		if (node->key == NULL) {
+			memcpy(node, &croot->node, sizeof(*node));
+			croot->node.key = NULL;
+#ifdef DEBUG
+			table->collisions_count--;
+#endif
+		}
+	} while (croot->node.key == NULL);
+}
+
+static void hash_compress_collisions(struct hash_table *table)
+{
+	struct collision_node *cnode;
+	size_t i;
+
+	for (i = 0; i < table->collisions_size; i++) {
+		cnode = &table->collisions[i];
+
+		if (cnode->node.key != NULL || cnode->next != NULL)
+			hash_compress(table, i, 0);
+	}
 }
 
 void hash_remove(struct hash_table *table, const void *key)
 {
-	struct hash_node **node, *old_node;
-
-	i_assert(table != NULL);
+	struct hash_node *node;
+	unsigned int hash;
 
-	node = hash_lookup_node(table, key);
-	if (*node != NULL && !(*node)->destroyed) {
-		table->nodes_count--;
+	hash = table->hash_func(key);
 
-		if (table->frozen) {
-			(*node)->destroyed = TRUE;
-                        table->nodes_destroyed++;
-		} else {
-			old_node = *node;
-			*node = old_node->next;
-			p_free(table->pool, old_node);
+	node = hash_lookup_node(table, key, hash);
+	node->key = NULL;
 
-			hash_resize(table);
-		}
-	}
+	if (table->frozen != 0)
+		table->removed_count++;
+	else if (!hash_resize(table))
+		hash_compress(table, hash % table->collisions_size, hash);
 }
 
-void hash_freeze(struct hash_table *table)
+size_t hash_size(struct hash_table *table)
 {
-	i_assert(table != NULL);
-
-	table->frozen++;
-}
-
-void hash_thaw(struct hash_table *table)
-{
-	i_assert(table != NULL);
-	i_assert(table->frozen > 0);
-
-	if (--table->frozen == 0)
-                hash_cleanup(table);
+	return table->nodes_count;
 }
 
 void hash_foreach(struct hash_table *table, HashForeachFunc func, void *context)
 {
 	struct hash_node *node;
-	unsigned int i;
-
-	i_assert(table != NULL);
-	i_assert(func != NULL);
+	struct collision_node *cnode;
+	size_t i;
 
 	hash_freeze(table);
 
-        foreach_stop = FALSE;
+	foreach_stop = FALSE;
+
 	for (i = 0; i < table->size; i++) {
-		for (node = table->nodes[i]; node; node = node->next) {
-			if (!node->destroyed) {
-				func(node->key, node->value, context);
+		node = &table->nodes[i];
 
-				if (foreach_stop) {
-					foreach_stop = FALSE;
-					hash_thaw(table);
-                                        return;
-				}
+		if (node->key != NULL) {
+			func(node->key, node->value, context);
+			if (foreach_stop) {
+				table->frozen--;
+				return;
 			}
 		}
 	}
-        hash_thaw(table);
+
+	if (!foreach_stop) {
+		for (i = 0; i < table->collisions_size; i++) {
+			cnode = &table->collisions[i];
+
+			do {
+				if (cnode->node.key != NULL) {
+					func(cnode->node.key, cnode->node.value,
+					     context);
+					if (foreach_stop) {
+						table->frozen--;
+						return;
+					}
+				}
+				cnode = cnode->next;
+			} while (cnode != NULL);
+		}
+	}
+
+	hash_thaw(table);
 }
 
 void hash_foreach_stop(void)
@@ -294,85 +450,85 @@
         foreach_stop = TRUE;
 }
 
-/* Returns the number of elements contained in the hash table. */
-unsigned int hash_size(struct hash_table *table)
+void hash_freeze(struct hash_table *table)
+{
+	table->frozen++;
+}
+
+void hash_thaw(struct hash_table *table)
 {
-	i_assert(table != NULL);
+	i_assert(table->frozen > 0);
+	if (--table->frozen > 0)
+		return;
 
-	return table->nodes_count;
+	if (table->removed_count > 0) {
+		if (!hash_resize(table))
+			hash_compress_collisions(table);
+	}
 }
 
 static int hash_resize(struct hash_table *table)
 {
-        HashFunc hash_func;
-	struct hash_node *node, *next, **new_nodes;
+	struct hash_node *old_nodes;
+	struct collision_node *old_cnodes, *cnode;
+	size_t old_size, old_csize, i;
+
 	float nodes_per_list;
-	unsigned int hash_val, new_size, i;
 
-	nodes_per_list = (float) table->nodes_count / (float) table->size;
-	if ((nodes_per_list > 0.3 || table->size <= HASH_TABLE_MIN_SIZE) &&
-	    (nodes_per_list < 3.0 || table->size >= HASH_TABLE_MAX_SIZE))
-		return FALSE;
+        nodes_per_list = (float) table->nodes_count / (float) table->size;
+        if ((nodes_per_list > 0.3 || table->size <= HASH_TABLE_MIN_SIZE) &&
+            (nodes_per_list < 2.0))
+                return FALSE;
 
-	new_size = CLAMP(primes_closest(table->nodes_count+1),
-			 HASH_TABLE_MIN_SIZE,
-			 HASH_TABLE_MAX_SIZE);
+	/* recreate primary table */
+	old_size = table->size;
+	old_nodes = table->nodes;
+
+	table->size = I_MAX(primes_closest(table->nodes_count+1),
+			    HASH_TABLE_MIN_SIZE);
+	table->nodes = p_new(table->table_pool, struct hash_node, table->size);
 
-	new_nodes = p_new(table->pool, struct hash_node *, new_size);
+	/* recreate collisions table */
+	old_csize = table->collisions_size;
+	old_cnodes = table->collisions;
 
-        hash_func = table->hash_func;
-	for (i = 0; i < table->size; i++) {
-		for (node = table->nodes[i]; node != NULL; node = next) {
-			next = node->next;
+	table->collisions_size = I_MAX(table->size / 10, COLLISIONS_MIN_SIZE);
+	table->collisions = p_new(table->table_pool, struct collision_node,
+				  table->collisions_size);
 
-			if (node->destroyed) {
-                                p_free(table->pool, node);
-			} else {
-				hash_val = hash_func(node->key) % new_size;
+	table->nodes_count = 0;
+	table->removed_count = 0;
+#ifdef DEBUG
+	table->collisions_count = 0;
+#endif
 
-				node->next = new_nodes[hash_val];
-				new_nodes[hash_val] = node;
-			}
+	table->frozen++;
+
+	/* move the data */
+	for (i = 0; i < old_size; i++) {
+		if (old_nodes[i].key != NULL) {
+			hash_insert_node(table, old_nodes[i].key,
+					 old_nodes[i].value, FALSE);
 		}
 	}
 
-	p_free(table->pool, table->nodes);
-	table->nodes = new_nodes;
-	table->size = new_size;
-	table->nodes_destroyed = 0;
-        return TRUE;
-}
-
-static void hash_cleanup(struct hash_table *table)
-{
-	struct hash_node **node, **next, *old_node;
-        unsigned int i;
-
-	if (hash_resize(table))
-		return;
-
-	if (table->nodes_destroyed == 0)
-                return;
+	for (i = 0; i < old_csize; i++) {
+		cnode = &old_cnodes[i];
 
-        /* find the destroyed nodes from hash table and remove them */
-	for (i = 0; i < table->size; i++) {
-		for (node = &table->nodes[i]; *node != NULL; node = next) {
-			next = &(*node)->next;
-
-			if ((*node)->destroyed) {
-                                old_node = *node;
-				*node = *next;
-				p_free(table->pool, old_node);
+		do {
+			if (cnode->node.key != NULL) {
+				hash_insert_node(table, cnode->node.key,
+						 cnode->node.value, FALSE);
+			}
+			cnode = cnode->next;
+		} while (cnode != NULL);
+	}
 
-				/* next points to free'd memory area now,
-				   fix it */
-				next = node;
+	table->frozen--;
 
-				if (--table->nodes_destroyed == 0)
-                                        return;
-			}
-		}
-	}
+	p_free(table->table_pool, old_nodes);
+	p_free(table->table_pool, old_cnodes);
+	return TRUE;
 }
 
 /* a char* hash function from ASU -- from glib */
--- a/src/lib/hash.h	Sat Jan 11 17:09:35 2003 +0200
+++ b/src/lib/hash.h	Sat Jan 11 17:29:46 2003 +0200
@@ -9,18 +9,15 @@
 
 /* Create a new hash table. If initial_size is 0, the default value is used.
    If hash_func or key_compare_func is NULL, direct hashing/comparing
-   is used. */
-struct hash_table *hash_create(pool_t pool, unsigned int initial_size,
-			       HashFunc hash_func,
+   is used.
+
+   table_pool is used to allocate/free large hash tables, node_pool is used
+   for smaller allocations and can also be alloconly pool. The pools must not
+   be free'd before hash_destroy() is called. */
+struct hash_table *hash_create(pool_t table_pool, pool_t node_pool,
+			       size_t initial_size, HashFunc hash_func,
 			       HashCompareFunc key_compare_func);
 void hash_destroy(struct hash_table *table);
-
-#ifdef POOL_CHECK_LEAKS
-#  define hash_destroy_clean(table) hash_destroy(table)
-#else
-#  define hash_destroy_clean(table)
-#endif
-
 void hash_clear(struct hash_table *table);
 
 void *hash_lookup(struct hash_table *table, const void *key);
@@ -33,7 +30,7 @@
 void hash_update(struct hash_table *table, void *key, void *value);
 
 void hash_remove(struct hash_table *table, const void *key);
-unsigned int hash_size(struct hash_table *table);
+size_t hash_size(struct hash_table *table);
 
 /* Calls the given function for each node in hash table. You may safely
    call hash_*() functions inside your function, but if you add any
--- a/src/login/auth-connection.c	Sat Jan 11 17:09:35 2003 +0200
+++ b/src/login/auth-connection.c	Sat Jan 11 17:29:46 2003 +0200
@@ -80,7 +80,8 @@
 					   FALSE);
 	conn->output = o_stream_create_file(fd, default_pool, MAX_OUTBUF_SIZE,
 					    IO_PRIORITY_DEFAULT, FALSE);
-	conn->requests = hash_create(default_pool, 100, NULL, NULL);
+	conn->requests = hash_create(default_pool, default_pool, 100,
+				     NULL, NULL);
 
 	conn->next = auth_connections;
 	auth_connections = conn;
@@ -300,6 +301,10 @@
 	request->mech = mech;
 	request->conn = conn;
 	request->id = ++request_id_counter;
+	if (request->id == 0) {
+		/* wrapped - ID 0 not allowed */
+		request->id = ++request_id_counter;
+	}
 	request->callback = callback;
 	request->context = context;
 
--- a/src/login/client.c	Sat Jan 11 17:09:35 2003 +0200
+++ b/src/login/client.c	Sat Jan 11 17:29:46 2003 +0200
@@ -436,7 +436,7 @@
 
 void clients_init(void)
 {
-	clients = hash_create(default_pool, 128, NULL, NULL);
+	clients = hash_create(default_pool, default_pool, 128, NULL, NULL);
 	to_idle = timeout_add(1000, idle_timeout, NULL);
 }
 
--- a/src/master/login-process.c	Sat Jan 11 17:09:35 2003 +0200
+++ b/src/master/login-process.c	Sat Jan 11 17:29:46 2003 +0200
@@ -423,7 +423,7 @@
         wanted_processes_count = 0;
 	oldest_nonlisten_process = newest_nonlisten_process = NULL;
 
-	processes = hash_create(default_pool, 128, NULL, NULL);
+	processes = hash_create(default_pool, default_pool, 128, NULL, NULL);
 	to = timeout_add(1000, login_processes_start_missing, NULL);
 }
 
--- a/src/master/main.c	Sat Jan 11 17:09:35 2003 +0200
+++ b/src/master/main.c	Sat Jan 11 17:29:46 2003 +0200
@@ -246,7 +246,7 @@
 
 	lib_init_signals(sig_quit);
 
-	pids = hash_create(default_pool, 128, NULL, NULL);
+	pids = hash_create(default_pool, default_pool, 128, NULL, NULL);
 	to = timeout_add(100, timeout_handler, NULL);
 
 	ssl_init();