/*
* 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/physicalZone.c#3 $
*/
#include "physicalZone.h"
#include "memoryAlloc.h"
#include "blockAllocator.h"
#include "blockMap.h"
#include "completion.h"
#include "constants.h"
#include "dataVIO.h"
#include "flush.h"
#include "hashLock.h"
#include "intMap.h"
#include "pbnLock.h"
#include "pbnLockPool.h"
#include "slabDepot.h"
#include "vdoInternal.h"
enum {
// Each user DataVIO needs a PBN read lock and write lock, and each packer
// output bin has an AllocatingVIO that needs a PBN write lock.
LOCK_POOL_CAPACITY = 2 * MAXIMUM_USER_VIOS + DEFAULT_PACKER_OUTPUT_BINS,
};
struct physicalZone {
/** Which physical zone this is */
ZoneCount zoneNumber;
/** The thread ID for this zone */
ThreadID threadID;
/** In progress operations keyed by PBN */
IntMap *pbnOperations;
/** Pool of unused PBNLock instances */
PBNLockPool *lockPool;
/** The block allocator for this zone */
BlockAllocator *allocator;
};
/**********************************************************************/
int makePhysicalZone(VDO *vdo, ZoneCount zoneNumber, PhysicalZone **zonePtr)
{
PhysicalZone *zone;
int result = ALLOCATE(1, PhysicalZone, __func__, &zone);
if (result != VDO_SUCCESS) {
return result;
}
result = makeIntMap(LOCK_MAP_CAPACITY, 0, &zone->pbnOperations);
if (result != VDO_SUCCESS) {
freePhysicalZone(&zone);
return result;
}
result = makePBNLockPool(LOCK_POOL_CAPACITY, &zone->lockPool);
if (result != VDO_SUCCESS) {
freePhysicalZone(&zone);
return result;
}
zone->zoneNumber = zoneNumber;
zone->threadID = getPhysicalZoneThread(getThreadConfig(vdo), zoneNumber);
zone->allocator = getBlockAllocatorForZone(vdo->depot, zoneNumber);
*zonePtr = zone;
return VDO_SUCCESS;
}
/**********************************************************************/
void freePhysicalZone(PhysicalZone **zonePtr)
{
if (*zonePtr == NULL) {
return;
}
PhysicalZone *zone = *zonePtr;
freePBNLockPool(&zone->lockPool);
freeIntMap(&zone->pbnOperations);
FREE(zone);
*zonePtr = NULL;
}
/**********************************************************************/
ZoneCount getPhysicalZoneNumber(const PhysicalZone *zone)
{
return zone->zoneNumber;
}
/**********************************************************************/
ThreadID getPhysicalZoneThreadID(const PhysicalZone *zone)
{
return zone->threadID;
}
/**********************************************************************/
BlockAllocator *getBlockAllocator(const PhysicalZone *zone)
{
return zone->allocator;
}
/**********************************************************************/
PBNLock *getPBNLock(PhysicalZone *zone, PhysicalBlockNumber pbn)
{
return ((zone == NULL) ? NULL : intMapGet(zone->pbnOperations, pbn));
}
/**********************************************************************/
int attemptPBNLock(PhysicalZone *zone,
PhysicalBlockNumber pbn,
PBNLockType type,
PBNLock **lockPtr)
{
// Borrow and prepare a lock from the pool so we don't have to do two IntMap
// accesses in the common case of no lock contention.
PBNLock *newLock;
int result = borrowPBNLockFromPool(zone->lockPool, type, &newLock);
if (result != VDO_SUCCESS) {
ASSERT_LOG_ONLY(false, "must always be able to borrow a PBN lock");
return result;
}
PBNLock *lock;
result = intMapPut(zone->pbnOperations, pbn, newLock, false,
(void **) &lock);
if (result != VDO_SUCCESS) {
returnPBNLockToPool(zone->lockPool, &newLock);
return result;
}
if (lock != NULL) {
// The lock is already held, so we don't need the borrowed lock.
returnPBNLockToPool(zone->lockPool, &newLock);
result = ASSERT(lock->holderCount > 0,
"physical block %llu lock held", pbn);
if (result != VDO_SUCCESS) {
return result;
}
*lockPtr = lock;
} else {
*lockPtr = newLock;
}
return VDO_SUCCESS;
}
/**********************************************************************/
void releasePBNLock(PhysicalZone *zone,
PhysicalBlockNumber lockedPBN,
PBNLock **lockPtr)
{
PBNLock *lock = *lockPtr;
if (lock == NULL) {
return;
}
*lockPtr = NULL;
ASSERT_LOG_ONLY(lock->holderCount > 0,
"should not be releasing a lock that is not held");
lock->holderCount -= 1;
if (lock->holderCount > 0) {
// The lock was shared and is still referenced, so don't release it yet.
return;
}
PBNLock *holder = intMapRemove(zone->pbnOperations, lockedPBN);
ASSERT_LOG_ONLY((lock == holder),
"physical block lock mismatch for block %llu",
lockedPBN);
releaseProvisionalReference(lock, lockedPBN, zone->allocator);
returnPBNLockToPool(zone->lockPool, &lock);
}
/**********************************************************************/
void dumpPhysicalZone(const PhysicalZone *zone)
{
dumpBlockAllocator(zone->allocator);
}