mirror of
https://sourceware.org/git/glibc.git
synced 2024-12-23 19:30:10 +00:00
ec239360d1
* db2/Makefile (distribute): Remove files which do not exist anymore.
1035 lines
25 KiB
C
1035 lines
25 KiB
C
/*-
|
|
* See the file LICENSE for redistribution information.
|
|
*
|
|
* Copyright (c) 1996, 1997, 1998
|
|
* Sleepycat Software. All rights reserved.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#ifndef lint
|
|
static const char sccsid[] = "@(#)lock.c 10.61 (Sleepycat) 1/3/99";
|
|
#endif /* not lint */
|
|
|
|
#ifndef NO_SYSTEM_INCLUDES
|
|
#include <sys/types.h>
|
|
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#endif
|
|
|
|
#include "db_int.h"
|
|
#include "shqueue.h"
|
|
#include "db_page.h"
|
|
#include "db_shash.h"
|
|
#include "lock.h"
|
|
#include "db_am.h"
|
|
#include "txn_auto.h"
|
|
#include "txn_ext.h"
|
|
#include "common_ext.h"
|
|
|
|
static void __lock_checklocker __P((DB_LOCKTAB *, struct __db_lock *, int));
|
|
static void __lock_freeobj __P((DB_LOCKTAB *, DB_LOCKOBJ *));
|
|
static int __lock_get_internal __P((DB_LOCKTAB *, u_int32_t, DB_TXN *,
|
|
u_int32_t, const DBT *, db_lockmode_t, struct __db_lock **));
|
|
static int __lock_is_parent __P((u_int32_t, DB_TXN *));
|
|
static int __lock_promote __P((DB_LOCKTAB *, DB_LOCKOBJ *));
|
|
static int __lock_put_internal __P((DB_LOCKTAB *, struct __db_lock *, int));
|
|
static void __lock_remove_waiter
|
|
__P((DB_LOCKTAB *, DB_LOCKOBJ *, struct __db_lock *, db_status_t));
|
|
static int __lock_vec_internal __P((DB_LOCKTAB *, u_int32_t, DB_TXN *,
|
|
u_int32_t, DB_LOCKREQ *, int, DB_LOCKREQ **elistp));
|
|
|
|
int
|
|
lock_id(lt, idp)
|
|
DB_LOCKTAB *lt;
|
|
u_int32_t *idp;
|
|
{
|
|
u_int32_t id;
|
|
|
|
LOCK_PANIC_CHECK(lt);
|
|
|
|
LOCK_LOCKREGION(lt);
|
|
if (lt->region->id >= DB_LOCK_MAXID)
|
|
lt->region->id = 0;
|
|
id = ++lt->region->id;
|
|
UNLOCK_LOCKREGION(lt);
|
|
|
|
*idp = id;
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
lock_vec(lt, locker, flags, list, nlist, elistp)
|
|
DB_LOCKTAB *lt;
|
|
u_int32_t locker, flags;
|
|
int nlist;
|
|
DB_LOCKREQ *list, **elistp;
|
|
{
|
|
return (__lock_vec_internal(lt,
|
|
locker, NULL, flags, list, nlist, elistp));
|
|
}
|
|
|
|
int
|
|
lock_tvec(lt, txn, flags, list, nlist, elistp)
|
|
DB_LOCKTAB *lt;
|
|
DB_TXN *txn;
|
|
u_int32_t flags;
|
|
int nlist;
|
|
DB_LOCKREQ *list, **elistp;
|
|
{
|
|
return (__lock_vec_internal(lt,
|
|
txn->txnid, txn, flags, list, nlist, elistp));
|
|
}
|
|
|
|
static int
|
|
__lock_vec_internal(lt, locker, txn, flags, list, nlist, elistp)
|
|
DB_LOCKTAB *lt;
|
|
u_int32_t locker;
|
|
DB_TXN *txn;
|
|
u_int32_t flags;
|
|
int nlist;
|
|
DB_LOCKREQ *list, **elistp;
|
|
{
|
|
struct __db_lock *lp;
|
|
DB_LOCKOBJ *sh_obj, *sh_locker, *sh_parent;
|
|
int i, ret, run_dd;
|
|
|
|
LOCK_PANIC_CHECK(lt);
|
|
|
|
/* Validate arguments. */
|
|
if ((ret =
|
|
__db_fchk(lt->dbenv, "lock_vec", flags, DB_LOCK_NOWAIT)) != 0)
|
|
return (ret);
|
|
|
|
LOCK_LOCKREGION(lt);
|
|
|
|
if ((ret = __lock_validate_region(lt)) != 0) {
|
|
UNLOCK_LOCKREGION(lt);
|
|
return (ret);
|
|
}
|
|
|
|
ret = 0;
|
|
for (i = 0; i < nlist && ret == 0; i++) {
|
|
switch (list[i].op) {
|
|
case DB_LOCK_GET:
|
|
ret = __lock_get_internal(lt, locker, txn, flags,
|
|
list[i].obj, list[i].mode, &lp);
|
|
if (ret == 0) {
|
|
list[i].lock = LOCK_TO_OFFSET(lt, lp);
|
|
lt->region->nrequests++;
|
|
}
|
|
break;
|
|
case DB_LOCK_INHERIT:
|
|
/* Find the locker. */
|
|
if ((ret = __lock_getobj(lt, locker,
|
|
NULL, DB_LOCK_LOCKER, &sh_locker)) != 0)
|
|
break;
|
|
if (txn == NULL || txn->parent == NULL) {
|
|
ret = EINVAL;
|
|
break;
|
|
}
|
|
|
|
if ((ret = __lock_getobj(lt, txn->parent->txnid,
|
|
NULL, DB_LOCK_LOCKER, &sh_parent)) != 0)
|
|
break;
|
|
|
|
/*
|
|
* Traverse all the locks held by this locker. Remove
|
|
* the locks from the locker's list and put them on the
|
|
* parent's list.
|
|
*/
|
|
for (lp = SH_LIST_FIRST(&sh_locker->heldby, __db_lock);
|
|
lp != NULL;
|
|
lp = SH_LIST_FIRST(&sh_locker->heldby, __db_lock)) {
|
|
SH_LIST_REMOVE(lp, locker_links, __db_lock);
|
|
SH_LIST_INSERT_HEAD(&sh_parent->heldby, lp,
|
|
locker_links, __db_lock);
|
|
lp->holder = txn->parent->txnid;
|
|
}
|
|
__lock_freeobj(lt, sh_locker);
|
|
lt->region->nlockers--;
|
|
break;
|
|
case DB_LOCK_PUT:
|
|
lp = OFFSET_TO_LOCK(lt, list[i].lock);
|
|
if (lp->holder != locker) {
|
|
ret = DB_LOCK_NOTHELD;
|
|
break;
|
|
}
|
|
list[i].mode = lp->mode;
|
|
|
|
ret = __lock_put_internal(lt, lp, 0);
|
|
__lock_checklocker(lt, lp, 0);
|
|
break;
|
|
case DB_LOCK_PUT_ALL:
|
|
/* Find the locker. */
|
|
if ((ret = __lock_getobj(lt, locker,
|
|
NULL, DB_LOCK_LOCKER, &sh_locker)) != 0)
|
|
break;
|
|
|
|
for (lp = SH_LIST_FIRST(&sh_locker->heldby, __db_lock);
|
|
lp != NULL;
|
|
lp = SH_LIST_FIRST(&sh_locker->heldby, __db_lock)) {
|
|
if ((ret = __lock_put_internal(lt, lp, 1)) != 0)
|
|
break;
|
|
}
|
|
__lock_freeobj(lt, sh_locker);
|
|
lt->region->nlockers--;
|
|
break;
|
|
case DB_LOCK_PUT_OBJ:
|
|
|
|
/* Look up the object in the hash table. */
|
|
HASHLOOKUP(lt->hashtab, __db_lockobj, links,
|
|
list[i].obj, sh_obj, lt->region->table_size,
|
|
__lock_ohash, __lock_cmp);
|
|
if (sh_obj == NULL) {
|
|
ret = EINVAL;
|
|
break;
|
|
}
|
|
/*
|
|
* Release waiters first, because they won't cause
|
|
* anyone else to be awakened. If we release the
|
|
* lockers first, all the waiters get awakened
|
|
* needlessly.
|
|
*/
|
|
for (lp = SH_TAILQ_FIRST(&sh_obj->waiters, __db_lock);
|
|
lp != NULL;
|
|
lp = SH_TAILQ_FIRST(&sh_obj->waiters, __db_lock)) {
|
|
lt->region->nreleases += lp->refcount;
|
|
__lock_remove_waiter(lt, sh_obj, lp,
|
|
DB_LSTAT_NOGRANT);
|
|
__lock_checklocker(lt, lp, 1);
|
|
}
|
|
|
|
for (lp = SH_TAILQ_FIRST(&sh_obj->holders, __db_lock);
|
|
lp != NULL;
|
|
lp = SH_TAILQ_FIRST(&sh_obj->holders, __db_lock)) {
|
|
|
|
lt->region->nreleases += lp->refcount;
|
|
SH_LIST_REMOVE(lp, locker_links, __db_lock);
|
|
SH_TAILQ_REMOVE(&sh_obj->holders, lp, links,
|
|
__db_lock);
|
|
lp->status = DB_LSTAT_FREE;
|
|
SH_TAILQ_INSERT_HEAD(<->region->free_locks,
|
|
lp, links, __db_lock);
|
|
}
|
|
|
|
/* Now free the object. */
|
|
__lock_freeobj(lt, sh_obj);
|
|
break;
|
|
#ifdef DEBUG
|
|
case DB_LOCK_DUMP:
|
|
/* Find the locker. */
|
|
if ((ret = __lock_getobj(lt, locker,
|
|
NULL, DB_LOCK_LOCKER, &sh_locker)) != 0)
|
|
break;
|
|
|
|
for (lp = SH_LIST_FIRST(&sh_locker->heldby, __db_lock);
|
|
lp != NULL;
|
|
lp = SH_LIST_NEXT(lp, locker_links, __db_lock)) {
|
|
__lock_printlock(lt, lp, 1);
|
|
ret = EINVAL;
|
|
}
|
|
if (ret == 0) {
|
|
__lock_freeobj(lt, sh_locker);
|
|
lt->region->nlockers--;
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
ret = EINVAL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (lt->region->need_dd && lt->region->detect != DB_LOCK_NORUN) {
|
|
run_dd = 1;
|
|
lt->region->need_dd = 0;
|
|
} else
|
|
run_dd = 0;
|
|
|
|
UNLOCK_LOCKREGION(lt);
|
|
|
|
if (ret == 0 && run_dd)
|
|
lock_detect(lt, 0, lt->region->detect);
|
|
|
|
if (elistp && ret != 0)
|
|
*elistp = &list[i - 1];
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
lock_get(lt, locker, flags, obj, lock_mode, lock)
|
|
DB_LOCKTAB *lt;
|
|
u_int32_t locker, flags;
|
|
const DBT *obj;
|
|
db_lockmode_t lock_mode;
|
|
DB_LOCK *lock;
|
|
{
|
|
struct __db_lock *lockp;
|
|
int ret;
|
|
|
|
LOCK_PANIC_CHECK(lt);
|
|
|
|
/* Validate arguments. */
|
|
if ((ret = __db_fchk(lt->dbenv,
|
|
"lock_get", flags, DB_LOCK_NOWAIT | DB_LOCK_UPGRADE)) != 0)
|
|
return (ret);
|
|
|
|
LOCK_LOCKREGION(lt);
|
|
|
|
if ((ret = __lock_validate_region(lt)) == 0) {
|
|
if (LF_ISSET(DB_LOCK_UPGRADE))
|
|
lockp = OFFSET_TO_LOCK(lt, *lock);
|
|
|
|
if ((ret = __lock_get_internal(lt,
|
|
locker, NULL, flags, obj, lock_mode, &lockp)) == 0) {
|
|
if (!LF_ISSET(DB_LOCK_UPGRADE))
|
|
*lock = LOCK_TO_OFFSET(lt, lockp);
|
|
lt->region->nrequests++;
|
|
}
|
|
}
|
|
|
|
UNLOCK_LOCKREGION(lt);
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
lock_tget(lt, txn, flags, obj, lock_mode, lock)
|
|
DB_LOCKTAB *lt;
|
|
DB_TXN *txn;
|
|
u_int32_t flags;
|
|
const DBT *obj;
|
|
db_lockmode_t lock_mode;
|
|
DB_LOCK *lock;
|
|
{
|
|
struct __db_lock *lockp;
|
|
int ret;
|
|
|
|
LOCK_PANIC_CHECK(lt);
|
|
|
|
/* Validate arguments. */
|
|
if ((ret = __db_fchk(lt->dbenv,
|
|
"lock_get", flags, DB_LOCK_NOWAIT | DB_LOCK_UPGRADE)) != 0)
|
|
return (ret);
|
|
|
|
LOCK_LOCKREGION(lt);
|
|
|
|
if ((ret = __lock_validate_region(lt)) == 0) {
|
|
if (LF_ISSET(DB_LOCK_UPGRADE))
|
|
lockp = OFFSET_TO_LOCK(lt, *lock);
|
|
|
|
if ((ret = __lock_get_internal(lt,
|
|
txn->txnid, txn, flags, obj, lock_mode, &lockp)) == 0) {
|
|
if (!LF_ISSET(DB_LOCK_UPGRADE))
|
|
*lock = LOCK_TO_OFFSET(lt, lockp);
|
|
lt->region->nrequests++;
|
|
}
|
|
}
|
|
|
|
UNLOCK_LOCKREGION(lt);
|
|
return (ret);
|
|
}
|
|
int
|
|
lock_put(lt, lock)
|
|
DB_LOCKTAB *lt;
|
|
DB_LOCK lock;
|
|
{
|
|
struct __db_lock *lockp;
|
|
int ret, run_dd;
|
|
|
|
LOCK_PANIC_CHECK(lt);
|
|
|
|
LOCK_LOCKREGION(lt);
|
|
|
|
if ((ret = __lock_validate_region(lt)) != 0)
|
|
return (ret);
|
|
else {
|
|
lockp = OFFSET_TO_LOCK(lt, lock);
|
|
ret = __lock_put_internal(lt, lockp, 0);
|
|
}
|
|
|
|
__lock_checklocker(lt, lockp, 0);
|
|
|
|
if (lt->region->need_dd && lt->region->detect != DB_LOCK_NORUN) {
|
|
run_dd = 1;
|
|
lt->region->need_dd = 0;
|
|
} else
|
|
run_dd = 0;
|
|
|
|
UNLOCK_LOCKREGION(lt);
|
|
|
|
if (ret == 0 && run_dd)
|
|
lock_detect(lt, 0, lt->region->detect);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
static int
|
|
__lock_put_internal(lt, lockp, do_all)
|
|
DB_LOCKTAB *lt;
|
|
struct __db_lock *lockp;
|
|
int do_all;
|
|
{
|
|
DB_LOCKOBJ *sh_obj;
|
|
int state_changed;
|
|
|
|
if (lockp->refcount == 0 || (lockp->status != DB_LSTAT_HELD &&
|
|
lockp->status != DB_LSTAT_WAITING) || lockp->obj == 0) {
|
|
__db_err(lt->dbenv, "lock_put: invalid lock %lu",
|
|
(u_long)((u_int8_t *)lockp - (u_int8_t *)lt->region));
|
|
return (EINVAL);
|
|
}
|
|
|
|
if (do_all)
|
|
lt->region->nreleases += lockp->refcount;
|
|
else
|
|
lt->region->nreleases++;
|
|
if (do_all == 0 && lockp->refcount > 1) {
|
|
lockp->refcount--;
|
|
return (0);
|
|
}
|
|
|
|
/* Get the object associated with this lock. */
|
|
sh_obj = (DB_LOCKOBJ *)((u_int8_t *)lockp + lockp->obj);
|
|
|
|
/* Remove lock from locker list. */
|
|
SH_LIST_REMOVE(lockp, locker_links, __db_lock);
|
|
|
|
/* Remove this lock from its holders/waitlist. */
|
|
if (lockp->status != DB_LSTAT_HELD)
|
|
__lock_remove_waiter(lt, sh_obj, lockp, DB_LSTAT_FREE);
|
|
else
|
|
SH_TAILQ_REMOVE(&sh_obj->holders, lockp, links, __db_lock);
|
|
|
|
state_changed = __lock_promote(lt, sh_obj);
|
|
|
|
/* Check if object should be reclaimed. */
|
|
if (SH_TAILQ_FIRST(&sh_obj->holders, __db_lock) == NULL) {
|
|
HASHREMOVE_EL(lt->hashtab, __db_lockobj,
|
|
links, sh_obj, lt->region->table_size, __lock_lhash);
|
|
if (sh_obj->lockobj.size > sizeof(sh_obj->objdata))
|
|
__db_shalloc_free(lt->mem,
|
|
SH_DBT_PTR(&sh_obj->lockobj));
|
|
SH_TAILQ_INSERT_HEAD(<->region->free_objs, sh_obj, links,
|
|
__db_lockobj);
|
|
state_changed = 1;
|
|
}
|
|
|
|
/* Free lock. */
|
|
lockp->status = DB_LSTAT_FREE;
|
|
SH_TAILQ_INSERT_HEAD(<->region->free_locks, lockp, links, __db_lock);
|
|
|
|
/*
|
|
* If we did not promote anyone; we need to run the deadlock
|
|
* detector again.
|
|
*/
|
|
if (state_changed == 0)
|
|
lt->region->need_dd = 1;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
__lock_get_internal(lt, locker, txn, flags, obj, lock_mode, lockp)
|
|
DB_LOCKTAB *lt;
|
|
u_int32_t locker, flags;
|
|
DB_TXN *txn;
|
|
const DBT *obj;
|
|
db_lockmode_t lock_mode;
|
|
struct __db_lock **lockp;
|
|
{
|
|
struct __db_lock *newl, *lp;
|
|
DB_LOCKOBJ *sh_obj, *sh_locker;
|
|
DB_LOCKREGION *lrp;
|
|
size_t newl_off;
|
|
int ihold, no_dd, ret;
|
|
|
|
no_dd = ret = 0;
|
|
|
|
/*
|
|
* Check that lock mode is valid.
|
|
*/
|
|
lrp = lt->region;
|
|
if ((u_int32_t)lock_mode >= lrp->nmodes) {
|
|
__db_err(lt->dbenv,
|
|
"lock_get: invalid lock mode %lu\n", (u_long)lock_mode);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Allocate a new lock. Optimize for the common case of a grant. */
|
|
if ((newl = SH_TAILQ_FIRST(&lrp->free_locks, __db_lock)) == NULL) {
|
|
if ((ret = __lock_grow_region(lt, DB_LOCK_LOCK, 0)) != 0)
|
|
return (ret);
|
|
lrp = lt->region;
|
|
newl = SH_TAILQ_FIRST(&lrp->free_locks, __db_lock);
|
|
}
|
|
newl_off = LOCK_TO_OFFSET(lt, newl);
|
|
|
|
/* Optimize for common case of granting a lock. */
|
|
SH_TAILQ_REMOVE(&lrp->free_locks, newl, links, __db_lock);
|
|
|
|
newl->mode = lock_mode;
|
|
newl->status = DB_LSTAT_HELD;
|
|
newl->holder = locker;
|
|
newl->refcount = 1;
|
|
|
|
if ((ret = __lock_getobj(lt, 0, obj, DB_LOCK_OBJTYPE, &sh_obj)) != 0)
|
|
return (ret);
|
|
|
|
lrp = lt->region; /* getobj might have grown */
|
|
newl = OFFSET_TO_LOCK(lt, newl_off);
|
|
|
|
/* Now make new lock point to object */
|
|
newl->obj = SH_PTR_TO_OFF(newl, sh_obj);
|
|
|
|
/*
|
|
* Now we have a lock and an object and we need to see if we should
|
|
* grant the lock. We use a FIFO ordering so we can only grant a
|
|
* new lock if it does not conflict with anyone on the holders list
|
|
* OR anyone on the waiters list. The reason that we don't grant if
|
|
* there's a conflict is that this can lead to starvation (a writer
|
|
* waiting on a popularly read item will never be granted). The
|
|
* downside of this is that a waiting reader can prevent an upgrade
|
|
* from reader to writer, which is not uncommon.
|
|
*
|
|
* There is one exception to the no-conflict rule. If a lock is held
|
|
* by the requesting locker AND the new lock does not conflict with
|
|
* any other holders, then we grant the lock. The most common place
|
|
* this happens is when the holder has a WRITE lock and a READ lock
|
|
* request comes in for the same locker. If we do not grant the read
|
|
* lock, then we guarantee deadlock.
|
|
*
|
|
* In case of conflict, we put the new lock on the end of the waiters
|
|
* list, unless we are upgrading in which case the locker goes on the
|
|
* front of the list.
|
|
*/
|
|
ihold = 0;
|
|
for (lp = SH_TAILQ_FIRST(&sh_obj->holders, __db_lock);
|
|
lp != NULL;
|
|
lp = SH_TAILQ_NEXT(lp, links, __db_lock)) {
|
|
if (locker == lp->holder ||
|
|
__lock_is_parent(lp->holder, txn)) {
|
|
if (lp->mode == lock_mode &&
|
|
lp->status == DB_LSTAT_HELD) {
|
|
if (LF_ISSET(DB_LOCK_UPGRADE))
|
|
goto upgrade;
|
|
|
|
/*
|
|
* Lock is held, so we can increment the
|
|
* reference count and return this lock.
|
|
*/
|
|
lp->refcount++;
|
|
*lockp = lp;
|
|
SH_TAILQ_INSERT_HEAD(&lrp->free_locks,
|
|
newl, links, __db_lock);
|
|
return (0);
|
|
} else
|
|
ihold = 1;
|
|
} else if (CONFLICTS(lt, lp->mode, lock_mode))
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If we are upgrading, then there are two scenarios. Either
|
|
* we had no conflicts, so we can do the upgrade. Or, there
|
|
* is a conflict and we should wait at the HEAD of the waiters
|
|
* list.
|
|
*/
|
|
if (LF_ISSET(DB_LOCK_UPGRADE)) {
|
|
if (lp == NULL)
|
|
goto upgrade;
|
|
|
|
/* There was a conflict, wait. */
|
|
SH_TAILQ_INSERT_HEAD(&sh_obj->waiters, newl, links, __db_lock);
|
|
goto wait;
|
|
}
|
|
|
|
if (lp == NULL && !ihold)
|
|
for (lp = SH_TAILQ_FIRST(&sh_obj->waiters, __db_lock);
|
|
lp != NULL;
|
|
lp = SH_TAILQ_NEXT(lp, links, __db_lock)) {
|
|
if (CONFLICTS(lt, lp->mode, lock_mode) &&
|
|
locker != lp->holder)
|
|
break;
|
|
}
|
|
if (lp == NULL)
|
|
SH_TAILQ_INSERT_TAIL(&sh_obj->holders, newl, links);
|
|
else if (!(flags & DB_LOCK_NOWAIT))
|
|
SH_TAILQ_INSERT_TAIL(&sh_obj->waiters, newl, links);
|
|
else {
|
|
/* Free the lock and return an error. */
|
|
newl->status = DB_LSTAT_FREE;
|
|
SH_TAILQ_INSERT_HEAD(&lrp->free_locks, newl, links, __db_lock);
|
|
return (DB_LOCK_NOTGRANTED);
|
|
}
|
|
|
|
/*
|
|
* Now, insert the lock onto its locker's list. If the locker does
|
|
* not currently hold any locks, there's no reason to run a deadlock
|
|
* detector, save that information.
|
|
*/
|
|
if ((ret =
|
|
__lock_getobj(lt, locker, NULL, DB_LOCK_LOCKER, &sh_locker)) != 0)
|
|
return (ret);
|
|
no_dd = SH_LIST_FIRST(&sh_locker->heldby, __db_lock) == NULL;
|
|
|
|
lrp = lt->region;
|
|
SH_LIST_INSERT_HEAD(&sh_locker->heldby, newl, locker_links, __db_lock);
|
|
|
|
if (lp != NULL) {
|
|
/*
|
|
* This is really a blocker for the process, so initialize it
|
|
* set. That way the current process will block when it tries
|
|
* to get it and the waking process will release it.
|
|
*/
|
|
wait: (void)__db_mutex_init(&newl->mutex,
|
|
MUTEX_LOCK_OFFSET(lt->region, &newl->mutex));
|
|
(void)__db_mutex_lock(&newl->mutex, lt->reginfo.fd);
|
|
|
|
newl->status = DB_LSTAT_WAITING;
|
|
lrp->nconflicts++;
|
|
|
|
/*
|
|
* We are about to wait; must release the region mutex. Then,
|
|
* when we wakeup, we need to reacquire the region mutex before
|
|
* continuing.
|
|
*/
|
|
if (lrp->detect == DB_LOCK_NORUN)
|
|
lt->region->need_dd = 1;
|
|
UNLOCK_LOCKREGION(lt);
|
|
|
|
/*
|
|
* We are about to wait; before waiting, see if the deadlock
|
|
* detector should be run.
|
|
*/
|
|
if (lrp->detect != DB_LOCK_NORUN && !no_dd)
|
|
(void)lock_detect(lt, 0, lrp->detect);
|
|
|
|
(void)__db_mutex_lock(&newl->mutex, lt->reginfo.fd);
|
|
|
|
LOCK_LOCKREGION(lt);
|
|
if (newl->status != DB_LSTAT_PENDING) {
|
|
/*
|
|
* If this lock errored due to a deadlock, then
|
|
* we have waiters that require promotion.
|
|
*/
|
|
if (newl->status == DB_LSTAT_ABORTED)
|
|
(void)__lock_promote(lt, sh_obj);
|
|
/* Return to free list. */
|
|
__lock_checklocker(lt, newl, 0);
|
|
SH_TAILQ_INSERT_HEAD(&lrp->free_locks, newl, links,
|
|
__db_lock);
|
|
switch (newl->status) {
|
|
case DB_LSTAT_ABORTED:
|
|
ret = DB_LOCK_DEADLOCK;
|
|
break;
|
|
case DB_LSTAT_NOGRANT:
|
|
ret = DB_LOCK_NOTGRANTED;
|
|
break;
|
|
default:
|
|
ret = EINVAL;
|
|
break;
|
|
}
|
|
newl->status = DB_LSTAT_FREE;
|
|
newl = NULL;
|
|
} else if (LF_ISSET(DB_LOCK_UPGRADE)) {
|
|
/*
|
|
* The lock that was just granted got put on the
|
|
* holders list. Since we're upgrading some other
|
|
* lock, we've got to remove it here.
|
|
*/
|
|
SH_TAILQ_REMOVE(&sh_obj->holders,
|
|
newl, links, __db_lock);
|
|
goto upgrade;
|
|
} else
|
|
newl->status = DB_LSTAT_HELD;
|
|
}
|
|
|
|
*lockp = newl;
|
|
return (ret);
|
|
|
|
upgrade:
|
|
/*
|
|
* This was an upgrade, so return the new lock to the free list and
|
|
* upgrade the mode.
|
|
*/
|
|
(*lockp)->mode = lock_mode;
|
|
newl->status = DB_LSTAT_FREE;
|
|
SH_TAILQ_INSERT_HEAD(&lrp->free_locks, newl, links, __db_lock);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* __lock_is_locked --
|
|
*
|
|
* PUBLIC: int __lock_is_locked
|
|
* PUBLIC: __P((DB_LOCKTAB *, u_int32_t, DBT *, db_lockmode_t));
|
|
*/
|
|
int
|
|
__lock_is_locked(lt, locker, dbt, mode)
|
|
DB_LOCKTAB *lt;
|
|
u_int32_t locker;
|
|
DBT *dbt;
|
|
db_lockmode_t mode;
|
|
{
|
|
struct __db_lock *lp;
|
|
DB_LOCKOBJ *sh_obj;
|
|
DB_LOCKREGION *lrp;
|
|
|
|
lrp = lt->region;
|
|
|
|
/* Look up the object in the hash table. */
|
|
HASHLOOKUP(lt->hashtab, __db_lockobj, links,
|
|
dbt, sh_obj, lrp->table_size, __lock_ohash, __lock_cmp);
|
|
if (sh_obj == NULL)
|
|
return (0);
|
|
|
|
for (lp = SH_TAILQ_FIRST(&sh_obj->holders, __db_lock);
|
|
lp != NULL;
|
|
lp = SH_TAILQ_FIRST(&sh_obj->holders, __db_lock)) {
|
|
if (lp->holder == locker && lp->mode == mode)
|
|
return (1);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* __lock_printlock --
|
|
*
|
|
* PUBLIC: void __lock_printlock __P((DB_LOCKTAB *, struct __db_lock *, int));
|
|
*/
|
|
void
|
|
__lock_printlock(lt, lp, ispgno)
|
|
DB_LOCKTAB *lt;
|
|
struct __db_lock *lp;
|
|
int ispgno;
|
|
{
|
|
DB_LOCKOBJ *lockobj;
|
|
db_pgno_t pgno;
|
|
size_t obj;
|
|
u_int8_t *ptr;
|
|
const char *mode, *status;
|
|
|
|
switch (lp->mode) {
|
|
case DB_LOCK_IREAD:
|
|
mode = "IREAD";
|
|
break;
|
|
case DB_LOCK_IWR:
|
|
mode = "IWR";
|
|
break;
|
|
case DB_LOCK_IWRITE:
|
|
mode = "IWRITE";
|
|
break;
|
|
case DB_LOCK_NG:
|
|
mode = "NG";
|
|
break;
|
|
case DB_LOCK_READ:
|
|
mode = "READ";
|
|
break;
|
|
case DB_LOCK_WRITE:
|
|
mode = "WRITE";
|
|
break;
|
|
default:
|
|
mode = "UNKNOWN";
|
|
break;
|
|
}
|
|
switch (lp->status) {
|
|
case DB_LSTAT_ABORTED:
|
|
status = "ABORT";
|
|
break;
|
|
case DB_LSTAT_ERR:
|
|
status = "ERROR";
|
|
break;
|
|
case DB_LSTAT_FREE:
|
|
status = "FREE";
|
|
break;
|
|
case DB_LSTAT_HELD:
|
|
status = "HELD";
|
|
break;
|
|
case DB_LSTAT_NOGRANT:
|
|
status = "NONE";
|
|
break;
|
|
case DB_LSTAT_WAITING:
|
|
status = "WAIT";
|
|
break;
|
|
case DB_LSTAT_PENDING:
|
|
status = "PENDING";
|
|
break;
|
|
default:
|
|
status = "UNKNOWN";
|
|
break;
|
|
}
|
|
printf("\t%lx\t%s\t%lu\t%s\t",
|
|
(u_long)lp->holder, mode, (u_long)lp->refcount, status);
|
|
|
|
lockobj = (DB_LOCKOBJ *)((u_int8_t *)lp + lp->obj);
|
|
ptr = SH_DBT_PTR(&lockobj->lockobj);
|
|
if (ispgno) {
|
|
/* Assume this is a DBT lock. */
|
|
memcpy(&pgno, ptr, sizeof(db_pgno_t));
|
|
printf("page %lu\n", (u_long)pgno);
|
|
} else {
|
|
obj = (u_int8_t *)lp + lp->obj - (u_int8_t *)lt->region;
|
|
printf("0x%lx ", (u_long)obj);
|
|
__db_pr(ptr, lockobj->lockobj.size);
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* PUBLIC: int __lock_getobj __P((DB_LOCKTAB *,
|
|
* PUBLIC: u_int32_t, const DBT *, u_int32_t type, DB_LOCKOBJ **));
|
|
*/
|
|
int
|
|
__lock_getobj(lt, locker, dbt, type, objp)
|
|
DB_LOCKTAB *lt;
|
|
u_int32_t locker, type;
|
|
const DBT *dbt;
|
|
DB_LOCKOBJ **objp;
|
|
{
|
|
DB_LOCKREGION *lrp;
|
|
DB_LOCKOBJ *sh_obj;
|
|
u_int32_t obj_size;
|
|
int ret;
|
|
void *p, *src;
|
|
|
|
lrp = lt->region;
|
|
|
|
/* Look up the object in the hash table. */
|
|
if (type == DB_LOCK_OBJTYPE) {
|
|
HASHLOOKUP(lt->hashtab, __db_lockobj, links, dbt, sh_obj,
|
|
lrp->table_size, __lock_ohash, __lock_cmp);
|
|
obj_size = dbt->size;
|
|
} else {
|
|
HASHLOOKUP(lt->hashtab, __db_lockobj, links, locker,
|
|
sh_obj, lrp->table_size, __lock_locker_hash,
|
|
__lock_locker_cmp);
|
|
obj_size = sizeof(locker);
|
|
}
|
|
|
|
/*
|
|
* If we found the object, then we can just return it. If
|
|
* we didn't find the object, then we need to create it.
|
|
*/
|
|
if (sh_obj == NULL) {
|
|
/* Create new object and then insert it into hash table. */
|
|
if ((sh_obj =
|
|
SH_TAILQ_FIRST(&lrp->free_objs, __db_lockobj)) == NULL) {
|
|
if ((ret = __lock_grow_region(lt, DB_LOCK_OBJ, 0)) != 0)
|
|
return (ret);
|
|
lrp = lt->region;
|
|
sh_obj = SH_TAILQ_FIRST(&lrp->free_objs, __db_lockobj);
|
|
}
|
|
|
|
/*
|
|
* If we can fit this object in the structure, do so instead
|
|
* of shalloc-ing space for it.
|
|
*/
|
|
if (obj_size <= sizeof(sh_obj->objdata))
|
|
p = sh_obj->objdata;
|
|
else
|
|
if ((ret =
|
|
__db_shalloc(lt->mem, obj_size, 0, &p)) != 0) {
|
|
if ((ret = __lock_grow_region(lt,
|
|
DB_LOCK_MEM, obj_size)) != 0)
|
|
return (ret);
|
|
lrp = lt->region;
|
|
/* Reacquire the head of the list. */
|
|
sh_obj = SH_TAILQ_FIRST(&lrp->free_objs,
|
|
__db_lockobj);
|
|
(void)__db_shalloc(lt->mem, obj_size, 0, &p);
|
|
}
|
|
|
|
src = type == DB_LOCK_OBJTYPE ? dbt->data : (void *)&locker;
|
|
memcpy(p, src, obj_size);
|
|
|
|
sh_obj->type = type;
|
|
SH_TAILQ_REMOVE(&lrp->free_objs, sh_obj, links, __db_lockobj);
|
|
|
|
SH_TAILQ_INIT(&sh_obj->waiters);
|
|
if (type == DB_LOCK_LOCKER)
|
|
SH_LIST_INIT(&sh_obj->heldby);
|
|
else
|
|
SH_TAILQ_INIT(&sh_obj->holders);
|
|
sh_obj->lockobj.size = obj_size;
|
|
sh_obj->lockobj.off = SH_PTR_TO_OFF(&sh_obj->lockobj, p);
|
|
|
|
HASHINSERT(lt->hashtab,
|
|
__db_lockobj, links, sh_obj, lrp->table_size, __lock_lhash);
|
|
|
|
if (type == DB_LOCK_LOCKER)
|
|
lrp->nlockers++;
|
|
}
|
|
|
|
*objp = sh_obj;
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Any lock on the waitlist has a process waiting for it. Therefore, we
|
|
* can't return the lock to the freelist immediately. Instead, we can
|
|
* remove the lock from the list of waiters, set the status field of the
|
|
* lock, and then let the process waking up return the lock to the
|
|
* free list.
|
|
*/
|
|
static void
|
|
__lock_remove_waiter(lt, sh_obj, lockp, status)
|
|
DB_LOCKTAB *lt;
|
|
DB_LOCKOBJ *sh_obj;
|
|
struct __db_lock *lockp;
|
|
db_status_t status;
|
|
{
|
|
SH_TAILQ_REMOVE(&sh_obj->waiters, lockp, links, __db_lock);
|
|
lockp->status = status;
|
|
|
|
/* Wake whoever is waiting on this lock. */
|
|
(void)__db_mutex_unlock(&lockp->mutex, lt->reginfo.fd);
|
|
}
|
|
|
|
static void
|
|
__lock_checklocker(lt, lockp, do_remove)
|
|
DB_LOCKTAB *lt;
|
|
struct __db_lock *lockp;
|
|
int do_remove;
|
|
{
|
|
DB_LOCKOBJ *sh_locker;
|
|
|
|
if (do_remove)
|
|
SH_LIST_REMOVE(lockp, locker_links, __db_lock);
|
|
|
|
/* if the locker list is NULL, free up the object. */
|
|
if (__lock_getobj(lt, lockp->holder, NULL, DB_LOCK_LOCKER, &sh_locker)
|
|
== 0 && SH_LIST_FIRST(&sh_locker->heldby, __db_lock) == NULL) {
|
|
__lock_freeobj(lt, sh_locker);
|
|
lt->region->nlockers--;
|
|
}
|
|
}
|
|
|
|
static void
|
|
__lock_freeobj(lt, obj)
|
|
DB_LOCKTAB *lt;
|
|
DB_LOCKOBJ *obj;
|
|
{
|
|
HASHREMOVE_EL(lt->hashtab,
|
|
__db_lockobj, links, obj, lt->region->table_size, __lock_lhash);
|
|
if (obj->lockobj.size > sizeof(obj->objdata))
|
|
__db_shalloc_free(lt->mem, SH_DBT_PTR(&obj->lockobj));
|
|
SH_TAILQ_INSERT_HEAD(<->region->free_objs, obj, links, __db_lockobj);
|
|
}
|
|
|
|
/*
|
|
* __lock_downgrade --
|
|
* Used by the concurrent access product to downgrade write locks
|
|
* back to iwrite locks.
|
|
*
|
|
* PUBLIC: int __lock_downgrade __P((DB_LOCKTAB *,
|
|
* PUBLIC: DB_LOCK, db_lockmode_t, u_int32_t));
|
|
*/
|
|
int
|
|
__lock_downgrade(lt, lock, new_mode, flags)
|
|
DB_LOCKTAB *lt;
|
|
DB_LOCK lock;
|
|
db_lockmode_t new_mode;
|
|
u_int32_t flags;
|
|
{
|
|
struct __db_lock *lockp;
|
|
DB_LOCKOBJ *obj;
|
|
int ret;
|
|
|
|
COMPQUIET(flags, 0);
|
|
LOCK_PANIC_CHECK(lt);
|
|
LOCK_LOCKREGION(lt);
|
|
|
|
if ((ret = __lock_validate_region(lt)) == 0) {
|
|
lockp = OFFSET_TO_LOCK(lt, lock);
|
|
lockp->mode = new_mode;
|
|
|
|
/* Get the object associated with this lock. */
|
|
obj = (DB_LOCKOBJ *)((u_int8_t *)lockp + lockp->obj);
|
|
(void)__lock_promote(lt, obj);
|
|
++lt->region->nreleases;
|
|
}
|
|
|
|
UNLOCK_LOCKREGION(lt);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* __lock_promote --
|
|
*
|
|
* Look through the waiters and holders lists and decide which (if any)
|
|
* locks can be promoted. Promote any that are eligible.
|
|
*/
|
|
static int
|
|
__lock_promote(lt, obj)
|
|
DB_LOCKTAB *lt;
|
|
DB_LOCKOBJ *obj;
|
|
{
|
|
struct __db_lock *lp_w, *lp_h, *next_waiter;
|
|
int state_changed, waiter_is_txn;
|
|
|
|
/*
|
|
* We need to do lock promotion. We also need to determine if
|
|
* we're going to need to run the deadlock detector again. If
|
|
* we release locks, and there are waiters, but no one gets promoted,
|
|
* then we haven't fundamentally changed the lockmgr state, so
|
|
* we may still have a deadlock and we have to run again. However,
|
|
* if there were no waiters, or we actually promoted someone, then
|
|
* we are OK and we don't have to run it immediately.
|
|
*
|
|
* During promotion, we look for state changes so we can return
|
|
* this information to the caller.
|
|
*/
|
|
for (lp_w = SH_TAILQ_FIRST(&obj->waiters, __db_lock),
|
|
state_changed = lp_w == NULL;
|
|
lp_w != NULL;
|
|
lp_w = next_waiter) {
|
|
waiter_is_txn = TXN_IS_HOLDING(lp_w);
|
|
next_waiter = SH_TAILQ_NEXT(lp_w, links, __db_lock);
|
|
for (lp_h = SH_TAILQ_FIRST(&obj->holders, __db_lock);
|
|
lp_h != NULL;
|
|
lp_h = SH_TAILQ_NEXT(lp_h, links, __db_lock)) {
|
|
if (CONFLICTS(lt, lp_h->mode, lp_w->mode) &&
|
|
lp_h->holder != lp_w->holder &&
|
|
!(waiter_is_txn &&
|
|
TXN_IS_HOLDING(lp_h) &&
|
|
__txn_is_ancestor(lt->dbenv->tx_info,
|
|
lp_h->txnoff, lp_w->txnoff)))
|
|
break;
|
|
}
|
|
if (lp_h != NULL) /* Found a conflict. */
|
|
break;
|
|
|
|
/* No conflict, promote the waiting lock. */
|
|
SH_TAILQ_REMOVE(&obj->waiters, lp_w, links, __db_lock);
|
|
lp_w->status = DB_LSTAT_PENDING;
|
|
SH_TAILQ_INSERT_TAIL(&obj->holders, lp_w, links);
|
|
|
|
/* Wake up waiter. */
|
|
(void)__db_mutex_unlock(&lp_w->mutex, lt->reginfo.fd);
|
|
state_changed = 1;
|
|
}
|
|
|
|
return (state_changed);
|
|
}
|
|
|
|
static int
|
|
__lock_is_parent(locker, txn)
|
|
u_int32_t locker;
|
|
DB_TXN *txn;
|
|
{
|
|
DB_TXN *t;
|
|
|
|
if (txn == NULL)
|
|
return (0);
|
|
|
|
for (t = txn->parent; t != NULL; t = t->parent)
|
|
if (t->txnid == locker)
|
|
return (1);
|
|
|
|
return (0);
|
|
}
|