/*
* Copyright (C) 2012, 2016 higherfrequencytrading.com
* Copyright (C) 2016 Roman Leventov
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.openhft.chronicle.hash.impl.stage.iter;
import net.openhft.chronicle.hash.ChronicleHashCorruption;
import net.openhft.chronicle.hash.VanillaGlobalMutableState;
import net.openhft.chronicle.hash.impl.TierCountersArea;
import net.openhft.chronicle.hash.impl.VanillaChronicleHash;
import net.openhft.chronicle.hash.impl.VanillaChronicleHashHolder;
import net.openhft.chronicle.hash.impl.stage.entry.SegmentStages;
import net.openhft.chronicle.map.ChronicleHashCorruptionImpl;
import net.openhft.chronicle.map.impl.IterationContext;
import net.openhft.sg.StageRef;
import net.openhft.sg.Staged;
import static net.openhft.chronicle.map.ChronicleHashCorruptionImpl.format;
import static net.openhft.chronicle.map.ChronicleHashCorruptionImpl.report;
@Staged
public abstract class SegmentsRecovery implements IterationContext {
@StageRef
VanillaChronicleHashHolder<?> hh;
@StageRef
SegmentStages s;
@StageRef
TierRecovery tierRecovery;
@Override
public void recoverSegments(
ChronicleHashCorruption.Listener corruptionListener,
ChronicleHashCorruptionImpl corruption) {
VanillaChronicleHash<?, ?, ?, ?> h = hh.h();
for (int segmentIndex = 0; segmentIndex < h.actualSegments; segmentIndex++) {
s.initSegmentIndex(segmentIndex);
resetSegmentLock(corruptionListener, corruption);
zeroOutFirstSegmentTierCountersArea(corruptionListener, corruption);
tierRecovery.recoverTier(segmentIndex, corruptionListener, corruption);
}
VanillaGlobalMutableState globalMutableState = h.globalMutableState();
long storedExtraTiersInUse = globalMutableState.getExtraTiersInUse();
long allocatedExtraTiers = globalMutableState.getAllocatedExtraTierBulks() * h.tiersInBulk;
long expectedExtraTiersInUse =
Math.max(0, Math.min(storedExtraTiersInUse, allocatedExtraTiers));
long actualExtraTiersInUse = 0;
long firstFreeExtraTierIndex = -1;
for (long extraTierIndex = 0; extraTierIndex < expectedExtraTiersInUse; extraTierIndex++) {
long tierIndex = h.extraTierIndexToTierIndex(extraTierIndex);
// `tier` is unused in recoverTier(), 0 should be a safe value
s.initSegmentTier(0, tierIndex);
int segmentIndex = tierRecovery.recoverTier(
-1, corruptionListener, corruption);
if (segmentIndex >= 0) {
long tierCountersAreaAddr = s.tierCountersAreaAddr();
int storedSegmentIndex = TierCountersArea.segmentIndex(tierCountersAreaAddr);
if (storedSegmentIndex != segmentIndex) {
report(corruptionListener, corruption, segmentIndex, () ->
format("wrong segment index stored in tier counters area " +
"of tier with index {}: {}, should be, based on entries: {}",
tierIndex, storedSegmentIndex, segmentIndex)
);
TierCountersArea.segmentIndex(tierCountersAreaAddr, segmentIndex);
}
s.nextTierIndex(0);
s.initSegmentIndex(segmentIndex);
s.goToLastTier();
s.nextTierIndex(tierIndex);
TierCountersArea.prevTierIndex(tierCountersAreaAddr, s.tierIndex);
TierCountersArea.tier(tierCountersAreaAddr, s.tier + 1);
actualExtraTiersInUse = extraTierIndex + 1;
} else {
firstFreeExtraTierIndex = extraTierIndex;
break;
}
}
if (storedExtraTiersInUse != actualExtraTiersInUse) {
long finalActualExtraTiersInUse = actualExtraTiersInUse;
report(corruptionListener, corruption, -1, () ->
format("wrong number of actual tiers in use in global mutable state, stored: {}, " +
"should be: {}", storedExtraTiersInUse, finalActualExtraTiersInUse)
);
globalMutableState.setExtraTiersInUse(actualExtraTiersInUse);
}
long firstFreeTierIndex;
if (firstFreeExtraTierIndex == -1) {
if (allocatedExtraTiers > expectedExtraTiersInUse) {
firstFreeTierIndex = h.extraTierIndexToTierIndex(expectedExtraTiersInUse);
} else {
firstFreeTierIndex = 0;
}
} else {
firstFreeTierIndex = h.extraTierIndexToTierIndex(firstFreeExtraTierIndex);
}
if (firstFreeTierIndex > 0) {
long lastTierIndex = h.extraTierIndexToTierIndex(allocatedExtraTiers - 1);
h.linkAndZeroOutFreeTiers(firstFreeTierIndex, lastTierIndex);
}
long storedFirstFreeTierIndex = globalMutableState.getFirstFreeTierIndex();
if (storedFirstFreeTierIndex != firstFreeTierIndex) {
report(corruptionListener, corruption, -1, () ->
format("wrong first free tier index in global mutable state, stored: {}, " +
"should be: {}", storedFirstFreeTierIndex, firstFreeTierIndex)
);
globalMutableState.setFirstFreeTierIndex(firstFreeTierIndex);
}
removeDuplicatesInSegments(corruptionListener, corruption);
}
private void removeDuplicatesInSegments(
ChronicleHashCorruption.Listener corruptionListener,
ChronicleHashCorruptionImpl corruption) {
VanillaChronicleHash<?, ?, ?, ?> h = hh.h();
for (int segmentIndex = 0; segmentIndex < h.actualSegments; segmentIndex++) {
s.initSegmentIndex(segmentIndex);
s.initSegmentTier();
s.goToLastTier();
while (true) {
tierRecovery.removeDuplicatesInSegment(corruptionListener, corruption);
if (s.tier > 0) {
s.prevTier();
} else {
break;
}
}
}
}
private void resetSegmentLock(
ChronicleHashCorruption.Listener corruptionListener,
ChronicleHashCorruptionImpl corruption) {
long lockState = s.segmentHeader.getLockState(s.segmentHeaderAddress);
if (lockState != s.segmentHeader.resetLockState()) {
report(corruptionListener, corruption, s.segmentIndex, () ->
format("lock of segment {} is not clear: {}",
s.segmentIndex, s.segmentHeader.lockStateToString(lockState))
);
s.segmentHeader.resetLock(s.segmentHeaderAddress);
}
}
private void zeroOutFirstSegmentTierCountersArea(
ChronicleHashCorruption.Listener corruptionListener,
ChronicleHashCorruptionImpl corruption) {
s.nextTierIndex(0);
if (s.prevTierIndex() != 0) {
report(corruptionListener, corruption, s.segmentIndex, () ->
format("stored prev tier index in first tier of segment {}: {}, should be 0",
s.segmentIndex, s.prevTierIndex())
);
s.prevTierIndex(0);
}
long tierCountersAreaAddr = s.tierCountersAreaAddr();
if (TierCountersArea.segmentIndex(tierCountersAreaAddr) != 0) {
report(corruptionListener, corruption, s.segmentIndex, () ->
format("stored segment index in first tier of segment {}: {}, should be 0",
s.segmentIndex, TierCountersArea.segmentIndex(tierCountersAreaAddr))
);
TierCountersArea.segmentIndex(tierCountersAreaAddr, 0);
}
if (TierCountersArea.tier(tierCountersAreaAddr) != 0) {
report(corruptionListener, corruption, s.segmentIndex, () ->
format("stored tier in first tier of segment {}: {}, should be 0",
s.segmentIndex, TierCountersArea.tier(tierCountersAreaAddr))
);
TierCountersArea.tier(tierCountersAreaAddr, 0);
}
}
}