📄 lock.c
字号:
if (run_dd) (void)dbenv->lock_detect(dbenv, 0, region->detect, &did_abort); if (ret != 0 && elistp != NULL) *elistp = &list[i - 1]; return (ret);}/* * Lock acquisition routines. There are two library interfaces: * * __lock_get -- * original lock get interface that takes a locker id. * * All the work for lock_get (and for the GET option of lock_vec) is done * inside of lock_get_internal. * * PUBLIC: int __lock_get __P((DB_ENV *, * PUBLIC: u_int32_t, u_int32_t, const DBT *, db_lockmode_t, DB_LOCK *)); */int__lock_get(dbenv, locker, flags, obj, lock_mode, lock) DB_ENV *dbenv; u_int32_t locker, flags; const DBT *obj; db_lockmode_t lock_mode; DB_LOCK *lock;{ int ret; PANIC_CHECK(dbenv); ENV_REQUIRES_CONFIG(dbenv, dbenv->lk_handle, "DB_ENV->lock_get", DB_INIT_LOCK); if (IS_RECOVERING(dbenv)) { LOCK_INIT(*lock); return (0); } /* Validate arguments. */ if ((ret = __db_fchk(dbenv, "DB_ENV->lock_get", flags, DB_LOCK_NOWAIT | DB_LOCK_UPGRADE | DB_LOCK_SWITCH)) != 0) return (ret); LOCKREGION(dbenv, (DB_LOCKTAB *)dbenv->lk_handle); ret = __lock_get_internal(dbenv->lk_handle, locker, flags, obj, lock_mode, 0, lock); UNLOCKREGION(dbenv, (DB_LOCKTAB *)dbenv->lk_handle); return (ret);}static int__lock_get_internal(lt, locker, flags, obj, lock_mode, timeout, lock) DB_LOCKTAB *lt; u_int32_t locker, flags; const DBT *obj; db_lockmode_t lock_mode; db_timeout_t timeout; DB_LOCK *lock;{ struct __db_lock *newl, *lp, *wwrite; DB_ENV *dbenv; DB_LOCKER *sh_locker; DB_LOCKOBJ *sh_obj; DB_LOCKREGION *region; u_int32_t locker_ndx, obj_ndx; int did_abort, ihold, on_locker_list, no_dd, ret; dbenv = lt->dbenv; region = lt->reginfo.primary; on_locker_list = no_dd = ret = 0; /* Check if locks have been globally turned off. */ if (F_ISSET(dbenv, DB_ENV_NOLOCKING)) return (0); /* * If we are not going to reuse this lock, initialize the offset to * invalid so that if we fail it will not look like a valid lock. */ if (!LF_ISSET(DB_LOCK_UPGRADE | DB_LOCK_SWITCH)) LOCK_INIT(*lock); /* Check that the lock mode is valid. */ if ((u_int32_t)lock_mode >= region->stat.st_nmodes) { __db_err(dbenv, "DB_ENV->lock_get: invalid lock mode %lu", (u_long)lock_mode); return (EINVAL); } /* Allocate a new lock. Optimize for the common case of a grant. */ region->stat.st_nrequests++; if ((newl = SH_TAILQ_FIRST(®ion->free_locks, __db_lock)) != NULL) SH_TAILQ_REMOVE(®ion->free_locks, newl, links, __db_lock); if (newl == NULL) { __db_err(dbenv, __db_lock_err, "locks"); return (ENOMEM); } if (++region->stat.st_nlocks > region->stat.st_maxnlocks) region->stat.st_maxnlocks = region->stat.st_nlocks; if (obj == NULL) { DB_ASSERT(LOCK_ISSET(*lock)); lp = (struct __db_lock *)R_ADDR(<->reginfo, lock->off); sh_obj = (DB_LOCKOBJ *) ((u_int8_t *)lp + lp->obj); } else { /* Allocate a shared memory new object. */ OBJECT_LOCK(lt, region, obj, lock->ndx); if ((ret = __lock_getobj(lt, obj, lock->ndx, 1, &sh_obj)) != 0) goto err; } /* Get the locker, we may need it to find our parent. */ LOCKER_LOCK(lt, region, locker, locker_ndx); if ((ret = __lock_getlocker(lt, locker, locker_ndx, locker > DB_LOCK_MAXID ? 1 : 0, &sh_locker)) != 0) { /* * XXX We cannot tell if we created the object or not, * so we don't kow if we should free it or not. */ goto err; } if (sh_locker == NULL) { __db_err(dbenv, "Locker does not exist"); ret = EINVAL; goto err; } /* * 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; lp = NULL; if (LF_ISSET(DB_LOCK_SWITCH)) goto put_lock; wwrite = NULL; for (lp = SH_TAILQ_FIRST(&sh_obj->holders, __db_lock); lp != NULL; lp = SH_TAILQ_NEXT(lp, links, __db_lock)) { if (locker == lp->holder) { 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. * We do not count reference increments * towards the locks held by the locker. */ lp->refcount++; lock->off = R_OFFSET(<->reginfo, lp); lock->gen = lp->gen; lock->mode = lp->mode; ret = 0; goto done; } else { ihold = 1; if (lock_mode == DB_LOCK_WRITE && lp->mode == DB_LOCK_WWRITE) wwrite = lp; } } else if (__lock_is_parent(lt, lp->holder, sh_locker)) ihold = 1; else if (CONFLICTS(lt, region, lp->mode, lock_mode)) break; } /* * If we are looking to upgrade a WWRITE to a WRITE lock * and there were no conflicting locks then we can just * upgrade this lock to the one we want. */ if (wwrite != NULL && lp == NULL) { lp = wwrite; lp->mode = lock_mode; lp->refcount++; lock->off = R_OFFSET(<->reginfo, lp); lock->gen = lp->gen; lock->mode = lp->mode; ret = 0; goto done; } /* * Make the new lock point to the new object, initialize fields. * * This lock is not linked in anywhere, so we can muck with it * without holding any mutexes. */put_lock: newl->holder = locker; newl->refcount = 1; newl->mode = lock_mode; newl->obj = SH_PTR_TO_OFF(newl, sh_obj); newl->status = DB_LSTAT_HELD; /* * 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. If this is the first waiter, * add the object to the deadlock detector's list. */ if (SH_TAILQ_FIRST(&sh_obj->waiters, __db_lock) == NULL) SH_TAILQ_INSERT_HEAD(®ion->dd_objs, sh_obj, dd_links, __db_lockobj); SH_TAILQ_INSERT_HEAD(&sh_obj->waiters, newl, links, __db_lock); goto llist; } 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, region, lp->mode, lock_mode) && locker != lp->holder) break; } if (!LF_ISSET(DB_LOCK_SWITCH) && lp == NULL) SH_TAILQ_INSERT_TAIL(&sh_obj->holders, newl, links); else if (!LF_ISSET(DB_LOCK_NOWAIT)) { /* * If this is the first waiter, add the object to the * deadlock detector's list. */ if (SH_TAILQ_FIRST(&sh_obj->waiters, __db_lock) == NULL) SH_TAILQ_INSERT_HEAD(®ion->dd_objs, sh_obj, dd_links, __db_lockobj); SH_TAILQ_INSERT_TAIL(&sh_obj->waiters, newl, links); } else { ret = DB_LOCK_NOTGRANTED; if (SH_LIST_FIRST(&sh_locker->heldby, __db_lock) == NULL && LF_ISSET(DB_LOCK_FREE_LOCKER)) __lock_freelocker(lt, region, sh_locker, locker_ndx); region->stat.st_nnowaits++; goto err; }llist: /* * 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. */ on_locker_list = 1; no_dd = sh_locker->master_locker == INVALID_ROFF && SH_LIST_FIRST(&sh_locker->child_locker, __db_locker) == NULL && SH_LIST_FIRST(&sh_locker->heldby, __db_lock) == NULL; SH_LIST_INSERT_HEAD(&sh_locker->heldby, newl, locker_links, __db_lock); if (LF_ISSET(DB_LOCK_SWITCH) || lp != NULL) { if (LF_ISSET(DB_LOCK_SWITCH) && (ret = __lock_put_nolock(dbenv, lock, &ihold, DB_LOCK_NOWAITERS)) != 0) goto err; /* * This is really a blocker for the thread. It should be * initialized locked, so that when we try to acquire it, we * block. */ newl->status = DB_LSTAT_WAITING; region->stat.st_nconflicts++; region->need_dd = 1; /* * First check to see if this txn has expired. * If not then see if the lock timeout is past * the expiration of the txn, if it is, use * the txn expiration time. lk_expire is passed * to avoid an extra call to get the time. */ if (__lock_expired(dbenv, &sh_locker->lk_expire, &sh_locker->tx_expire)) { newl->status = DB_LSTAT_ABORTED; region->stat.st_ndeadlocks++; region->stat.st_ntxntimeouts++; /* * Remove the lock from the wait queue and if * this was the only lock on the wait queue remove * this object from the deadlock detector object * list. */ SH_LIST_REMOVE(newl, locker_links, __db_lock); SH_TAILQ_REMOVE( &sh_obj->waiters, newl, links, __db_lock); if (SH_TAILQ_FIRST(&sh_obj->waiters, __db_lock) == NULL) SH_TAILQ_REMOVE(®ion->dd_objs, sh_obj, dd_links, __db_lockobj); /* Clear the timeout, we are done. */ LOCK_SET_TIME_INVALID(&sh_locker->tx_expire); goto expired; } /* * If a timeout was specified in this call then it * takes priority. If a lock timeout has been specified * for this transaction then use that, otherwise use * the global timeout value. */ if (!LF_ISSET(DB_LOCK_SET_TIMEOUT)) { if (F_ISSET(sh_locker, DB_LOCKER_TIMEOUT)) timeout = sh_locker->lk_timeout; else timeout = region->lk_timeout; } if (timeout != 0) __lock_expires(dbenv, &sh_locker->lk_expire, timeout); else LOCK_SET_TIME_INVALID(&sh_locker->lk_expire); if (LOCK_TIME_ISVALID(&sh_locker->tx_expire) && (timeout == 0 || __lock_expired(dbenv, &sh_locker->lk_expire, &sh_locker->tx_expire))) sh_locker->lk_expire = sh_locker->tx_expire; UNLOCKREGION(dbenv, (DB_LOCKTAB *)dbenv->lk_handle); /* * We are about to wait; before waiting, see if the deadlock * detector should be run. */ if (region->detect != DB_LOCK_NORUN && !no_dd) (void)dbenv->lock_detect( dbenv, 0, region->detect, &did_abort); MUTEX_LOCK(dbenv, &newl->mutex); LOCKREGION(dbenv, (DB_LOCKTAB *)dbenv->lk_handle);expired: /* Turn off lock timeout. */ LOCK_SET_TIME_INVALID(&sh_locker->lk_expire); if (newl->status != DB_LSTAT_PENDING) { (void)__lock_checklocker(lt, newl, newl->holder, 0); switch (newl->status) { case DB_LSTAT_ABORTED: on_locker_list = 0; ret = DB_LOCK_DEADLOCK; break; case DB_LSTAT_NOTEXIST: ret = DB_LOCK_NOTEXIST; break; case DB_LSTAT_EXPIRED: SHOBJECT_LOCK(lt, region, sh_obj, obj_ndx); if ((ret = __lock_put_internal( lt, newl, obj_ndx, 0) != 0)) goto err; if (LOCK_TIME_EQUAL( &sh_locker->lk_expire, &sh_locker->tx_expire)) { region->stat.st_ndeadlocks++; region->stat.st_ntxntimeouts++; return (DB_LOCK_DEADLOCK); } else { region->stat.st_nlocktimeouts++; return (DB_LOCK_NOTGRANTED); } default: ret = EINVAL; break; } goto err; } 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); /* * Ensure that the object is not believed to be on * the object's lists, if we're traversing by locker. */ newl->links.stqe_prev = -1; goto upgrade; } else newl->status = DB_LSTAT_HELD; } lock->off = R_OFFSET(<->reginfo, newl); lock->gen = newl->gen; lock->mode = newl->mode; sh_locker->nlocks++; if (IS_WRITELOCK(newl->mode)) sh_locker->nwrites++; return (0);upgrade:/* * This was an upgrade, so return the new lock to the free list and * upgrade the mode of the original lock. */ lp = (struct __db_lock *)R_ADDR(<->reginfo, lock->off); if (IS_WRITELOCK(lock_mode) && !IS_WRITELOCK(lp->mode)) sh_locker->nwrites++; lp->mode = lock_mode; ret = 0; /* FALLTHROUGH */done:err: newl->status = DB_LSTAT_FREE; region->stat.st_nlocks--; if (on_locker_list) { SH_LIST_REMOVE(newl, locker_links, __db_lock); } SH_TAILQ_INSERT_HEAD(®ion->free_locks, newl, links, __db_lock); return (ret);}/* * Lock release routines. * * The user callable one is lock_put and the three we use internally are * __lock_put_nolock, __lock_put_internal and __lock_downgrade. * * PUBLIC: int __lock_put __P((DB_ENV *, DB_LOCK *)); */int__lock_put(dbenv, lock) DB_ENV *dbenv; DB_LOCK *lock;{ DB_LOCKTAB *lt; int ret, run_dd; PANIC_CHECK(dbenv); ENV_REQUIRES_CONFIG(dbenv, dbenv->lk_handle, "DB_LOCK->lock_put", DB_INIT_LOCK); if (IS_RECOVERING(dbenv)) return (0); lt = dbenv->lk_handle;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -