From 63ec3f987e66d9af87cc3d125a631b765806ffcf Mon Sep 17 00:00:00 2001
From: Marek Vavrusa <marek.vavrusa@nic.cz>
Date: Tue, 19 Mar 2013 12:52:58 +0100
Subject: [PATCH] Implemented proper locking and lookup using
 <cls,imputed(qname),netblock>.

Updated documentation and corrected default settings.
---
 doc/reference.texi         |   6 +-
 samples/knot.full.conf     |   8 +-
 src/knot/conf/conf.h       |   2 +-
 src/knot/server/rrl.c      | 246 ++++++++++++++++++++++---------------
 src/knot/server/rrl.h      |  44 +++++--
 src/tests/knot/rrl_tests.c | 107 +++++++++++-----
 6 files changed, 267 insertions(+), 146 deletions(-)

diff --git a/doc/reference.texi b/doc/reference.texi
index 2d9ac7e466..f0ec122bb7 100644
--- a/doc/reference.texi
+++ b/doc/reference.texi
@@ -201,10 +201,12 @@ Default value: @kbd{0 (disabled)}
 @vindex rate-limit-size
 
 Option controls the size of a hashtable of buckets. The larger the hashtable, the lesser probability of a hash collision, but
-at the expense of additional memory costs. Each bucket is estimated roughly to 16B.
+at the expense of additional memory costs. Each bucket is estimated roughly to 32B.
 Size should be selected as a reasonably large prime due to the better hash function distribution properties.
+Hash table is internally chained and works well up to a fill rate of 90%, general rule of thumb is to select a prime near
+@kbd{1.2 * maximum_qps}.
 
-Default value: @kbd{1572869}
+Default value: @kbd{393241}
 
 @node rate-limit-slip
 @subsubsection rate-limit-slip
