/* * 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/allocatingVIO.c#4 $ */ #include "allocatingVIO.h" #include "logger.h" #include "allocationSelector.h" #include "blockAllocator.h" #include "dataVIO.h" #include "pbnLock.h" #include "slabDepot.h" #include "types.h" #include "vdoInternal.h" #include "vioWrite.h" /** * Make a single attempt to acquire a write lock on a newly-allocated PBN. * * @param allocatingVIO The AllocatingVIO that wants a write lock for its * newly allocated block * * @return VDO_SUCCESS or an error code **/ static int attemptPBNWriteLock(AllocatingVIO *allocatingVIO) { assertInPhysicalZone(allocatingVIO); ASSERT_LOG_ONLY(allocatingVIO->allocationLock == NULL, "must not acquire a lock while already referencing one"); PBNLock *lock; int result = attemptPBNLock(allocatingVIO->zone, allocatingVIO->allocation, allocatingVIO->writeLockType, &lock); if (result != VDO_SUCCESS) { return result; } if (lock->holderCount > 0) { // This block is already locked, which should be impossible. return logErrorWithStringError(VDO_LOCK_ERROR, "Newly allocated block %" PRIu64 " was spuriously locked (holderCount=%u)", allocatingVIO->allocation, lock->holderCount); } // We've successfully acquired a new lock, so mark it as ours. lock->holderCount += 1; allocatingVIO->allocationLock = lock; assignProvisionalReference(lock); return VDO_SUCCESS; } /** * Attempt to allocate and lock a physical block. If successful, continue * along the write path. * * @param allocatingVIO The AllocatingVIO which needs an allocation * * @return VDO_SUCCESS or an error if a block could not be allocated **/ static int allocateAndLockBlock(AllocatingVIO *allocatingVIO) { BlockAllocator *allocator = getBlockAllocator(allocatingVIO->zone); int result = allocateBlock(allocator, &allocatingVIO->allocation); if (result != VDO_SUCCESS) { return result; } result = attemptPBNWriteLock(allocatingVIO); if (result != VDO_SUCCESS) { return result; } // We got a block! VIO *vio = allocatingVIOAsVIO(allocatingVIO); vio->physical = allocatingVIO->allocation; allocatingVIO->allocationCallback(allocatingVIO); return VDO_SUCCESS; } static void allocateBlockForWrite(VDOCompletion *completion); /** * Retry allocating a block for write. * * @param waiter The AllocatingVIO that was waiting to allocate * @param context The context (unused) **/ static void retryAllocateBlockForWrite(Waiter *waiter, void *context __attribute__((unused))) { AllocatingVIO *allocatingVIO = waiterAsAllocatingVIO(waiter); allocateBlockForWrite(allocatingVIOAsCompletion(allocatingVIO)); } /** * Attempt to enqueue an AllocatingVIO to wait for a slab to be scrubbed in the * current allocation zone. * * @param allocatingVIO The AllocatingVIO which wants to allocate a block * * @return VDO_SUCCESS if the AllocatingVIO was queued, VDO_NO_SPACE if there * are no slabs to be scrubbed in the current zone, or some other * error **/ static int waitForCleanSlab(AllocatingVIO *allocatingVIO) { Waiter *waiter = allocatingVIOAsWaiter(allocatingVIO); waiter->callback = retryAllocateBlockForWrite; BlockAllocator *allocator = getBlockAllocator(allocatingVIO->zone); int result = enqueueForCleanSlab(allocator, waiter); if (result != VDO_SUCCESS) { return result; } // We've successfully enqueued, when we come back, pretend like we've // never tried this allocation before. allocatingVIO->waitForCleanSlab = false; allocatingVIO->allocationAttempts = 0; return VDO_SUCCESS; } /** * Attempt to allocate a block in an AllocatingVIO's current allocation zone. * * @param allocatingVIO The AllocatingVIO * * @return VDO_SUCCESS or an error **/ static int allocateBlockInZone(AllocatingVIO *allocatingVIO) { allocatingVIO->allocationAttempts++; int result = allocateAndLockBlock(allocatingVIO); if (result != VDO_NO_SPACE) { return result; } if (allocatingVIO->waitForCleanSlab) { result = waitForCleanSlab(allocatingVIO); if (result != VDO_NO_SPACE) { return result; } } VDO *vdo = getVDOFromAllocatingVIO(allocatingVIO); const ThreadConfig *threadConfig = getThreadConfig(vdo); if (allocatingVIO->allocationAttempts >= threadConfig->physicalZoneCount) { if (allocatingVIO->waitForCleanSlab) { // There were no free blocks in any zone, and no zone had slabs to // scrub. allocatingVIO->allocationCallback(allocatingVIO); return VDO_SUCCESS; } allocatingVIO->waitForCleanSlab = true; allocatingVIO->allocationAttempts = 0; } // Try the next zone ZoneCount zoneNumber = getPhysicalZoneNumber(allocatingVIO->zone) + 1; if (zoneNumber == threadConfig->physicalZoneCount) { zoneNumber = 0; } allocatingVIO->zone = vdo->physicalZones[zoneNumber]; launchPhysicalZoneCallback(allocatingVIO, allocateBlockForWrite, THIS_LOCATION("$F;cb=allocBlockInZone")); return VDO_SUCCESS; } /** * Attempt to allocate a block. This callback is registered in * allocateDataBlock() and allocateBlockInZone(). * * @param completion The AllocatingVIO needing an allocation **/ static void allocateBlockForWrite(VDOCompletion *completion) { AllocatingVIO *allocatingVIO = asAllocatingVIO(completion); assertInPhysicalZone(allocatingVIO); allocatingVIOAddTraceRecord(allocatingVIO, THIS_LOCATION(NULL)); int result = allocateBlockInZone(allocatingVIO); if (result != VDO_SUCCESS) { setCompletionResult(completion, result); allocatingVIO->allocationCallback(allocatingVIO); } } /**********************************************************************/ void allocateDataBlock(AllocatingVIO *allocatingVIO, AllocationSelector *selector, PBNLockType writeLockType, AllocationCallback *callback) { allocatingVIO->writeLockType = writeLockType; allocatingVIO->allocationCallback = callback; allocatingVIO->allocationAttempts = 0; allocatingVIO->allocation = ZERO_BLOCK; VIO *vio = allocatingVIOAsVIO(allocatingVIO); allocatingVIO->zone = vio->vdo->physicalZones[getNextAllocationZone(selector)]; launchPhysicalZoneCallback(allocatingVIO, allocateBlockForWrite, THIS_LOCATION("$F;cb=allocDataBlock")); } /**********************************************************************/ void releaseAllocationLock(AllocatingVIO *allocatingVIO) { assertInPhysicalZone(allocatingVIO); PhysicalBlockNumber lockedPBN = allocatingVIO->allocation; if (hasProvisionalReference(allocatingVIO->allocationLock)) { allocatingVIO->allocation = ZERO_BLOCK; } releasePBNLock(allocatingVIO->zone, lockedPBN, &allocatingVIO->allocationLock); } /**********************************************************************/ void resetAllocation(AllocatingVIO *allocatingVIO) { ASSERT_LOG_ONLY(allocatingVIO->allocationLock == NULL, "must not reset allocation while holding a PBN lock"); allocatingVIOAsVIO(allocatingVIO)->physical = ZERO_BLOCK; allocatingVIO->zone = NULL; allocatingVIO->allocation = ZERO_BLOCK; allocatingVIO->allocationAttempts = 0; allocatingVIO->waitForCleanSlab = false; }