/*
* 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/uds-releases/jasper/src/uds/indexZone.c#4 $
*/
#include "indexZone.h"
#include "errors.h"
#include "index.h"
#include "indexCheckpoint.h"
#include "indexRouter.h"
#include "logger.h"
#include "memoryAlloc.h"
#include "permassert.h"
#include "request.h"
#include "sparseCache.h"
#include "uds.h"
/**********************************************************************/
int makeIndexZone(struct index *index, unsigned int zoneNumber)
{
IndexZone *zone;
int result = ALLOCATE(1, IndexZone, "index zone", &zone);
if (result != UDS_SUCCESS) {
return result;
}
result = makeOpenChapter(index->volume->geometry, index->zoneCount,
&zone->openChapter);
if (result != UDS_SUCCESS) {
freeIndexZone(zone);
return result;
}
result = makeOpenChapter(index->volume->geometry, index->zoneCount,
&zone->writingChapter);
if (result != UDS_SUCCESS) {
freeIndexZone(zone);
return result;
}
zone->index = index;
zone->id = zoneNumber;
index->zones[zoneNumber] = zone;
return UDS_SUCCESS;
}
/**********************************************************************/
void freeIndexZone(IndexZone *zone)
{
if (zone == NULL) {
return;
}
freeOpenChapter(zone->openChapter);
freeOpenChapter(zone->writingChapter);
FREE(zone);
}
/**********************************************************************/
bool isZoneChapterSparse(const IndexZone *zone,
uint64_t virtualChapter)
{
return isChapterSparse(zone->index->volume->geometry,
zone->oldestVirtualChapter,
zone->newestVirtualChapter,
virtualChapter);
}
/**********************************************************************/
void setActiveChapters(IndexZone *zone)
{
zone->oldestVirtualChapter = zone->index->oldestVirtualChapter;
zone->newestVirtualChapter = zone->index->newestVirtualChapter;
}
/**
* Swap the open and writing chapters after blocking until there are no active
* chapter writers on the index.
*
* @param zone The zone swapping chapters
*
* @return UDS_SUCCESS or a return code
**/
static int swapOpenChapter(IndexZone *zone)
{
// Wait for any currently writing chapter to complete
int result = finishPreviousChapter(zone->index->chapterWriter,
zone->newestVirtualChapter);
if (result != UDS_SUCCESS) {
return result;
}
// Swap the writing and open chapters
OpenChapterZone *tempChapter = zone->openChapter;
zone->openChapter = zone->writingChapter;
zone->writingChapter = tempChapter;
return UDS_SUCCESS;
}
/**
* Advance to a new open chapter, and forget the oldest chapter in the
* index if necessary.
*
* @param zone The zone containing the chapter to reap
*
* @return UDS_SUCCESS or an error code
**/
static int reapOldestChapter(IndexZone *zone)
{
Index *index = zone->index;
unsigned int chaptersPerVolume = index->volume->geometry->chaptersPerVolume;
int result
= ASSERT(((zone->newestVirtualChapter - zone->oldestVirtualChapter)
<= chaptersPerVolume),
"newest (%llu) and oldest (%llu) virtual chapters "
"less than or equal to chapters per volume (%u)",
zone->newestVirtualChapter, zone->oldestVirtualChapter,
chaptersPerVolume);
if (result != UDS_SUCCESS) {
return result;
}
setMasterIndexZoneOpenChapter(index->masterIndex, zone->id,
zone->newestVirtualChapter);
return UDS_SUCCESS;
}
/**********************************************************************/
int executeSparseCacheBarrierMessage(IndexZone *zone,
BarrierMessageData *barrier)
{
/*
* Check if the chapter index for the virtual chapter is already in the
* cache, and if it's not, rendezvous with the other zone threads to add the
* chapter index to the sparse index cache.
*/
return updateSparseCache(zone, barrier->virtualChapter);
}
/**
* Handle notification that some other zone has closed its open chapter. If
* the chapter that was closed is still the open chapter for this zone,
* close it now in order to minimize skew.
*
* @param zone The zone receiving the notification
* @param chapterClosed The notification
*
* @return UDS_SUCCESS or an error code
**/
static int handleChapterClosed(IndexZone *zone,
ChapterClosedMessageData *chapterClosed)
{
if (zone->newestVirtualChapter == chapterClosed->virtualChapter) {
return openNextChapter(zone, NULL);
}
return UDS_SUCCESS;
}
/**********************************************************************/
int dispatchIndexZoneControlRequest(Request *request)
{
ZoneMessage *message = &request->zoneMessage;
IndexZone *zone = message->index->zones[request->zoneNumber];
switch (request->action) {
case REQUEST_SPARSE_CACHE_BARRIER:
return executeSparseCacheBarrierMessage(zone, &message->data.barrier);
case REQUEST_ANNOUNCE_CHAPTER_CLOSED:
return handleChapterClosed(zone, &message->data.chapterClosed);
default:
return ASSERT_FALSE("valid control message type: %d", request->action);
}
}
/**
* Announce the closure of the current open chapter to the other zones.
*
* @param request The request which caused the chapter to close
* (may be NULL)
* @param zone The zone which first closed the chapter
* @param closedChapter The chapter which was closed
*
* @return UDS_SUCCESS or an error code
**/
static int announceChapterClosed(Request *request,
IndexZone *zone,
uint64_t closedChapter)
{
IndexRouter *router = ((request != NULL) ? request->router : NULL);
ZoneMessage zoneMessage = {
.index = zone->index,
.data = {
.chapterClosed = { .virtualChapter = closedChapter }
}
};
unsigned int i;
for (i = 0; i < zone->index->zoneCount; i++) {
if (zone->id == i) {
continue;
}
int result;
if (router != NULL) {
result = launchZoneControlMessage(REQUEST_ANNOUNCE_CHAPTER_CLOSED,
zoneMessage, i, router);
} else {
// We're in a test which doesn't have zone queues, so we can just
// call the message function directly.
result = handleChapterClosed(zone->index->zones[i],
&zoneMessage.data.chapterClosed);
}
if (result != UDS_SUCCESS) {
return result;
}
}
return UDS_SUCCESS;
}
/**********************************************************************/
int openNextChapter(IndexZone *zone, Request *request)
{
logDebug("closing chapter %llu of zone %d after %u entries (%u short)",
zone->newestVirtualChapter, zone->id, zone->openChapter->size,
zone->openChapter->capacity - zone->openChapter->size);
int result = swapOpenChapter(zone);
if (result != UDS_SUCCESS) {
return result;
}
uint64_t closedChapter = zone->newestVirtualChapter++;
result = reapOldestChapter(zone);
if (result != UDS_SUCCESS) {
return logUnrecoverable(result, "reapOldestChapter failed");
}
resetOpenChapter(zone->openChapter);
// begin, continue, or finish the checkpoint processing
// moved above startClosingChapter because some of the
// checkpoint processing now done by the chapter writer thread
result = processCheckpointing(zone->index,
zone->id,
zone->newestVirtualChapter);
if (result != UDS_SUCCESS) {
return result;
}
unsigned int finishedZones = startClosingChapter(zone->index->chapterWriter,
zone->id,
zone->writingChapter);
if ((finishedZones == 1) && (zone->index->zoneCount > 1)) {
// This is the first zone of a multi-zone index to close this chapter,
// so inform the other zones in order to control zone skew.
result = announceChapterClosed(request, zone, closedChapter);
if (result != UDS_SUCCESS) {
return result;
}
}
// If the chapter being opened won't overwrite the oldest chapter, we're
// done.
if (!areSamePhysicalChapter(zone->index->volume->geometry,
zone->newestVirtualChapter,
zone->oldestVirtualChapter)) {
return UDS_SUCCESS;
}
uint64_t victim = zone->oldestVirtualChapter++;
if (finishedZones < zone->index->zoneCount) {
// We are not the last zone to close the chapter, so we're done
return UDS_SUCCESS;
}
/*
* We are the last zone to close the chapter, so clean up the cache. That
* it is safe to let the last thread out of the previous chapter to do this
* relies on the fact that although the new open chapter shadows the oldest
* chapter in the cache, until we write the new open chapter to disk, we'll
* never look for it in the cache.
*/
return forgetChapter(zone->index->volume, victim, INVALIDATION_EXPIRE);
}
/**********************************************************************/
IndexRegion computeIndexRegion(const IndexZone *zone,
uint64_t virtualChapter)
{
if (virtualChapter == zone->newestVirtualChapter) {
return LOC_IN_OPEN_CHAPTER;
}
return (isZoneChapterSparse(zone, virtualChapter)
? LOC_IN_SPARSE : LOC_IN_DENSE);
}
/**********************************************************************/
int getRecordFromZone(IndexZone *zone,
Request *request,
bool *found,
uint64_t virtualChapter)
{
if (virtualChapter == zone->newestVirtualChapter) {
searchOpenChapter(zone->openChapter, &request->chunkName,
&request->oldMetadata, found);
return UDS_SUCCESS;
}
if ((zone->newestVirtualChapter > 0)
&& (virtualChapter == (zone->newestVirtualChapter - 1))
&& (zone->writingChapter->size > 0)) {
// Only search the writing chapter if it is full, else look on disk.
searchOpenChapter(zone->writingChapter, &request->chunkName,
&request->oldMetadata, found);
return UDS_SUCCESS;
}
// The slow lane thread has determined the location previously. We don't need
// to search again. Just return the location.
if (request->slLocationKnown) {
*found = request->slLocation != LOC_UNAVAILABLE;
return UDS_SUCCESS;
}
Volume *volume = zone->index->volume;
if (isZoneChapterSparse(zone, virtualChapter)
&& sparseCacheContains(volume->sparseCache, virtualChapter,
request->zoneNumber)) {
// The named chunk, if it exists, is in a sparse chapter that is cached,
// so just run the chunk through the sparse chapter cache search.
return searchSparseCacheInZone(zone, request, virtualChapter, found);
}
return searchVolumePageCache(volume, request, &request->chunkName,
virtualChapter, &request->oldMetadata, found);
}
/**********************************************************************/
int putRecordInZone(IndexZone *zone,
Request *request,
const UdsChunkData *metadata)
{
unsigned int remaining;
int result = putOpenChapter(zone->openChapter, &request->chunkName, metadata,
&remaining);
if (result != UDS_SUCCESS) {
return result;
}
if (remaining == 0) {
return openNextChapter(zone, request);
}
return UDS_SUCCESS;
}
/**************************************************************************/
int searchSparseCacheInZone(IndexZone *zone,
Request *request,
uint64_t virtualChapter,
bool *found)
{
int recordPageNumber;
int result = searchSparseCache(zone, &request->chunkName, &virtualChapter,
&recordPageNumber);
if ((result != UDS_SUCCESS) || (virtualChapter == UINT64_MAX)) {
return result;
}
Volume *volume = zone->index->volume;
// XXX map to physical chapter and validate. It would be nice to just pass
// the virtual in to the slow lane, since it's tracking invalidations.
unsigned int chapter
= mapToPhysicalChapter(volume->geometry, virtualChapter);
return searchCachedRecordPage(volume, request, &request->chunkName, chapter,
recordPageNumber, &request->oldMetadata,
found);
}