X-Git-Url: http://git.ozlabs.org/?p=ccan;a=blobdiff_plain;f=ccan%2Ftdb%2Flock.c;h=dfcac7a540adc30482dcd86e0c299b36bfb52e0b;hp=2a6f1230c9d7308b83592771ec03de5d7f5a95f1;hb=63e80faf9b42fbe73e25c804dd734b6179e42c24;hpb=729fc5b931c8eb26f192399d8aa7064f9af058df diff --git a/ccan/tdb/lock.c b/ccan/tdb/lock.c index 2a6f1230..dfcac7a5 100644 --- a/ccan/tdb/lock.c +++ b/ccan/tdb/lock.c @@ -152,14 +152,6 @@ int tdb_brlock(struct tdb_context *tdb, return -1; } - /* Sanity check */ - if (tdb->transaction && offset >= lock_offset(-1) && len != 0) { - tdb->ecode = TDB_ERR_RDONLY; - TDB_LOG((tdb, TDB_DEBUG_TRACE, "tdb_brlock attempted in transaction at offset %d rw_type=%d flags=%d len=%d\n", - offset, rw_type, flags, (int)len)); - return -1; - } - do { ret = fcntl_lock(tdb, rw_type, offset, len, flags & TDB_LOCK_WAIT); @@ -485,11 +477,9 @@ int tdb_transaction_unlock(struct tdb_context *tdb, int ltype) return tdb_nest_unlock(tdb, TRANSACTION_LOCK, ltype, false); } - -/* lock/unlock entire database. It can only be upgradable if you have some - * other way of guaranteeing exclusivity (ie. transaction write lock). */ -int tdb_allrecord_lock(struct tdb_context *tdb, int ltype, - enum tdb_lock_flags flags, bool upgradable) +/* Returns 0 if all done, -1 if error, 1 if ok. */ +static int tdb_allrecord_check(struct tdb_context *tdb, int ltype, + enum tdb_lock_flags flags, bool upgradable) { /* There are no locks on read-only dbs */ if (tdb->read_only || tdb->traverse_read) { @@ -519,11 +509,73 @@ int tdb_allrecord_lock(struct tdb_context *tdb, int ltype, tdb->ecode = TDB_ERR_LOCK; return -1; } + return 1; +} - if (tdb_brlock(tdb, ltype, FREELIST_TOP, 0, flags)) { - if (flags & TDB_LOCK_WAIT) { - TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_lockall failed (%s)\n", strerror(errno))); - } +/* We only need to lock individual bytes, but Linux merges consecutive locks + * so we lock in contiguous ranges. */ +static int tdb_chainlock_gradual(struct tdb_context *tdb, + int ltype, enum tdb_lock_flags flags, + size_t off, size_t len) +{ + int ret; + enum tdb_lock_flags nb_flags = (flags & ~TDB_LOCK_WAIT); + + if (len <= 4) { + /* Single record. Just do blocking lock. */ + return tdb_brlock(tdb, ltype, off, len, flags); + } + + /* First we try non-blocking. */ + ret = tdb_brlock(tdb, ltype, off, len, nb_flags); + if (ret == 0) { + return 0; + } + + /* Try locking first half, then second. */ + ret = tdb_chainlock_gradual(tdb, ltype, flags, off, len / 2); + if (ret == -1) + return -1; + + ret = tdb_chainlock_gradual(tdb, ltype, flags, + off + len / 2, len - len / 2); + if (ret == -1) { + tdb_brunlock(tdb, ltype, off, len / 2); + return -1; + } + return 0; +} + +/* lock/unlock entire database. It can only be upgradable if you have some + * other way of guaranteeing exclusivity (ie. transaction write lock). + * We do the locking gradually to avoid being starved by smaller locks. */ +int tdb_allrecord_lock(struct tdb_context *tdb, int ltype, + enum tdb_lock_flags flags, bool upgradable) +{ + switch (tdb_allrecord_check(tdb, ltype, flags, upgradable)) { + case -1: + return -1; + case 0: + return 0; + } + + /* We cover two kinds of locks: + * 1) Normal chain locks. Taken for almost all operations. + * 3) Individual records locks. Taken after normal or free + * chain locks. + * + * It is (1) which cause the starvation problem, so we're only + * gradual for that. */ + if (tdb_chainlock_gradual(tdb, ltype, flags, FREELIST_TOP, + tdb->header.hash_size * 4) == -1) { + return -1; + } + + /* Grab individual record locks. */ + if (tdb_brlock(tdb, ltype, lock_offset(tdb->header.hash_size), 0, + flags) == -1) { + tdb_brunlock(tdb, ltype, FREELIST_TOP, + tdb->header.hash_size * 4); return -1; }