/* * 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/pbnLockPool.c#2 $ */ #include "pbnLockPool.h" #include "logger.h" #include "memoryAlloc.h" #include "permassert.h" #include "ringNode.h" #include "pbnLock.h" /** * Unused (idle) PBN locks are kept in a ring. Just like in a malloc * implementation, the lock structure is unused memory, so we can save a bit * of space (and not pollute the lock structure proper) by using a union to * overlay the lock structure with the free list. **/ typedef union idlePBNLock { /** Only used while locks are in the pool */ RingNode node; /** Only used while locks are not in the pool */ PBNLock lock; } IdlePBNLock; /** * The lock pool is little more than the memory allocated for the locks. **/ struct pbnLockPool { /** The number of locks allocated for the pool */ size_t capacity; /** The number of locks currently borrowed from the pool */ size_t borrowed; /** A ring containing all idle PBN lock instances */ RingNode idleRing; /** The memory for all the locks allocated by this pool */ IdlePBNLock locks[]; }; /**********************************************************************/ int makePBNLockPool(size_t capacity, PBNLockPool **poolPtr) { PBNLockPool *pool; int result = ALLOCATE_EXTENDED(PBNLockPool, capacity, IdlePBNLock, __func__, &pool); if (result != VDO_SUCCESS) { return result; } pool->capacity = capacity; pool->borrowed = capacity; initializeRing(&pool->idleRing); for (size_t i = 0; i < capacity; i++) { PBNLock *lock = &pool->locks[i].lock; returnPBNLockToPool(pool, &lock); } *poolPtr = pool; return VDO_SUCCESS; } /**********************************************************************/ void freePBNLockPool(PBNLockPool **poolPtr) { if (*poolPtr == NULL) { return; } PBNLockPool *pool = *poolPtr; ASSERT_LOG_ONLY(pool->borrowed == 0, "All PBN locks must be returned to the pool before it is" " freed, but %zu locks are still on loan", pool->borrowed); FREE(pool); *poolPtr = NULL; } /**********************************************************************/ int borrowPBNLockFromPool(PBNLockPool *pool, PBNLockType type, PBNLock **lockPtr) { if (pool->borrowed >= pool->capacity) { return logErrorWithStringError(VDO_LOCK_ERROR, "no free PBN locks left to borrow"); } pool->borrowed += 1; RingNode *idleNode = popRingNode(&pool->idleRing); // The lock was zeroed when it was placed in the pool, but the overlapping // ring pointers are non-zero after a pop. memset(idleNode, 0, sizeof(*idleNode)); STATIC_ASSERT(offsetof(IdlePBNLock, node) == offsetof(IdlePBNLock, lock)); PBNLock *lock = (PBNLock *) idleNode; initializePBNLock(lock, type); *lockPtr = lock; return VDO_SUCCESS; } /**********************************************************************/ void returnPBNLockToPool(PBNLockPool *pool, PBNLock **lockPtr) { // Take what should be the last lock reference from the caller PBNLock *lock = *lockPtr; *lockPtr = NULL; // A bit expensive, but will promptly catch some use-after-free errors. memset(lock, 0, sizeof(*lock)); RingNode *idleNode = (RingNode *) lock; initializeRing(idleNode); pushRingNode(&pool->idleRing, idleNode); ASSERT_LOG_ONLY(pool->borrowed > 0, "shouldn't return more than borrowed"); pool->borrowed -= 1; }