/*
* Copyright (c) 2020 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* $Id: //eng/vdo-releases/aluminum/src/c++/vdo/base/pbnLock.c#3 $
*/
#include "pbnLock.h"
#include "logger.h"
#include "blockAllocator.h"
#include "referenceBlock.h"
struct pbnLockImplementation {
PBNLockType type;
const char *name;
const char *releaseReason;
};
/**
* This array must have an entry for every PBNLockType value.
**/
static const PBNLockImplementation LOCK_IMPLEMENTATIONS[] = {
[VIO_READ_LOCK] = {
.type = VIO_READ_LOCK,
.name = "read",
.releaseReason = "candidate duplicate",
},
[VIO_WRITE_LOCK] = {
.type = VIO_WRITE_LOCK,
.name = "write",
.releaseReason = "newly allocated",
},
[VIO_COMPRESSED_WRITE_LOCK] = {
.type = VIO_COMPRESSED_WRITE_LOCK,
.name = "compressed write",
.releaseReason = "failed compression",
},
[VIO_BLOCK_MAP_WRITE_LOCK] = {
.type = VIO_BLOCK_MAP_WRITE_LOCK,
.name = "block map write",
.releaseReason = "block map write",
},
};
/**********************************************************************/
static inline bool hasLockType(const PBNLock *lock, PBNLockType type)
{
return (lock->implementation == &LOCK_IMPLEMENTATIONS[type]);
}
/**********************************************************************/
bool isPBNReadLock(const PBNLock *lock)
{
return hasLockType(lock, VIO_READ_LOCK);
}
/**********************************************************************/
static inline void setPBNLockType(PBNLock *lock, PBNLockType type)
{
lock->implementation = &LOCK_IMPLEMENTATIONS[type];
}
/**********************************************************************/
void initializePBNLock(PBNLock *lock, PBNLockType type)
{
lock->holderCount = 0;
setPBNLockType(lock, type);
}
/**********************************************************************/
void downgradePBNWriteLock(PBNLock *lock)
{
ASSERT_LOG_ONLY(!isPBNReadLock(lock),
"PBN lock must not already have been downgraded");
ASSERT_LOG_ONLY(!hasLockType(lock, VIO_BLOCK_MAP_WRITE_LOCK),
"must not downgrade block map write locks");
ASSERT_LOG_ONLY(lock->holderCount == 1,
"PBN write lock should have one holder but has %u",
lock->holderCount);
if (hasLockType(lock, VIO_WRITE_LOCK)) {
// DataVIO write locks are downgraded in place--the writer retains the
// hold on the lock. They've already had a single incRef journaled.
lock->incrementLimit = MAXIMUM_REFERENCE_COUNT - 1;
} else {
// Compressed block write locks are downgraded when they are shared with
// all their hash locks. The writer is releasing its hold on the lock.
lock->holderCount = 0;
lock->incrementLimit = MAXIMUM_REFERENCE_COUNT;
}
setPBNLockType(lock, VIO_READ_LOCK);
}
/**********************************************************************/
bool claimPBNLockIncrement(PBNLock *lock)
{
/*
* Claim the next free reference atomically since hash locks from multiple
* hash zone threads might be concurrently deduplicating against a single
* PBN lock on compressed block. As long as hitting the increment limit will
* lead to the PBN lock being released in a sane time-frame, we won't
* overflow a 32-bit claim counter, allowing a simple add instead of a
* compare-and-swap.
*/
uint32_t claimNumber = atomicAdd32(&lock->incrementsClaimed, 1);
return (claimNumber <= lock->incrementLimit);
}
/**********************************************************************/
void assignProvisionalReference(PBNLock *lock)
{
ASSERT_LOG_ONLY(!lock->hasProvisionalReference,
"lock does not have a provisional reference");
lock->hasProvisionalReference = true;
}
/**********************************************************************/
void unassignProvisionalReference(PBNLock *lock)
{
lock->hasProvisionalReference = false;
}
/**********************************************************************/
void releaseProvisionalReference(PBNLock *lock,
PhysicalBlockNumber lockedPBN,
BlockAllocator *allocator)
{
if (hasProvisionalReference(lock)) {
releaseBlockReference(allocator, lockedPBN,
lock->implementation->releaseReason);
unassignProvisionalReference(lock);
}
}