mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
tdb: workaround starvation problem in locking entire database.
We saw tdb_lockall() take 71 seconds under heavy load; this is because Linux (at least) doesn't prevent new small locks being obtained while we're waiting for a big log. The workaround is to do divide and conquer using non-blocking chainlocks: if we get down to a single chain we block. Using a simple test program where children did "hold lock for 100ms, sleep for 1 second" the time to do tdb_lockall() dropped signifiantly. There are ln(hashsize) locks taken in the contended case, but that's slow anyway. More analysis is given in my blog at http://rusty.ozlabs.org/?p=120 This may also help transactions, though in that case it's the initial read lock which uses this gradual locking routine; the update-to-write-lock code is separate and still tries to update in one go. Even though ABI doesn't change, minor version bumped so behavior change can be easily detected. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
parent
f00b61c7d4
commit
11ab43084b
60
lib/tdb/ABI/tdb-1.2.3.sigs
Normal file
60
lib/tdb/ABI/tdb-1.2.3.sigs
Normal file
@ -0,0 +1,60 @@
|
||||
tdb_add_flags: void (struct tdb_context *, unsigned int)
|
||||
tdb_append: int (struct tdb_context *, TDB_DATA, TDB_DATA)
|
||||
tdb_chainlock: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_chainlock_mark: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_chainlock_nonblock: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_chainlock_read: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_chainlock_unmark: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_chainunlock: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_chainunlock_read: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_check: int (struct tdb_context *, int (*)(TDB_DATA, TDB_DATA, void *), void *)
|
||||
tdb_close: int (struct tdb_context *)
|
||||
tdb_delete: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_dump_all: void (struct tdb_context *)
|
||||
tdb_enable_seqnum: void (struct tdb_context *)
|
||||
tdb_error: enum TDB_ERROR (struct tdb_context *)
|
||||
tdb_errorstr: const char *(struct tdb_context *)
|
||||
tdb_exists: int (struct tdb_context *, TDB_DATA)
|
||||
tdb_fd: int (struct tdb_context *)
|
||||
tdb_fetch: TDB_DATA (struct tdb_context *, TDB_DATA)
|
||||
tdb_firstkey: TDB_DATA (struct tdb_context *)
|
||||
tdb_freelist_size: int (struct tdb_context *)
|
||||
tdb_get_flags: int (struct tdb_context *)
|
||||
tdb_get_logging_private: void *(struct tdb_context *)
|
||||
tdb_get_seqnum: int (struct tdb_context *)
|
||||
tdb_hash_size: int (struct tdb_context *)
|
||||
tdb_increment_seqnum_nonblock: void (struct tdb_context *)
|
||||
tdb_lockall: int (struct tdb_context *)
|
||||
tdb_lockall_mark: int (struct tdb_context *)
|
||||
tdb_lockall_nonblock: int (struct tdb_context *)
|
||||
tdb_lockall_read: int (struct tdb_context *)
|
||||
tdb_lockall_read_nonblock: int (struct tdb_context *)
|
||||
tdb_lockall_unmark: int (struct tdb_context *)
|
||||
tdb_log_fn: tdb_log_func (struct tdb_context *)
|
||||
tdb_map_size: size_t (struct tdb_context *)
|
||||
tdb_name: const char *(struct tdb_context *)
|
||||
tdb_nextkey: TDB_DATA (struct tdb_context *, TDB_DATA)
|
||||
tdb_null: dptr = 0xXXXX, dsize = 0
|
||||
tdb_open: struct tdb_context *(const char *, int, int, int, mode_t)
|
||||
tdb_open_ex: struct tdb_context *(const char *, int, int, int, mode_t, const struct tdb_logging_context *, tdb_hash_func)
|
||||
tdb_parse_record: int (struct tdb_context *, TDB_DATA, int (*)(TDB_DATA, TDB_DATA, void *), void *)
|
||||
tdb_printfreelist: int (struct tdb_context *)
|
||||
tdb_remove_flags: void (struct tdb_context *, unsigned int)
|
||||
tdb_reopen: int (struct tdb_context *)
|
||||
tdb_reopen_all: int (int)
|
||||
tdb_repack: int (struct tdb_context *)
|
||||
tdb_set_logging_function: void (struct tdb_context *, const struct tdb_logging_context *)
|
||||
tdb_set_max_dead: void (struct tdb_context *, int)
|
||||
tdb_setalarm_sigptr: void (struct tdb_context *, volatile sig_atomic_t *)
|
||||
tdb_store: int (struct tdb_context *, TDB_DATA, TDB_DATA, int)
|
||||
tdb_transaction_cancel: int (struct tdb_context *)
|
||||
tdb_transaction_commit: int (struct tdb_context *)
|
||||
tdb_transaction_prepare_commit: int (struct tdb_context *)
|
||||
tdb_transaction_start: int (struct tdb_context *)
|
||||
tdb_transaction_start_nonblock: int (struct tdb_context *)
|
||||
tdb_traverse: int (struct tdb_context *, tdb_traverse_func, void *)
|
||||
tdb_traverse_read: int (struct tdb_context *, tdb_traverse_func, void *)
|
||||
tdb_unlockall: int (struct tdb_context *)
|
||||
tdb_unlockall_read: int (struct tdb_context *)
|
||||
tdb_validate_freelist: int (struct tdb_context *, int *)
|
||||
tdb_wipe_all: int (struct tdb_context *)
|
@ -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);
|
||||
@ -486,11 +478,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) {
|
||||
@ -520,11 +510,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;
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ AC_PREREQ(2.50)
|
||||
AC_DEFUN([SMB_MODULE_DEFAULT], [echo -n ""])
|
||||
AC_DEFUN([SMB_LIBRARY_ENABLE], [echo -n ""])
|
||||
AC_DEFUN([SMB_ENABLE], [echo -n ""])
|
||||
AC_INIT(tdb, 1.2.2)
|
||||
AC_INIT(tdb, 1.2.3)
|
||||
AC_CONFIG_SRCDIR([common/tdb.c])
|
||||
AC_CONFIG_HEADER(include/config.h)
|
||||
AC_LIBREPLACE_ALL_CHECKS
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
APPNAME = 'tdb'
|
||||
VERSION = '1.2.2'
|
||||
VERSION = '1.2.3'
|
||||
|
||||
blddir = 'bin'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user