/*
* 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.map.impl.stage.replication;
import net.openhft.chronicle.hash.ReplicatedHashSegmentContext;
import net.openhft.chronicle.hash.impl.stage.entry.SegmentStages;
import net.openhft.chronicle.hash.impl.stage.query.QueryAlloc;
import net.openhft.chronicle.hash.replication.ReplicableEntry;
import net.openhft.chronicle.map.MapAbsentEntry;
import net.openhft.chronicle.map.MapSegmentContext;
import net.openhft.chronicle.map.ReplicatedChronicleMap;
import net.openhft.chronicle.map.impl.IterationContext;
import net.openhft.chronicle.map.impl.ReplicatedChronicleMapHolder;
import net.openhft.sg.StageRef;
import net.openhft.sg.Staged;
import java.util.function.Consumer;
import static net.openhft.chronicle.hash.replication.TimeProvider.currentTime;
import static net.openhft.chronicle.hash.replication.TimeProvider.systemTimeIntervalBetween;
@Staged
public class ReplicatedQueryAlloc extends QueryAlloc {
@StageRef ReplicatedChronicleMapHolder<?, ?, ?> mh;
@StageRef SegmentStages s;
final CleanupAction cleanupAction = new CleanupAction();
/**
* Returns {@code true} if at least one old deleted entry was removed.
* @param prevPos position to skip during cleanup (because cleaned up separately)
*/
public boolean forcedOldDeletedEntriesCleanup(long prevPos) {
ReplicatedChronicleMap<?, ?, ?> map = mh.m();
if (!map.cleanupRemovedEntries)
return false;
try (MapSegmentContext<?, ?, ?> sc = map.segmentContext(s.segmentIndex)) {
cleanupAction.removedCompletely = 0;
cleanupAction.posToSkip = prevPos;
cleanupAction.iterationContext = (IterationContext<?, ?, ?>) sc;
((ReplicatedHashSegmentContext<?, ?>) sc)
.forEachSegmentReplicableEntry(cleanupAction);
return cleanupAction.removedCompletely > 0;
}
}
private class CleanupAction implements Consumer<ReplicableEntry> {
int removedCompletely;
long posToSkip;
IterationContext<?, ?, ?> iterationContext;
@Override
public void accept(ReplicableEntry e) {
ReplicatedChronicleMap<?, ?, ?> map = mh.m();
if (!(e instanceof MapAbsentEntry) || iterationContext.pos() == posToSkip)
return;
long currentTime = currentTime();
if (e.originTimestamp() > currentTime)
return; // presumably unsynchronized clocks
long deleteTimeout = systemTimeIntervalBetween(
e.originTimestamp(), currentTime, map.cleanupTimeoutUnit);
if (deleteTimeout <= map.cleanupTimeout || e.isChanged())
return;
e.doRemoveCompletely();
removedCompletely++;
}
}
@Override
public long alloc(int chunks, long prevPos, int prevChunks) {
long ret = s.allocReturnCode(chunks);
if (ret >= 0) {
if (prevPos >= 0)
s.free(prevPos, prevChunks);
return ret;
}
int firstAttemptedTier = s.tier;
long firstAttemptedTierIndex = s.tierIndex;
long firstAttemptedTierBaseAddr = s.tierBaseAddr;
boolean cleanedFirstAttemptedTier = forcedOldDeletedEntriesCleanup(prevPos);
s.goToFirstTier();
while (true) {
boolean visitingFirstAttemptedTier = s.tier == firstAttemptedTier;
if (cleanedFirstAttemptedTier || !visitingFirstAttemptedTier) {
ret = s.allocReturnCode(chunks);
if (ret >= 0) {
if (prevPos >= 0) {
if (visitingFirstAttemptedTier) {
s.free(prevPos, prevChunks);
} else if (s.tier < firstAttemptedTier) {
int currentTier = s.tier;
long currentTierIndex = s.tierIndex;
long currentTierBaseAddr = s.tierBaseAddr;
s.initSegmentTier(firstAttemptedTier, firstAttemptedTierIndex,
firstAttemptedTierBaseAddr);
s.free(prevPos, prevChunks);
s.initSegmentTier(currentTier, currentTierIndex, currentTierBaseAddr);
}
}
return ret;
}
}
if (visitingFirstAttemptedTier && prevPos >= 0)
s.free(prevPos, prevChunks);
s.nextTier();
}
}
}