Commit 453d3fe2 authored by Rusty Russell's avatar Rusty Russell

tdb2: split expand into functions and test separately.

parent 12b598b0
...@@ -559,92 +559,39 @@ static bool zones_happy(struct tdb_context *tdb) ...@@ -559,92 +559,39 @@ static bool zones_happy(struct tdb_context *tdb)
return true; return true;
} }
/* Expand the database. */ /* Returns how much extra room we get, or TDB_OFF_ERR. */
int tdb_expand(struct tdb_context *tdb, tdb_len_t klen, tdb_len_t dlen, static tdb_len_t expand_to_fill_zones(struct tdb_context *tdb)
bool growing)
{ {
uint64_t new_num_buckets, new_num_zones, new_zone_bits; tdb_len_t add;
uint64_t i, old_num_total, old_num_zones, old_size, old_zone_bits;
tdb_len_t add, freebucket_size, needed;
tdb_off_t off, old_free_off;
const tdb_off_t *oldf;
struct tdb_used_record fhdr;
/* We need room for the record header too. */
needed = sizeof(struct tdb_used_record)
+ adjust_size(klen, dlen, growing);
/* tdb_allrecord_lock will update header; did zones change? */
old_zone_bits = tdb->header.v.zone_bits;
old_num_zones = tdb->header.v.num_zones;
/* FIXME: this is overkill. An expand lock? */
if (tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false) == -1)
return -1;
/* Someone may have expanded for us. */
if (old_zone_bits != tdb->header.v.zone_bits
|| old_num_zones != tdb->header.v.num_zones)
goto success;
/* They may have also expanded the underlying size (otherwise we'd
* have expanded our mmap to look at those offsets already). */
old_size = tdb->map_size;
tdb->methods->oob(tdb, tdb->map_size + 1, true);
if (tdb->map_size != old_size)
goto success;
/* Did we enlarge zones without enlarging file? */
if (tdb->map_size < tdb->header.v.num_zones<<tdb->header.v.zone_bits) {
add = (tdb->header.v.num_zones<<tdb->header.v.zone_bits)
- tdb->map_size;
/* Updates tdb->map_size. */
if (tdb->methods->expand_file(tdb, add) == -1)
goto fail;
if (add_free_record(tdb, tdb->map_size - add, add) == -1)
goto fail;
if (add >= needed) {
/* Allocate from this zone. */
tdb->last_zone = zone_of(tdb, tdb->map_size - add);
goto success;
}
}
/* Slow path. Should we increase the number of buckets? */
new_num_buckets = tdb->header.v.free_buckets;
if (larger_buckets_might_help(tdb))
new_num_buckets++;
/* Now we'll need room for the new free buckets, too. Assume /* We can enlarge zones without enlarging file to match. */
* worst case (zones expand). */ add = (tdb->header.v.num_zones<<tdb->header.v.zone_bits)
needed += sizeof(fhdr) - tdb->map_size;
+ ((tdb->header.v.num_zones+1) if (add <= sizeof(struct tdb_free_record))
* (new_num_buckets+1) * sizeof(tdb_off_t)); return 0;
/* If we need less that one zone, and they're working well, just add /* Updates tdb->map_size. */
* another one. */ if (tdb->methods->expand_file(tdb, add) == -1)
if (needed < (1UL<<tdb->header.v.zone_bits) && zones_happy(tdb)) { return TDB_OFF_ERR;
new_num_zones = tdb->header.v.num_zones+1; if (add_free_record(tdb, tdb->map_size - add, add) == -1)
new_zone_bits = tdb->header.v.zone_bits; return TDB_OFF_ERR;
add = 1ULL << tdb->header.v.zone_bits; return add;
} else { }
/* Increase the zone size. */
new_num_zones = tdb->header.v.num_zones;
new_zone_bits = tdb->header.v.zone_bits+1;
while ((new_num_zones << new_zone_bits)
< tdb->map_size + needed) {
new_zone_bits++;
}
/* We expand by enough full zones to meet the need. */ static int update_zones(struct tdb_context *tdb,
add = ((tdb->map_size + needed + (1ULL << new_zone_bits)-1) uint64_t new_num_zones,
& ~((1ULL << new_zone_bits)-1)) uint64_t new_zone_bits,
- tdb->map_size; uint64_t new_num_buckets,
} tdb_len_t add)
{
tdb_len_t freebucket_size;
const tdb_off_t *oldf;
tdb_off_t i, off, old_num_total, old_free_off;
struct tdb_used_record fhdr;
/* Updates tdb->map_size. */ /* Updates tdb->map_size. */
if (tdb->methods->expand_file(tdb, add) == -1) if (tdb->methods->expand_file(tdb, add) == -1)
goto fail; return -1;
/* Use first part as new free bucket array. */ /* Use first part as new free bucket array. */
off = tdb->map_size - add; off = tdb->map_size - add;
...@@ -653,9 +600,9 @@ int tdb_expand(struct tdb_context *tdb, tdb_len_t klen, tdb_len_t dlen, ...@@ -653,9 +600,9 @@ int tdb_expand(struct tdb_context *tdb, tdb_len_t klen, tdb_len_t dlen,
/* Write header. */ /* Write header. */
if (set_header(tdb, &fhdr, 0, freebucket_size, freebucket_size, 0)) if (set_header(tdb, &fhdr, 0, freebucket_size, freebucket_size, 0))
goto fail; return -1;
if (tdb_write_convert(tdb, off, &fhdr, sizeof(fhdr)) == -1) if (tdb_write_convert(tdb, off, &fhdr, sizeof(fhdr)) == -1)
goto fail; return -1;
/* Adjust off to point to start of buckets, add to be remainder. */ /* Adjust off to point to start of buckets, add to be remainder. */
add -= freebucket_size + sizeof(fhdr); add -= freebucket_size + sizeof(fhdr);
...@@ -667,7 +614,7 @@ int tdb_expand(struct tdb_context *tdb, tdb_len_t klen, tdb_len_t dlen, ...@@ -667,7 +614,7 @@ int tdb_expand(struct tdb_context *tdb, tdb_len_t klen, tdb_len_t dlen,
oldf = tdb_access_read(tdb, old_free_off, oldf = tdb_access_read(tdb, old_free_off,
old_num_total * sizeof(tdb_off_t), true); old_num_total * sizeof(tdb_off_t), true);
if (!oldf) if (!oldf)
goto fail; return -1;
/* Switch to using our new zone. */ /* Switch to using our new zone. */
if (zero_out(tdb, off, freebucket_size) == -1) if (zero_out(tdb, off, freebucket_size) == -1)
...@@ -716,14 +663,95 @@ int tdb_expand(struct tdb_context *tdb, tdb_len_t klen, tdb_len_t dlen, ...@@ -716,14 +663,95 @@ int tdb_expand(struct tdb_context *tdb, tdb_len_t klen, tdb_len_t dlen,
/* Start allocating from where the new space is. */ /* Start allocating from where the new space is. */
tdb->last_zone = zone_of(tdb, tdb->map_size - add); tdb->last_zone = zone_of(tdb, tdb->map_size - add);
tdb_access_release(tdb, oldf); tdb_access_release(tdb, oldf);
if (write_header(tdb) == -1) return write_header(tdb);
fail_release:
tdb_access_release(tdb, oldf);
return -1;
}
/* Expand the database. */
int tdb_expand(struct tdb_context *tdb, tdb_len_t klen, tdb_len_t dlen,
bool growing)
{
uint64_t new_num_buckets, new_num_zones, new_zone_bits;
uint64_t old_num_zones, old_size, old_zone_bits;
tdb_len_t add, needed;
/* We need room for the record header too. */
needed = sizeof(struct tdb_used_record)
+ adjust_size(klen, dlen, growing);
/* tdb_allrecord_lock will update header; did zones change? */
old_zone_bits = tdb->header.v.zone_bits;
old_num_zones = tdb->header.v.num_zones;
/* FIXME: this is overkill. An expand lock? */
if (tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false) == -1)
return -1;
/* Someone may have expanded for us. */
if (old_zone_bits != tdb->header.v.zone_bits
|| old_num_zones != tdb->header.v.num_zones)
goto success;
/* They may have also expanded the underlying size (otherwise we'd
* have expanded our mmap to look at those offsets already). */
old_size = tdb->map_size;
tdb->methods->oob(tdb, tdb->map_size + 1, true);
if (tdb->map_size != old_size)
goto success;
add = expand_to_fill_zones(tdb);
if (add == TDB_OFF_ERR)
goto fail; goto fail;
if (add >= needed) {
/* Allocate from this zone. */
tdb->last_zone = zone_of(tdb, tdb->map_size - add);
goto success;
}
/* Slow path. Should we increase the number of buckets? */
new_num_buckets = tdb->header.v.free_buckets;
if (larger_buckets_might_help(tdb))
new_num_buckets++;
/* Now we'll need room for the new free buckets, too. Assume
* worst case (zones expand). */
needed += sizeof(struct tdb_used_record)
+ ((tdb->header.v.num_zones+1)
* (new_num_buckets+1) * sizeof(tdb_off_t));
/* If we need less that one zone, and they're working well, just add
* another one. */
if (needed < (1UL<<tdb->header.v.zone_bits) && zones_happy(tdb)) {
new_num_zones = tdb->header.v.num_zones+1;
new_zone_bits = tdb->header.v.zone_bits;
add = 1ULL << tdb->header.v.zone_bits;
} else {
/* Increase the zone size. */
new_num_zones = tdb->header.v.num_zones;
new_zone_bits = tdb->header.v.zone_bits+1;
while ((new_num_zones << new_zone_bits)
< tdb->map_size + needed) {
new_zone_bits++;
}
/* We expand by enough full zones to meet the need. */
add = ((tdb->map_size + needed + (1ULL << new_zone_bits)-1)
& ~((1ULL << new_zone_bits)-1))
- tdb->map_size;
}
if (update_zones(tdb, new_num_zones, new_zone_bits, new_num_buckets,
add) == -1)
goto fail;
success: success:
tdb_allrecord_unlock(tdb, F_WRLCK); tdb_allrecord_unlock(tdb, F_WRLCK);
return 0; return 0;
fail_release:
tdb_access_release(tdb, oldf);
fail: fail:
tdb_allrecord_unlock(tdb, F_WRLCK); tdb_allrecord_unlock(tdb, F_WRLCK);
return -1; return -1;
......
...@@ -6,35 +6,123 @@ ...@@ -6,35 +6,123 @@
#include <ccan/tap/tap.h> #include <ccan/tap/tap.h>
#include "logging.h" #include "logging.h"
/* Release lock to check db. */
static void check(struct tdb_context *tdb)
{
tdb_allrecord_unlock(tdb, F_WRLCK);
ok1(tdb_check(tdb, NULL, NULL) == 0);
ok1(tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false) == 0);
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
unsigned int i; unsigned int i;
tdb_off_t off;
uint64_t val, buckets;
struct tdb_context *tdb; struct tdb_context *tdb;
int flags[] = { TDB_INTERNAL, TDB_DEFAULT, int flags[] = { TDB_INTERNAL, TDB_DEFAULT,
TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT }; TDB_INTERNAL|TDB_CONVERT, TDB_CONVERT };
plan_tests(sizeof(flags) / sizeof(flags[0]) * 10 + 1); plan_tests(sizeof(flags) / sizeof(flags[0]) * 40 + 1);
/* First, lower level expansion tests. */
for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
tdb = tdb_open("/tmp/run-new_database.tdb", flags[i],
O_RDWR|O_CREAT|O_TRUNC, 0600, NULL);
tdb->log = tap_log_fn;
ok1(tdb);
if (!tdb)
continue;
ok1(tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false)
== 0);
/* Expanding file is pretty easy. */
off = expand_to_fill_zones(tdb);
ok1(off > 0 && off != TDB_OFF_ERR);
check(tdb);
/* Second expand should do nothing. */
ok1(expand_to_fill_zones(tdb) == 0);
check(tdb);
/* Now, try adding a zone. */
val = tdb->header.v.num_zones + 1;
ok1(update_zones(tdb, val,
tdb->header.v.zone_bits,
tdb->header.v.free_buckets,
1ULL << tdb->header.v.zone_bits) == 0);
ok1(tdb->header.v.num_zones == val);
check(tdb);
/* Now, try doubling zone size. */
val = tdb->header.v.zone_bits + 1;
ok1(update_zones(tdb, tdb->header.v.num_zones,
val,
tdb->header.v.free_buckets,
1ULL << val) == 0);
ok1(tdb->header.v.zone_bits == val);
check(tdb);
/* Now, try adding a zone, and a bucket. */
val = tdb->header.v.num_zones + 1;
buckets = tdb->header.v.free_buckets + 1;
ok1(update_zones(tdb, val,
tdb->header.v.zone_bits,
buckets,
1ULL << tdb->header.v.zone_bits) == 0);
ok1(tdb->header.v.num_zones == val);
ok1(tdb->header.v.free_buckets == buckets);
check(tdb);
/* Now, try doubling zone size, and adding a bucket. */
val = tdb->header.v.zone_bits + 1;
buckets = tdb->header.v.free_buckets + 1;
ok1(update_zones(tdb, tdb->header.v.num_zones,
val,
buckets,
1ULL << val) == 0);
ok1(tdb->header.v.zone_bits == val);
ok1(tdb->header.v.free_buckets == buckets);
check(tdb);
/* Now, try massive zone increase. */
val = tdb->header.v.zone_bits + 4;
ok1(update_zones(tdb, tdb->header.v.num_zones,
val,
tdb->header.v.free_buckets,
1ULL << val) == 0);
ok1(tdb->header.v.zone_bits == val);
check(tdb);
tdb_allrecord_unlock(tdb, F_WRLCK);
tdb_close(tdb);
}
/* Now using tdb_expand. */
for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) { for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) {
tdb = tdb_open("/tmp/run-new_database.tdb", flags[i], tdb = tdb_open("/tmp/run-new_database.tdb", flags[i],
O_RDWR|O_CREAT|O_TRUNC, 0600, NULL); O_RDWR|O_CREAT|O_TRUNC, 0600, NULL);
tdb->log = tap_log_fn; tdb->log = tap_log_fn;
ok1(tdb); ok1(tdb);
if (tdb) { if (!tdb)
/* First expand (expand file to fill zone). */ continue;
ok1(tdb_expand(tdb, 1, 1, false) == 0);
ok1(tdb->header.v.num_zones == 1); /* First expand (expand file to fill zone). */
ok1(tdb_check(tdb, NULL, NULL) == 0); ok1(tdb_expand(tdb, 1, 1, false) == 0);
/* Little expand (extra zone). */ ok1(tdb->header.v.num_zones == 1);
ok1(tdb_expand(tdb, 1, 1, false) == 0); ok1(tdb_check(tdb, NULL, NULL) == 0);
ok1(tdb->header.v.num_zones == 2); /* Little expand (extra zone). */
ok1(tdb_check(tdb, NULL, NULL) == 0); ok1(tdb_expand(tdb, 1, 1, false) == 0);
/* Big expand (enlarge zones) */ ok1(tdb->header.v.num_zones == 2);
ok1(tdb_expand(tdb, 1, 4096, false) == 0); ok1(tdb_check(tdb, NULL, NULL) == 0);
ok1(tdb->header.v.num_zones == 2); /* Big expand (enlarge zones) */
ok1(tdb_check(tdb, NULL, NULL) == 0); ok1(tdb_expand(tdb, 1, 4096, false) == 0);
tdb_close(tdb); ok1(tdb->header.v.num_zones == 2);
} ok1(tdb_check(tdb, NULL, NULL) == 0);
tdb_close(tdb);
} }
ok1(tap_log_messages == 0); ok1(tap_log_messages == 0);
return exit_status(); return exit_status();
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment