void LockManager::downgrade(LockRequest* request, LockMode newMode) { invariant(request->lock); invariant(request->status == LockRequest::STATUS_GRANTED); invariant(request->recursiveCount > 0); // The conflict set of the newMode should be a subset of the conflict set of the old mode. // Can't downgrade from S -> IX for example. invariant((LockConflictsTable[request->mode] | LockConflictsTable[newMode]) == LockConflictsTable[request->mode]); LockHead* lock = request->lock; LockBucket* bucket = _getBucket(lock->resourceId); SimpleMutex::scoped_lock scopedLock(bucket->mutex); invariant(lock->grantedQueueBegin != NULL); invariant(lock->grantedQueueEnd != NULL); invariant(lock->grantedModes != 0); lock->changeGrantedModeCount(newMode, LockHead::Increment); lock->changeGrantedModeCount(request->mode, LockHead::Decrement); request->mode = newMode; _onLockModeChanged(lock, true); }
bool LockManager::unlock(LockRequest* request) { invariant(request->lock); // Fast path for decrementing multiple references of the same lock. It is safe to do this // without locking, because 1) all calls for the same lock request must be done on the same // thread and 2) if there are lock requests hanging of a given LockHead, then this lock // will never disappear. request->recursiveCount--; if ((request->status == LockRequest::STATUS_GRANTED) && (request->recursiveCount > 0)) { return false; } LockHead* lock = request->lock; LockBucket* bucket = _getBucket(lock->resourceId); scoped_spinlock scopedLock(bucket->mutex); invariant(lock->grantedQueue != NULL); invariant(lock->grantedModes != 0); if (request->status == LockRequest::STATUS_WAITING) { // This cancels a pending lock request invariant(request->recursiveCount == 0); lock->removeFromConflictQueue(request); lock->changeConflictModeCount(request->mode, LockHead::Decrement); } else if (request->status == LockRequest::STATUS_CONVERTING) { // This cancels a pending convert request invariant(request->recursiveCount > 0); // Lock only goes from GRANTED to CONVERTING, so cancelling the conversion request // brings it back to the previous granted mode. request->status = LockRequest::STATUS_GRANTED; lock->conversionsCount--; lock->changeGrantedModeCount(request->convertMode, LockHead::Decrement); request->convertMode = MODE_NONE; _onLockModeChanged(lock, lock->grantedCounts[request->convertMode] == 0); } else if (request->status == LockRequest::STATUS_GRANTED) { // This releases a currently held lock and is the most common path, so it should be // as efficient as possible. invariant(request->recursiveCount == 0); // Remove from the granted list lock->removeFromGrantedQueue(request); lock->changeGrantedModeCount(request->mode, LockHead::Decrement); _onLockModeChanged(lock, lock->grantedCounts[request->mode] == 0); } else { // Invalid request status invariant(false); } return (request->recursiveCount == 0); }
LockResult LockManager::lock(const ResourceId& resId, LockRequest* request, LockMode mode) { dassert(mode > MODE_NONE); // Fast path for acquiring the same lock multiple times in modes, which are already covered // by the current mode. It is safe to do this without locking, because 1) all calls for the // same lock request must be done on the same thread and 2) if there are lock requests // hanging off a given LockHead, then this lock will never disappear. if ((LockConflictsTable[request->mode] | LockConflictsTable[mode]) == LockConflictsTable[request->mode]) { request->recursiveCount++; return LOCK_OK; } // TODO: For the time being we do not need conversions between unrelated lock modes (i.e., // modes which both add and remove to the conflicts set), so these are not implemented yet // (e.g., S -> IX). invariant((LockConflictsTable[request->mode] | LockConflictsTable[mode]) == LockConflictsTable[mode]); LockBucket* bucket = _getBucket(resId); scoped_spinlock scopedLock(bucket->mutex); LockHead* lock; LockHeadMap::iterator it = bucket->data.find(resId); if (it == bucket->data.end()) { // Lock is free (not on the map) invariant(request->status == LockRequest::STATUS_NEW); lock = new LockHead(resId); bucket->data.insert(LockHeadPair(resId, lock)); } else { // Lock is not free lock = it->second; } // Sanity check if requests are being reused invariant(request->lock == NULL || request->lock == lock); request->lock = lock; request->recursiveCount++; if (request->status == LockRequest::STATUS_NEW) { invariant(request->recursiveCount == 1); // New lock request if (conflicts(mode, lock->grantedModes)) { request->status = LockRequest::STATUS_WAITING; request->mode = mode; request->convertMode = MODE_NONE; // Put it on the conflict queue. This is the place where various policies could be // applied for where in the wait queue does a request go. lock->addToConflictQueue(request); lock->changeConflictModeCount(mode, LockHead::Increment); return LOCK_WAITING; } else { // No conflict, new request request->status = LockRequest::STATUS_GRANTED; request->mode = mode; request->convertMode = MODE_NONE; lock->addToGrantedQueue(request); lock->changeGrantedModeCount(mode, LockHead::Increment); return LOCK_OK; } } else { // If we are here, we already hold the lock in some mode. In order to keep it simple, // we do not allow requesting a conversion while a lock is already waiting or pending // conversion, hence the assertion below. invariant(request->status == LockRequest::STATUS_GRANTED); invariant(request->recursiveCount > 1); invariant(request->mode != mode); // Construct granted mask without our current mode, so that it is not counted as // conflicting uint32_t grantedModesWithoutCurrentRequest = 0; // We start the counting at 1 below, because LockModesCount also includes MODE_NONE // at position 0, which can never be acquired/granted. for (uint32_t i = 1; i < LockModesCount; i++) { const uint32_t currentRequestHolds = (request->mode == static_cast<LockMode>(i) ? 1 : 0); if (lock->grantedCounts[i] > currentRequestHolds) { grantedModesWithoutCurrentRequest |= modeMask(static_cast<LockMode>(i)); } } // This check favours conversion requests over pending requests. For example: // // T1 requests lock L in IS // T2 requests lock L in X // T1 then upgrades L from IS -> S // // Because the check does not look into the conflict modes bitmap, it will grant L to // T1 in S mode, instead of block, which would otherwise cause deadlock. if (conflicts(mode, grantedModesWithoutCurrentRequest)) { request->status = LockRequest::STATUS_CONVERTING; request->convertMode = mode; lock->conversionsCount++; lock->changeGrantedModeCount(request->convertMode, LockHead::Increment); return LOCK_WAITING; } else { // No conflict, existing request lock->changeGrantedModeCount(mode, LockHead::Increment); lock->changeGrantedModeCount(request->mode, LockHead::Decrement); request->mode = mode; return LOCK_OK; } } }