diff --git a/samples/knot.full.conf b/samples/knot.full.conf
index f6a9d89aff..8c02d3c11f 100644
--- a/samples/knot.full.conf
+++ b/samples/knot.full.conf
@@ -70,10 +70,10 @@ system {
   # Number of hashtable buckets, set to reasonable value as default.
   # We chose a reasonably large prime number as it's used for hashtable size,
   # it is recommended to do so as well due to better distribution.
-  # Tweak if you experience a lot of hash collisions, estimated memory overhead
-  # is approx. 16B per bucket
-  # Default: 1572869 
-  rate-limit-size 1572869;
+  # Rule of thumb is to set it to about 1.2 * (maximum_qps) 
+  # Memory cost is approx. 32B per bucket
+  # Default: 393241 
+  rate-limit-size 393241;
 
   # Rate limit SLIP 
   # Each Nth blocked response will be sent as truncated, this is a way to allow
diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h
index f56d92d7e9..380d9dfaab 100644
--- a/src/knot/conf/conf.h
+++ b/src/knot/conf/conf.h
@@ -51,7 +51,7 @@
 #define CONFIG_HANDSHAKE_WD 10 /*!< [secs] for connection to make a request.*/
 #define CONFIG_IDLE_WD  60 /*!< [secs] of allowed inactivity between requests */
 #define CONFIG_RRL_SLIP 2 /*!< Default slip value. */
-#define CONFIG_RRL_SIZE 1572869 /*!< Htable default size. */
+#define CONFIG_RRL_SIZE 393241 /*!< Htable default size. */
 
 /*!
  * \brief Configuration for the interface
diff --git a/src/knot/server/rrl.c b/src/knot/server/rrl.c
index d9133cfda4..a106cf53c0 100644
--- a/src/knot/server/rrl.c
+++ b/src/knot/server/rrl.c
@@ -43,21 +43,6 @@
 /* Enable RRL logging. */
 #define RRL_ENABLE_LOG
 
-/* RRL granular locking. */
-static int rrl_lock_mx(rrl_table_t *t, int lk_id)
-{
-	assert(lk_id > -1);
-	dbg_rrl_verb("%s: locking id '%d'\n", __func__, lk_id);
-	return pthread_mutex_lock(&t->lk[lk_id].mx);
-}
-
-static int rrl_unlock_mx(rrl_table_t *t, int lk_id)
-{
-	assert(lk_id > -1);
-	dbg_rrl_verb("%s: unlocking id '%d'\n", __func__, lk_id);
-	return pthread_mutex_unlock(&t->lk[lk_id].mx);
-}
-
 /* Classification */
 enum {
 	CLS_NULL     = 0 << 0, /* Empty bucket. */
@@ -201,8 +186,11 @@ static int rrl_classify(char *dst, size_t maxlen, const sockaddr_t *a,
 	blklen += sizeof(nb);
 
 	/* Name */
+	uint16_t *nlen = (uint16_t*)(dst + blklen);
+	blklen += sizeof(uint16_t);
 	int len = rrl_clsname(dst + blklen, maxlen - blklen, cls, p, z);
 	if (len < 0) return len;
+	*nlen = len;
 	blklen += len;
 	
 	/* Seed. */
@@ -218,12 +206,14 @@ static int bucket_free(rrl_item_t *b, uint32_t now) {
 	return b->cls == CLS_NULL || (b->time + 1 < now);
 }
 
-static int bucket_match(rrl_item_t *b, uint8_t cls, uint64_t addr)
+static int bucket_match(rrl_item_t *b, rrl_item_t *m)
 {
-	return b->cls == cls && b->pref == addr;
+	return b->cls    == m->cls &&
+	       b->netblk == m->netblk &&
+	       b->qname  == m->qname;
 }
 
-static unsigned find_free(rrl_table_t *t, unsigned i, uint8_t cls, uint64_t addr, uint32_t now)
+static int find_free(rrl_table_t *t, unsigned i, uint32_t now)
 {
 	rrl_item_t *np = t->arr + t->size;
 	rrl_item_t *b = NULL;
@@ -244,6 +234,51 @@ static unsigned find_free(rrl_table_t *t, unsigned i, uint8_t cls, uint64_t addr
 	return i;
 }
 
+static inline unsigned find_match(rrl_table_t *t, uint32_t id, rrl_item_t *m)
+{
+	unsigned f = 0;
+	unsigned d = 0;
+	unsigned match = t->arr[id].hop;
+	while (match != 0) {
+		d = __builtin_ctz(match);
+		f = (id + d) % t->size;
+		if (bucket_match(t->arr + f, m)) {
+			return d;
+		} else {
+			match &= ~(1<<d); /* clear potential match */
+		}
+	}
+
+	return HOP_LEN + 1;
+}
+
+static inline unsigned reduce_dist(rrl_table_t *t, unsigned id, unsigned d, unsigned *f)
+{
+	unsigned rd = HOP_LEN - 1;
+	while (rd > 0) {
+		unsigned s = (t->size + *f - rd) % t->size; /* bucket to be vacated */
+		unsigned o = __builtin_ctz(t->arr[s].hop); /* offset of first valid bucket */
+		if (t->arr[s].hop != 0 && o < rd) {        /* only offsets in <s, f> are interesting */
+			unsigned e = (s + o) % t->size;    /* this item will be displaced to [f] */
+			unsigned keep_hop = t->arr[*f].hop; /* unpredictable padding */
+			memcpy(t->arr + *f, t->arr + e, sizeof(rrl_item_t));
+			t->arr[*f].hop = keep_hop;
+			t->arr[e].cls = CLS_NULL;
+			t->arr[s].hop &= ~(1<<o);
+			t->arr[s].hop |= 1<<rd;
+			*f = e;
+			return d - (rd - o);
+		}
+		--rd;
+	}
+	
+	assert(rd == 0); /* this happens with p=1/fact(HOP_LEN) */
+	*f = id;
+	d = 0; /* force vacate initial element */
+	dbg_rrl("%s: no potential relocation, freeing bucket %u\n", __func__, id);
+	return d;
+}
+
 static void rrl_log_state(const sockaddr_t *a, uint16_t flags, uint8_t cls)
 {
 #ifdef RRL_ENABLE_LOG
@@ -260,18 +295,14 @@ static void rrl_log_state(const sockaddr_t *a, uint16_t flags, uint8_t cls)
 #endif
 }
 
-
-
 rrl_table_t *rrl_create(size_t size)
 {
 	const size_t tbl_len = sizeof(rrl_table_t) + size * sizeof(rrl_item_t);
 	rrl_table_t *t = malloc(tbl_len);
 	if (!t) return NULL;
-	
-	memset(t, 0, tbl_len);
-	t->rate = 0;
-	t->seed = (uint32_t)(tls_rand() * (double)UINT32_MAX);
+	memset(t, 0, sizeof(rrl_table_t));
 	t->size = size;
+	rrl_reseed(t);
 	dbg_rrl("%s: created table size '%zu'\n", __func__, t->size);
 	return t;
 }
@@ -290,25 +321,31 @@ uint32_t rrl_rate(rrl_table_t *rrl)
 	return rrl->rate;
 }
 
-int rrl_setlocks(rrl_table_t *rrl, size_t granularity)
+int rrl_setlocks(rrl_table_t *rrl, unsigned granularity)
 {
 	if (!rrl) return KNOT_EINVAL;
 	assert(!rrl->lk); /* Cannot change while locks are used. */
+	assert(granularity <= rrl->size / 10); /* Due to int. division err. */
+	
+	if (pthread_mutex_init(&rrl->ll, NULL) < 0) {
+		return KNOT_ENOMEM;
+	}
 	
 	/* Alloc new locks. */
-	rrl->lk = malloc(granularity * sizeof(rrl_lock_t));
+	rrl->lk = malloc(granularity * sizeof(pthread_mutex_t));
 	if (!rrl->lk) return KNOT_ENOMEM;
-	memset(rrl->lk, 0, granularity * sizeof(rrl_lock_t));
+	memset(rrl->lk, 0, granularity * sizeof(pthread_mutex_t));
 	
 	/* Initialize. */
 	for (size_t i = 0; i < granularity; ++i) {
-		if (pthread_mutex_init(&rrl->lk[i].mx, NULL) < 0) break;
+		if (pthread_mutex_init(rrl->lk + i, NULL) < 0) break;
 		++rrl->lk_count;
 	}
+
 	/* Incomplete initialization */
 	if (rrl->lk_count != granularity) {
 		for (size_t i = 0; i < rrl->lk_count; ++i) {
-			pthread_mutex_destroy(&rrl->lk[i].mx);
+			pthread_mutex_destroy(rrl->lk + i);
 		}
 		free(rrl->lk);
 		rrl->lk_count = 0;
@@ -321,7 +358,7 @@ int rrl_setlocks(rrl_table_t *rrl, size_t granularity)
 }
 
 rrl_item_t* rrl_hash(rrl_table_t *t, const sockaddr_t *a, rrl_req_t *p,
-                     const knot_zone_t *zone, uint32_t stamp, int *lk)
+                     const knot_zone_t *zone, uint32_t stamp, int *lock)
 {
 	char buf[RRL_CLSBLK_MAXLEN];
 	int len = rrl_classify(buf, sizeof(buf), a, p, zone, t->seed);
@@ -331,83 +368,54 @@ rrl_item_t* rrl_hash(rrl_table_t *t, const sockaddr_t *a, rrl_req_t *p,
 	
 	uint32_t id = hash(buf, len) % t->size;
 	
-	/*! \todo locking is wrong now, need to map to regions and check */
-	*lk = -1;
-	if (t->lk_count > 0) {
-		*lk = id % t->lk_count;
-		rrl_lock_mx(t, *lk);
+	/* Lock for lookup. */
+	pthread_mutex_lock(&t->ll);
+
+	/* Find an exact match in <id, id + HOP_LEN). */
+	uint16_t *qname = (uint16_t*)(buf + sizeof(uint8_t) + sizeof(uint64_t));
+	rrl_item_t match = {
+	        0, *((uint64_t*)(buf + 1)), t->rate,    /* hop, netblk, ntok */
+	        buf[0], RRL_BF_NULL,                    /* cls, flags */
+	        hash((char*)(qname + 1), *qname), stamp /* qname, time*/
+	};
+
+	unsigned d = find_match(t, id, &match);
+	if (d > HOP_LEN) { /* not an exact match, find free element [f] */
+		d = find_free(t, id, stamp);
 	}
 	
-	/* Find an exact match in <id, id+K). */
-	unsigned f = 0;
-	unsigned d = 0;
-	uint64_t nprefix = *((uint64_t*)(buf + sizeof(uint8_t)));
-	unsigned match = t->arr[id].hop;
-	while (match != 0) {
-		d = __builtin_ctz(match);
-		f = (id + d) % t->size;
-		if (bucket_match(t->arr + f, buf[0], nprefix)) {
-			break;
-		} else {
-			match &= ~(1<<d); /* clear potential match */
-		}
-	}
-
-	if (match == 0) { /* not an exact match, find free element [f] */
-		d = find_free(t, id, buf[0], nprefix, stamp); 
-		f = (id + d) % t->size;
-	}
+	/* Reduce distance to fit <id, id + HOP_LEN) */
+	unsigned f = (id + d) % t->size;
 	while (d >= HOP_LEN) {
-		unsigned rd = HOP_LEN - 1;
-		while (rd > 0) {
-			unsigned s = (t->size + f - rd) % t->size; /* bucket to be vacated */
-			unsigned o = __builtin_ctz(t->arr[s].hop); /* offset of first valid bucket */
-			if (t->arr[s].hop != 0 && o < rd) {        /* only offsets in <s, f> are interesting */
-				unsigned e = (s + o) % t->size;    /* this item will be displaced to [f] */
-				unsigned keep_hop = t->arr[f].hop; /* unpredictable padding */
-				memcpy(t->arr + f, t->arr + e, sizeof(rrl_item_t));
-				t->arr[f].hop = keep_hop;
-				t->arr[e].cls = CLS_NULL;
-				t->arr[s].hop &= ~(1<<o);
-				t->arr[s].hop |= 1<<rd;
-				f = e;
-				d -= (rd - o);
-				break;
-			}
-			--rd;
-		}
-		if (rd == 0) { /* this happens with p=1/fact(HOP_LEN) */
-			f = id;
-			d = 0; /* force vacate initial element */
-			dbg_rrl("%s: no potential relocation, freeing bucket %u\n", __func__, id);
-		}
+		d = reduce_dist(t, id, d, &f);
 	}
+	
+	/* Assign granular lock and unlock lookup. */
+	*lock = f % t->lk_count;
+	rrl_lock(t, *lock);
+	pthread_mutex_unlock(&t->ll);
+
 	/* found free elm 'k' which is in <id, id + HOP_LEN) */
 	t->arr[id].hop |= (1 << d);
 	rrl_item_t* b = t->arr + f;
 	assert(f == (id+d) % t->size);
-	dbg_rrl("%s: classified pkt as %u '%u+%u' bucket=%p \n", __func__, f, id, d, b);
+	dbg_rrl("%s: classified pkt as %4x '%u+%u' bucket=%p \n", __func__, f, id, d, b);
 
 	/* Inspect bucket state. */
+	unsigned hop = b->hop;
 	if (b->cls == CLS_NULL) {
-		b->cls = *buf; /* Stored as a first byte in clsblock. */
-		b->flags = RRL_BF_NULL;
-		b->ntok = t->rate;
-		b->time = stamp;
-		b->pref = nprefix; /* Invalidate */
+		memcpy(b, &match, sizeof(rrl_item_t));
+		b->hop = hop;
 	}
 	/* Check for collisions. */
-	/* \todo <prefix, imputed(qname), cls> */
-	if (!bucket_match(b, buf[0], nprefix)) {
-		dbg_rrl("%s: collision in bucket '0x%4x'\n", __func__, id);
+	if (!bucket_match(b, &match)) {
+		dbg_rrl("%s: collision in bucket '%4x'\n", __func__, id);
 		if (!(b->flags & RRL_BF_SSTART)) {
-			b->pref = nprefix;
-			b->cls = *buf;
-			b->flags = RRL_BF_NULL; /* Reset flags. */
-			b->time = stamp; /* Reset time */
+			memcpy(b, &match, sizeof(rrl_item_t));
+			b->hop = hop;
 			b->ntok = t->rate + t->rate / RRL_SSTART;
 			b->flags |= RRL_BF_SSTART;
-			dbg_rrl("%s: bucket '0x%4x' slow-start\n", __func__, id);
+			dbg_rrl("%s: bucket '%4x' slow-start\n", __func__, id);
 		}
 	}
 	
@@ -425,8 +433,8 @@ int rrl_query(rrl_table_t *rrl, const sockaddr_t *a, rrl_req_t *req,
 	uint32_t now = time(NULL);
 	rrl_item_t *b = rrl_hash(rrl, a, req, zone, now, &lock);
 	if (!b) {
-		assert(lock < 0);
 		dbg_rrl("%s: failed to compute bucket from packet\n", __func__);
+		if (lock > -1) rrl_unlock(rrl, lock);
 		return KNOT_ERROR;
 	}
 	
@@ -450,7 +458,6 @@ int rrl_query(rrl_table_t *rrl, const sockaddr_t *a, rrl_req_t *req,
 		/* Add new tokens. */
 		uint32_t dn = rrl->rate * dt;
 		if (b->flags & RRL_BF_SSTART) { /* Bucket in slow-start. */
-			dn /= RRL_SSTART;
 			b->flags &= ~RRL_BF_SSTART;
 			dbg_rrl("%s: bucket '0x%x' slow-start finished\n",
 			        __func__, (unsigned)(b - rrl->arr));
@@ -473,12 +480,8 @@ int rrl_query(rrl_table_t *rrl, const sockaddr_t *a, rrl_req_t *req,
 	} else if (b->ntok == 0) {
 		ret = KNOT_ELIMIT;
 	}
-
-	/* Unlock bucket. */
-	if (lock > -1) {
-		rrl_unlock_mx(rrl, lock);
-	}
 	
+	if (lock > -1) rrl_unlock(rrl, lock);
 	return ret;
 }
 
@@ -486,8 +489,9 @@ int rrl_destroy(rrl_table_t *rrl)
 {
 	if (rrl) {
 		dbg_rrl("%s: freeing table %p\n", __func__, rrl);
+		if (rrl->lk_count > 0) pthread_mutex_destroy(&rrl->ll);
 		for (size_t i = 0; i < rrl->lk_count; ++i) {
-			pthread_mutex_destroy(&rrl->lk[i].mx);
+			pthread_mutex_destroy(rrl->lk + i);
 		}
 		free(rrl->lk);
 	}
@@ -495,3 +499,47 @@ int rrl_destroy(rrl_table_t *rrl)
 	free(rrl);
 	return KNOT_EOK;
 }
+
+int rrl_reseed(rrl_table_t *rrl)
+{
+	/* Lock entire table. */
+	if (rrl->lk_count > 0) {
+		pthread_mutex_lock(&rrl->ll);
+		for (unsigned i = 0; i < rrl->lk_count; ++i) {
+			rrl_lock(rrl, i);
+		}
+	}
+	
+	memset(rrl->arr, 0, rrl->size * sizeof(rrl_item_t));
+	rrl->seed = (uint32_t)(tls_rand() * (double)UINT32_MAX);
+	dbg_rrl("%s: reseed to '%u'\n", __func__, rrl->seed);
+	
+	if (rrl->lk_count > 0) {
+		for (unsigned i = 0; i < rrl->lk_count; ++i) {
+			rrl_unlock(rrl, i);
+		}
+		pthread_mutex_unlock(&rrl->ll);
+	}
+	
+	return KNOT_EOK;
+}
+
+int rrl_lock(rrl_table_t *t, int lk_id)
+{
+	assert(lk_id > -1);
+	dbg_rrl_verb("%s: locking id '%d'\n", __func__, lk_id);
+	if (pthread_mutex_lock(t->lk + lk_id) != 0) {
+		return KNOT_ERROR;
+	}
+	return KNOT_EOK;
+}
+
+int rrl_unlock(rrl_table_t *t, int lk_id)
+{
+	assert(lk_id > -1);
+	dbg_rrl_verb("%s: unlocking id '%d'\n", __func__, lk_id);
+	if (pthread_mutex_unlock(t->lk + lk_id)!= 0) {
+		return KNOT_ERROR;
+	}
+	return KNOT_EOK;
+}
diff --git a/src/knot/server/rrl.h b/src/knot/server/rrl.h
index 94d9387c71..6ced2f89c2 100644
--- a/src/knot/server/rrl.h
+++ b/src/knot/server/rrl.h
@@ -34,24 +34,21 @@
 #include "libknot/zone/zone.h"
 
 /* Defaults */
-#define RRL_LOCK_GRANULARITY 10 /* Last digit granularity */
+#define RRL_LOCK_GRANULARITY 32 /* Last digit granularity */
 
 /*!
  * \brief RRL hash bucket.
  */
 typedef struct rrl_item {
 	unsigned hop;        /* Hop bitmap. */
-	uint64_t pref;       /* Prefix associated. */
+	uint64_t netblk;     /* Prefix associated. */
 	uint16_t ntok;       /* Tokens available */
 	uint8_t  cls;        /* Bucket class */
 	uint8_t  flags;      /* Flags */
+	uint32_t qname;      /* imputed(QNAME) hash */
 	uint32_t time;       /* Timestamp */
 } rrl_item_t;
 
-typedef struct rrl_lock {    /* Wrapper around lock struct. */
-	pthread_mutex_t mx;
-} rrl_lock_t;
-
 /*!
  * \brief RRL hash bucket table.
  * 
@@ -67,8 +64,9 @@ typedef struct rrl_lock {    /* Wrapper around lock struct. */
 typedef struct rrl_table {
 	uint32_t rate;       /* Configured RRL limit */
 	uint32_t seed;       /* Pseudorandom seed for hashing. */
-	rrl_lock_t *lk;      /* Table locks. */
-	size_t lk_count;     /* Table lock count (granularity). */
+	pthread_mutex_t ll;
+	pthread_mutex_t *lk;      /* Table locks. */
+	unsigned lk_count;   /* Table lock count (granularity). */
 	size_t size;         /* Number of buckets */
 	rrl_item_t arr[];    /* Buckets */
 } rrl_table_t;
@@ -116,7 +114,7 @@ uint32_t rrl_setrate(rrl_table_t *rrl, uint32_t rate);
  * \retval KNOT_EOK
  * \retval KNOT_EINVAL
  */
-int rrl_setlocks(rrl_table_t *rrl, size_t granularity);
+int rrl_setlocks(rrl_table_t *rrl, unsigned granularity);
 
 /*!
  * \brief Get bucket for current combination of parameters.
@@ -125,11 +123,11 @@ int rrl_setlocks(rrl_table_t *rrl, size_t granularity);
  * \param p RRL request.
  * \param zone Relate zone.
  * \param stamp Timestamp (current time).
- * \param lk Destination for assigned lock (*lk will be set to a value).
+ * \param lock Held lock.
  * \return assigned bucket
  */
 rrl_item_t* rrl_hash(rrl_table_t *t, const sockaddr_t *a, rrl_req_t *p,
-                     const knot_zone_t *zone, uint32_t stamp, int *lk);
+                     const knot_zone_t *zone, uint32_t stamp, int* lock);
 
 /*!
  * \brief Query the RRL table for accept or deny, when the rate limit is reached.
@@ -151,6 +149,30 @@ int rrl_query(rrl_table_t *rrl, const sockaddr_t *a, rrl_req_t *req,
  */
 int rrl_destroy(rrl_table_t *rrl);
 
+/*!
+ * \brief Reseed RRL table secret.
+ * \param rrl RRL table.
+ * \return KNOT_EOK
+ */
+int rrl_reseed(rrl_table_t *rrl);
+
+/*!
+ * \brief Lock specified element lock.
+ * \param rrl RRL table.
+ * \param lk_id Specified lock.
+ * \retval KNOT_EOK
+ * \retval KNOT_ERROR
+ */
+int rrl_lock(rrl_table_t *rrl, int lk_id);
+
+/*!
+ * \brief Unlock specified element lock.
+ * \param rrl RRL table.
+ * \param lk_id Specified lock.
+ * \retval KNOT_EOK
+ * \retval KNOT_ERROR
+ */
+int rrl_unlock(rrl_table_t *rrl, int lk_id);
 
 #endif /* _KNOTD_RRL_H_ */
 
diff --git a/src/tests/knot/rrl_tests.c b/src/tests/knot/rrl_tests.c
index ffeeebff47..b996dd0d58 100644
--- a/src/tests/knot/rrl_tests.c
+++ b/src/tests/knot/rrl_tests.c
@@ -18,6 +18,7 @@
 #include <sys/socket.h>
 #include "tests/knot/rrl_tests.h"
 #include "knot/server/rrl.h"
+#include "knot/server/dthreads.h"
 #include "knot/common.h"
 #include "libknot/packet/response.h"
 #include "libknot/packet/query.h"
@@ -26,13 +27,65 @@
 
 /* Enable time-dependent tests. */
 //#define ENABLE_TIMED_TESTS
-#define RRL_SIZE (786433) /* prime */
-#define RRL_INSERTS (629146) /* fill=0.8 */
+#define RRL_SIZE 196613
+#define RRL_THREADS 16
+#define RRL_INSERTS (0.5*(RRL_SIZE/RRL_THREADS)) /* lf = 0.5 */
+#define RRL_LOCKS 64
+
 struct bucketmap_t {
 	unsigned i;
 	uint64_t x;
 };
 
+/*! \brief Unit runnable. */
+struct runnable_data {
+	int passed;
+	rrl_table_t *rrl;
+	sockaddr_t *addr;
+	rrl_req_t *rq;
+	knot_zone_t *zone;
+};
+
+static void* rrl_runnable(void *arg)
+{
+	struct runnable_data* d = (struct runnable_data*)arg;
+	sockaddr_t addr;
+	memcpy(&addr, d->addr, sizeof(sockaddr_t));
+	sockaddr_update(&addr);
+	int lock = -1;
+	uint32_t now = time(NULL);
+	struct bucketmap_t *m = malloc(RRL_INSERTS * sizeof(struct bucketmap_t));
+	for (unsigned i = 0; i < RRL_INSERTS; ++i) {
+		m[i].i = tls_rand() * UINT32_MAX;
+		addr.addr4.sin_addr.s_addr = m[i].i;
+		rrl_item_t *b =  rrl_hash(d->rrl, &addr, d->rq, d->zone, now, &lock);
+		rrl_unlock(d->rrl, lock);
+		m[i].x = b->netblk;
+	}
+	for (unsigned i = 0; i < RRL_INSERTS; ++i) {
+		addr.addr4.sin_addr.s_addr = m[i].i;
+		rrl_item_t *b = rrl_hash(d->rrl, &addr, d->rq, d->zone, now, &lock);
+		rrl_unlock(d->rrl, lock);
+		if (b->netblk != m[i].x) {
+			d->passed = 0;
+		}
+	}
+	free(m);
+	return NULL;
+}
+
+static void rrl_hopscotch(struct runnable_data* rd)
+{
+	rd->passed = 1;
+	pthread_t thr[RRL_THREADS];
+	for (unsigned i = 0; i < RRL_THREADS; ++i) {
+		pthread_create(thr + i, NULL, &rrl_runnable, rd);
+	}
+	for (unsigned i = 0; i < RRL_THREADS; ++i) {
+		pthread_join(thr[i], NULL);
+	}
+}
+
 static int rrl_tests_count(int argc, char *argv[]);
 static int rrl_tests_run(int argc, char *argv[]);
 
@@ -51,7 +104,7 @@ unit_api rrl_tests_api = {
 
 static int rrl_tests_count(int argc, char *argv[])
 {
-	int c = 7;
+	int c = 10;
 #ifndef ENABLE_TIMED_TESTS
 	c -= 2;
 #endif
@@ -91,15 +144,19 @@ static int rrl_tests_run(int argc, char *argv[])
 	uint32_t rate = 10;
 	rrl_setrate(rrl, rate);
 	ok(rate == rrl_rate(rrl), "rrl: setrate");
+	
+	/* 3. setlocks */
+	int ret = rrl_setlocks(rrl, RRL_LOCKS);
+	ok(ret == KNOT_EOK, "rrl: setlocks");
 
-	/* 3. N unlimited requests. */
+	/* 4. N unlimited requests. */
 	knot_dname_t *apex = knot_dname_new_from_str("rrl.", 4, NULL);
 	knot_zone_t *zone = knot_zone_new(knot_node_new(apex, NULL, 0), 0, 0);
 	sockaddr_t addr;
 	sockaddr_t addr6;
 	sockaddr_set(&addr, AF_INET, "1.2.3.4", 0);
 	sockaddr_set(&addr6, AF_INET6, "1122:3344:5566:7788::aabb", 0);
-	int ret = 0;
+	ret = 0;
 	for (unsigned i = 0; i < rate; ++i) {
 		if (rrl_query(rrl, &addr, &rq, zone) != KNOT_EOK ||
 		    rrl_query(rrl, &addr6, &rq, zone) != KNOT_EOK) {
@@ -110,16 +167,16 @@ static int rrl_tests_run(int argc, char *argv[])
 	ok(ret == 0, "rrl: unlimited IPv4/v6 requests");
 
 #ifdef ENABLE_TIMED_TESTS
-	/* 4. limited request */
+	/* 5. limited request */
 	ret = rrl_query(rrl, &addr, &rq, zone);
 	ok(ret != 0, "rrl: throttled IPv4 request");
 
-	/* 5. limited IPv6 request */
+	/* 6. limited IPv6 request */
 	ret = rrl_query(rrl, &addr6, &rq, zone);
 	ok(ret != 0, "rrl: throttled IPv6 request");
 #endif
 	
-	/* 6. invalid values. */
+	/* 7. invalid values. */
 	ret = 0;
 	lives_ok( {
 	                  rrl_create(0);            // NULL
@@ -132,27 +189,19 @@ static int rrl_tests_run(int argc, char *argv[])
 	                  ret += rrl_destroy(0); // -1
 	}, "rrl: not crashed while executing functions on NULL context");
 	
-	/* 7. hopscotch test */
-	int lk = 0;
-	int passed = 1;
-	uint32_t now = time(NULL);
-	struct bucketmap_t *m = malloc(RRL_INSERTS * sizeof(struct bucketmap_t));
-	for (unsigned i = 0; i < RRL_INSERTS; ++i) {
-		m[i].i = tls_rand() * UINT32_MAX;
-		addr.addr4.sin_addr.s_addr = m[i].i;
-		rrl_item_t *b =  rrl_hash(rrl, &addr, &rq, zone, now, &lk);
-		m[i].x = b->pref;
-	}
-	for (unsigned i = 0; i < RRL_INSERTS; ++i) {
-		addr.addr4.sin_addr.s_addr = m[i].i;
-		rrl_item_t *b = rrl_hash(rrl, &addr, &rq, zone, now, &lk);
-		if (b->pref != m[i].x) {
-			passed = 0;
-			//break;
-		}
-	}
-	free(m);
-	ok(passed, "rrl: hashtable is ~ consistent\n");
+	/* 8. hopscotch test */
+	struct runnable_data rd = {
+		1, rrl, &addr, &rq, zone
+	};
+	rrl_hopscotch(&rd);
+	ok(rd.passed, "rrl: hashtable is ~ consistent");
+	
+	/* 9. reseed */
+	ok(rrl_reseed(rrl) == 0, "rrl: reseed");
+	
+	/* 10. hopscotch after reseed. */
+	rrl_hopscotch(&rd);
+	ok(rd.passed, "rrl: hashtable is ~ consistent");
 	
 	knot_dname_release(qst.qname);
 	knot_dname_release(apex);
-- 
GitLab