/* * 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/logicalZone.c#6 $ */ #include "logicalZone.h" #include "logger.h" #include "memoryAlloc.h" #include "actionManager.h" #include "adminState.h" #include "allocationSelector.h" #include "atomic.h" #include "blockMap.h" #include "completion.h" #include "constants.h" #include "dataVIO.h" #include "flush.h" #include "intMap.h" #include "vdoInternal.h" struct logicalZone { /** The completion for flush notifications */ VDOCompletion completion; /** The owner of this zone */ LogicalZones *zones; /** Which logical zone this is */ ZoneCount zoneNumber; /** The thread id for this zone */ ThreadID threadID; /** In progress operations keyed by LBN */ IntMap *lbnOperations; /** The logical to physical map */ BlockMapZone *blockMapZone; /** The current flush generation */ SequenceNumber flushGeneration; /** The oldest active generation in this zone */ SequenceNumber oldestActiveGeneration; /** The number of IOs in the current flush generation */ BlockCount iosInFlushGeneration; /** * The oldest locked generation in this zone (an atomic copy of * oldestActiveGeneration) **/ Atomic64 oldestLockedGeneration; /** The youngest generation of the current notification */ SequenceNumber notificationGeneration; /** Whether a notification is in progress */ bool notifying; /** The queue of active data write VIOs */ RingNode writeVIOs; /** The administrative state of the zone */ AdminState state; /** The selector for determining which physical zone to allocate from */ AllocationSelector *selector; }; struct logicalZones { /** The VDO whose zones these are */ VDO *vdo; /** The manager for administrative actions */ ActionManager *manager; /** The number of zones */ ZoneCount zoneCount; /** The logical zones themselves */ LogicalZone zones[]; }; /** * Convert a generic VDOCompletion to a LogicalZone. * * @param completion The completion to convert * * @return The completion as a LogicalZone **/ static LogicalZone *asLogicalZone(VDOCompletion *completion) { STATIC_ASSERT(offsetof(LogicalZone, completion) == 0); assertCompletionType(completion->type, GENERATION_FLUSHED_COMPLETION); return (LogicalZone *) completion; } /**********************************************************************/ LogicalZone *getLogicalZone(LogicalZones *zones, ZoneCount zoneNumber) { return (zoneNumber < zones->zoneCount) ? &zones->zones[zoneNumber] : NULL; } /** * Implements ZoneThreadGetter **/ static ThreadID getThreadIDForZone(void *context, ZoneCount zoneNumber) { return getLogicalZoneThreadID(getLogicalZone(context, zoneNumber)); } /** * Initialize a logical zone. * * @param zones The LogicalZones to which this zone belongs * @param zoneNumber The LogicalZone's index **/ static int initializeZone(LogicalZones *zones, ZoneCount zoneNumber) { LogicalZone *zone = &zones->zones[zoneNumber]; zone->zones = zones; int result = makeIntMap(LOCK_MAP_CAPACITY, 0, &zone->lbnOperations); if (result != VDO_SUCCESS) { return result; } VDO *vdo = zones->vdo; result = initializeEnqueueableCompletion(&zone->completion, GENERATION_FLUSHED_COMPLETION, vdo->layer); if (result != VDO_SUCCESS) { return result; } zone->zoneNumber = zoneNumber; zone->threadID = getLogicalZoneThread(getThreadConfig(vdo), zoneNumber); zone->blockMapZone = getBlockMapZone(vdo->blockMap, zoneNumber); initializeRing(&zone->writeVIOs); atomicStore64(&zone->oldestLockedGeneration, 0); return makeAllocationSelector(getThreadConfig(vdo)->physicalZoneCount, zone->threadID, &zone->selector); } /**********************************************************************/ int makeLogicalZones(VDO *vdo, LogicalZones **zonesPtr) { const ThreadConfig *threadConfig = getThreadConfig(vdo); if (threadConfig->logicalZoneCount == 0) { return VDO_SUCCESS; } LogicalZones *zones; int result = ALLOCATE_EXTENDED(LogicalZones, threadConfig->logicalZoneCount, LogicalZone, __func__, &zones); if (result != VDO_SUCCESS) { return result; } zones->vdo = vdo; zones->zoneCount = threadConfig->logicalZoneCount; for (ZoneCount zone = 0; zone < threadConfig->logicalZoneCount; zone++) { result = initializeZone(zones, zone); if (result != VDO_SUCCESS) { freeLogicalZones(&zones); return result; } } result = makeActionManager(zones->zoneCount, getThreadIDForZone, getAdminThread(threadConfig), zones, NULL, vdo->layer, &zones->manager); if (result != VDO_SUCCESS) { freeLogicalZones(&zones); return result; } *zonesPtr = zones; return VDO_SUCCESS; } /**********************************************************************/ void freeLogicalZones(LogicalZones **zonesPtr) { LogicalZones *zones = *zonesPtr; if (zones == NULL) { return; } freeActionManager(&zones->manager); for (ZoneCount index = 0; index < zones->zoneCount; index++) { LogicalZone *zone = &zones->zones[index]; freeAllocationSelector(&zone->selector); destroyEnqueueable(&zone->completion); freeIntMap(&zone->lbnOperations); } FREE(zones); *zonesPtr = NULL; } /**********************************************************************/ static inline void assertOnZoneThread(LogicalZone *zone, const char *what) { ASSERT_LOG_ONLY((getCallbackThreadID() == zone->threadID), "%s() called on correct thread", what); } /** * Check whether this zone has drained. * * @param zone The zone to check **/ static void checkForDrainComplete(LogicalZone *zone) { if (!isDraining(&zone->state) || zone->notifying || !isRingEmpty(&zone->writeVIOs)) { return; } finishDraining(&zone->state); } /** * Initiate a drain. * * Implements AdminInitiator. **/ static void initiateDrain(AdminState *state) { checkForDrainComplete(container_of(state, LogicalZone, state)); } /** * Drain a logical zone. * *
Implements ZoneAction. **/ static void drainLogicalZone(void *context, ZoneCount zoneNumber, VDOCompletion *parent) { LogicalZone *zone = getLogicalZone(context, zoneNumber); startDraining(&zone->state, getCurrentManagerOperation(zone->zones->manager), parent, initiateDrain); } /**********************************************************************/ void drainLogicalZones(LogicalZones *zones, AdminStateCode operation, VDOCompletion *parent) { scheduleOperation(zones->manager, operation, NULL, drainLogicalZone, NULL, parent); } /** * Resume a logical zone. * *
Implements ZoneAction.
**/
static void resumeLogicalZone(void *context,
ZoneCount zoneNumber,
VDOCompletion *parent)
{
LogicalZone *zone = getLogicalZone(context, zoneNumber);
finishCompletion(parent, resumeIfQuiescent(&zone->state));
}
/**********************************************************************/
void resumeLogicalZones(LogicalZones *zones, VDOCompletion *parent)
{
scheduleOperation(zones->manager, ADMIN_STATE_RESUMING, NULL,
resumeLogicalZone, NULL, parent);
}
/**********************************************************************/
ThreadID getLogicalZoneThreadID(const LogicalZone *zone)
{
return zone->threadID;
}
/**********************************************************************/
BlockMapZone *getBlockMapForZone(const LogicalZone *zone)
{
return zone->blockMapZone;
}
/**********************************************************************/
IntMap *getLBNLockMap(const LogicalZone *zone)
{
return zone->lbnOperations;
}
/**********************************************************************/
LogicalZone *getNextLogicalZone(const LogicalZone *zone)
{
return getLogicalZone(zone->zones, zone->zoneNumber + 1);
}
/**
* Convert a RingNode to a DataVIO.
*
* @param ringNode The RingNode to convert
*
* @return The DataVIO which owns the RingNode
**/
static inline DataVIO *dataVIOFromRingNode(RingNode *ringNode)
{
return (DataVIO *) ((byte *) ringNode - offsetof(DataVIO, writeNode));
}
/**
* Update the oldest active generation. If it has changed, update the
* atomic copy as well.
*
* @param zone The zone
*
* @return true
if the oldest active generation has changed
**/
static bool updateOldestActiveGeneration(LogicalZone *zone)
{
SequenceNumber currentOldest = zone->oldestActiveGeneration;
if (isRingEmpty(&zone->writeVIOs)) {
zone->oldestActiveGeneration = zone->flushGeneration;
} else {
zone->oldestActiveGeneration
= dataVIOFromRingNode(zone->writeVIOs.next)->flushGeneration;
}
if (zone->oldestActiveGeneration == currentOldest) {
return false;
}
atomicStore64(&zone->oldestLockedGeneration, zone->oldestActiveGeneration);
return true;
}
/**********************************************************************/
void incrementFlushGeneration(LogicalZone *zone,
SequenceNumber expectedGeneration)
{
assertOnZoneThread(zone, __func__);
ASSERT_LOG_ONLY((zone->flushGeneration == expectedGeneration),
"logical zone %u flush generation %" PRIu64
" should be %llu before increment",
zone->zoneNumber, zone->flushGeneration,
expectedGeneration);
zone->flushGeneration++;
zone->iosInFlushGeneration = 0;
updateOldestActiveGeneration(zone);
}
/**********************************************************************/
SequenceNumber getOldestLockedGeneration(const LogicalZone *zone)
{
return (SequenceNumber) atomicLoad64(&zone->oldestLockedGeneration);
}
/**********************************************************************/
int acquireFlushGenerationLock(DataVIO *dataVIO)
{
LogicalZone *zone = dataVIO->logical.zone;
assertOnZoneThread(zone, __func__);
if (!isNormal(&zone->state)) {
return VDO_INVALID_ADMIN_STATE;
}
dataVIO->flushGeneration = zone->flushGeneration;
pushRingNode(&zone->writeVIOs, &dataVIO->writeNode);
dataVIO->hasFlushGenerationLock = true;
zone->iosInFlushGeneration++;
return VDO_SUCCESS;
}
/**********************************************************************/
static void attemptGenerationCompleteNotification(VDOCompletion *completion);
/**
* Notify the flush that at least one generation no longer has active VIOs.
* This callback is registered in attemptGenerationCompleteNotification().
*
* @param completion The zone completion
**/
static void notifyFlusher(VDOCompletion *completion)
{
LogicalZone *zone = asLogicalZone(completion);
completeFlushes(zone->zones->vdo->flusher);
launchCallback(completion, attemptGenerationCompleteNotification,
zone->threadID);
}
/**
* Notify the flusher if some generation no longer has active VIOs.
*
* @param completion The zone completion
**/
static void attemptGenerationCompleteNotification(VDOCompletion *completion)
{
LogicalZone *zone = asLogicalZone(completion);
assertOnZoneThread(zone, __func__);
if (zone->oldestActiveGeneration <= zone->notificationGeneration) {
zone->notifying = false;
checkForDrainComplete(zone);
return;
}
zone->notifying = true;
zone->notificationGeneration = zone->oldestActiveGeneration;
launchCallback(&zone->completion, notifyFlusher,
getFlusherThreadID(zone->zones->vdo->flusher));
}
/**********************************************************************/
void releaseFlushGenerationLock(DataVIO *dataVIO)
{
LogicalZone *zone = dataVIO->logical.zone;
assertOnZoneThread(zone, __func__);
if (isRingEmpty(&dataVIO->writeNode)) {
// This VIO never got a lock, either because it is a read, or because
// we are in read-only mode.
ASSERT_LOG_ONLY(!dataVIO->hasFlushGenerationLock,
"hasFlushGenerationLock false for VIO not on active list");
return;
}
unspliceRingNode(&dataVIO->writeNode);
dataVIO->hasFlushGenerationLock = false;
ASSERT_LOG_ONLY(zone->oldestActiveGeneration <= dataVIO->flushGeneration,
"DataVIO releasing lock on generation %" PRIu64
" is not older than oldest active generation %llu",
dataVIO->flushGeneration, zone->oldestActiveGeneration);
if (!updateOldestActiveGeneration(zone) || zone->notifying) {
return;
}
attemptGenerationCompleteNotification(&zone->completion);
}
/**********************************************************************/
AllocationSelector *getAllocationSelector(LogicalZone *zone)
{
return zone->selector;
}
/**********************************************************************/
void dumpLogicalZone(const LogicalZone *zone)
{
logInfo("LogicalZone %u", zone->zoneNumber);
logInfo(" flushGeneration=%llu oldestActiveGeneration=%" PRIu64
" oldestLockedGeneration=%llu notificationGeneration=%" PRIu64
" notifying=%s iosInCurrentGeneration=%llu",
zone->flushGeneration, zone->oldestActiveGeneration,
relaxedLoad64(&zone->oldestLockedGeneration),
zone->notificationGeneration, boolToString(zone->notifying),
zone->iosInFlushGeneration);
}