<commit_before>/*
 *      Copyright (C) 2015  <EMAIL>
 *
 *      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.HashEntry;
import net.openhft.chronicle.hash.HashSegmentContext;
import net.openhft.chronicle.hash.impl.CompactOffHeapLinearHashTable;
import net.openhft.chronicle.hash.impl.VanillaChronicleHashHolder;
import net.openhft.chronicle.hash.impl.stage.entry.HashEntryStages;
import net.openhft.chronicle.hash.impl.stage.entry.HashLookupPos;
import net.openhft.chronicle.hash.impl.stage.hash.CheckOnEachPublicOperation;
import net.openhft.sg.StageRef;
import net.openhft.sg.Staged;

import java.util.function.Consumer;
import java.util.function.Predicate;

@Staged
public abstract class HashSegmentIteration<K, E extends HashEntry<K>>
        implements HashEntry<K>, HashSegmentContext<K, E> {
    
    @StageRef public IterationSegmentStages s;
    @StageRef HashEntryStages<K> e;
    @StageRef VanillaChronicleHashHolder<?> hh;
    @StageRef public CheckOnEachPublicOperation checkOnEachPublicOperation;
    @StageRef protected HashLookupPos hlp;
    
    public boolean shouldTestEntry() {
        return true;
    }

    public Object entryForIteration() {
        return this;
    }

    public long tierEntriesForIteration() {
        return s.tierEntries();
    }

    public boolean entryRemovedOnThisIteration = false;
    
    abstract boolean entryRemovedOnThisIterationInit();
    
    protected void initEntryRemovedOnThisIteration(boolean entryRemovedOnThisIteration) {
        this.entryRemovedOnThisIteration = entryRemovedOnThisIteration;
    }

    public long hashLookupEntry = 0;

    public abstract boolean hashLookupEntryInit();

    public void initHashLookupEntry(long entry) {
        hashLookupEntry = entry;
    }

    abstract void closeHashLookupEntry();

    @Override
    public boolean forEachSegmentEntryWhile(Predicate<? super E> predicate) {
        checkOnEachPublicOperation.checkOnEachPublicOperation();
        s.innerUpdateLock.lock();
        return innerForEachSegmentEntryWhile(predicate);
    }

    public <T> boolean innerForEachSegmentEntryWhile(Predicate<? super T> predicate) {
        try {
            s.goToLastTier();
            while (true) {
                int currentTier = s.tier;
                long currentTierBaseAddr = s.tierBaseAddr;
                long currentTierIndex = s.tierIndex;
                boolean interrupted = forEachTierEntryWhile(
                        predicate, currentTier, currentTierBaseAddr, currentTierIndex);
                if (interrupted)
                    return false;
                if (currentTier == 0)
                    return true;
                s.prevTier();
            }
        } finally {
            closeHashLookupEntry();
            s.innerReadLock.unlock();
            initEntryRemovedOnThisIteration(false);
        }
    }

    private <T> boolean forEachTierEntryWhile(
            Predicate<? super T> predicate,
            int currentTier, long currentTierBaseAddr, long tierIndex) {
        long leftEntries = tierEntriesForIteration();
        boolean interrupted = false;
        long startPos = 0L;
        CompactOffHeapLinearHashTable hashLookup = hh.h().hashLookup;
        while (!hashLookup.empty(hashLookup.readEntry(currentTierBaseAddr, startPos))) {
            startPos = hashLookup.step(startPos);
        }
        hlp.initHashLookupPos(startPos);
        long currentHashLookupPos;
        int steps = 0;
        do {
            // Step from hlp.hashLookupPos, not currentHashLookupPos (with additional initialization
            // of this local variable to startPos outside the loop), because if e.remove() is
            // called in the `predicate`, hlp.hashLookupPos is stepped back in doRemove(), and
            // currentHashLookupPos become invalid
            currentHashLookupPos = hashLookup.step(hlp.hashLookupPos);
            steps++;
            hlp.setHashLookupPos(currentHashLookupPos);
            long entry = hashLookup.readEntry(currentTierBaseAddr, currentHashLookupPos);
            initHashLookupEntry(entry);
            if (!hashLookup.empty(entry)) {
                e.readExistingEntry(hashLookup.value(entry));
                if (shouldTestEntry()) {
                    initEntryRemovedOnThisIteration(false);
                    try {
                        if (!predicate.test((T) entryForIteration())) {
                            interrupted = true;
                            break;
                        } else {
                            if (--leftEntries == 0)
                                break;
                        }
                    } finally {
                        // if doReplaceValue() -> relocation() -> alloc() -> nextTier()
                        // was called, restore the tier we were iterating over
                        if (s.tier != currentTier) {
                            s.initSegmentTier_WithBaseAddr(
                                    currentTier, currentTierBaseAddr, tierIndex);
                            // To cover shift deleted slot, at the next step forward.
                            // hash lookup entry is relocated to the next chained tier, and the
                            // slot in _current_ tier's hash lookup is shift deleted, see
                            // relocation()
                            currentHashLookupPos = hashLookup.stepBack(currentHashLookupPos);
                            steps--;
                            hlp.initHashLookupPos(currentHashLookupPos);
                        }
                        // force entry checksum update (delayedUpdateChecksum depends on keyOffset)
                        e.closeKeyOffset();
                    }
                }
            }
            // the `steps == 0` condition and this variable updates in the loop fix the bug, when
            // shift deletion occurs on the first entry of the tier, and the currentHashLookupPos
            // becomes equal to start pos without making the whole loop, but only visiting a single
            // entry
        } while (currentHashLookupPos != startPos || steps == 0);
        if (!interrupted && leftEntries > 0) {
            throw new IllegalStateException("We a tier without interruption, " +
                    "but according to tier counters there should be " + leftEntries +
                    " more entries. Size diverged?");
        }
        return interrupted;
    }

    @Override
    public void forEachSegmentEntry(Consumer<? super E> action) {
        forEachSegmentEntryWhile(e -> {
            action.accept(e);
            return true;<commit_msg>Release writeLock after visiting each entry in HashSegmentIteration.forEachTierEntryWhile()<commit_after>/*
 *      Copyright (C) 2015  <EMAIL>
 *
 *      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.HashEntry;
import net.openhft.chronicle.hash.HashSegmentContext;
import net.openhft.chronicle.hash.impl.CompactOffHeapLinearHashTable;
import net.openhft.chronicle.hash.impl.VanillaChronicleHashHolder;
import net.openhft.chronicle.hash.impl.stage.entry.HashEntryStages;
import net.openhft.chronicle.hash.impl.stage.entry.HashLookupPos;
import net.openhft.chronicle.hash.impl.stage.hash.CheckOnEachPublicOperation;
import net.openhft.sg.StageRef;
import net.openhft.sg.Staged;

import java.util.function.Consumer;
import java.util.function.Predicate;

@Staged
public abstract class HashSegmentIteration<K, E extends HashEntry<K>>
        implements HashEntry<K>, HashSegmentContext<K, E> {
    
    @StageRef public IterationSegmentStages s;
    @StageRef HashEntryStages<K> e;
    @StageRef VanillaChronicleHashHolder<?> hh;
    @StageRef public CheckOnEachPublicOperation checkOnEachPublicOperation;
    @StageRef protected HashLookupPos hlp;
    
    public boolean shouldTestEntry() {
        return true;
    }

    public Object entryForIteration() {
        return this;
    }

    public long tierEntriesForIteration() {
        return s.tierEntries();
    }

    public boolean entryRemovedOnThisIteration = false;
    
    abstract boolean entryRemovedOnThisIterationInit();
    
    protected void initEntryRemovedOnThisIteration(boolean entryRemovedOnThisIteration) {
        this.entryRemovedOnThisIteration = entryRemovedOnThisIteration;
    }

    public long hashLookupEntry = 0;

    public abstract boolean hashLookupEntryInit();

    public void initHashLookupEntry(long entry) {
        hashLookupEntry = entry;
    }

    abstract void closeHashLookupEntry();

    @Override
    public boolean forEachSegmentEntryWhile(Predicate<? super E> predicate) {
        checkOnEachPublicOperation.checkOnEachPublicOperation();
        s.innerUpdateLock.lock();
        return innerForEachSegmentEntryWhile(predicate);
    }

    public <T> boolean innerForEachSegmentEntryWhile(Predicate<? super T> predicate) {
        try {
            s.goToLastTier();
            while (true) {
                int currentTier = s.tier;
                long currentTierBaseAddr = s.tierBaseAddr;
                long currentTierIndex = s.tierIndex;
                boolean interrupted = forEachTierEntryWhile(
                        predicate, currentTier, currentTierBaseAddr, currentTierIndex);
                if (interrupted)
                    return false;
                if (currentTier == 0)
                    return true;
                s.prevTier();
            }
        } finally {
            closeHashLookupEntry();
            s.innerReadLock.unlock();
            initEntryRemovedOnThisIteration(false);
        }
    }

    private <T> boolean forEachTierEntryWhile(
            Predicate<? super T> predicate,
            int currentTier, long currentTierBaseAddr, long tierIndex) {
        long leftEntries = tierEntriesForIteration();
        boolean interrupted = false;
        long startPos = 0L;
        CompactOffHeapLinearHashTable hashLookup = hh.h().hashLookup;
        while (!hashLookup.empty(hashLookup.readEntry(currentTierBaseAddr, startPos))) {
            startPos = hashLookup.step(startPos);
        }
        hlp.initHashLookupPos(startPos);
        long currentHashLookupPos;
        int steps = 0;
        do {
            // Step from hlp.hashLookupPos, not currentHashLookupPos (with additional initialization
            // of this local variable to startPos outside the loop), because if e.remove() is
            // called in the `predicate`, hlp.hashLookupPos is stepped back in doRemove(), and
            // currentHashLookupPos become invalid
            currentHashLookupPos = hashLookup.step(hlp.hashLookupPos);
            steps++;
            hlp.setHashLookupPos(currentHashLookupPos);
            long entry = hashLookup.readEntry(currentTierBaseAddr, currentHashLookupPos);
            initHashLookupEntry(entry);
            if (!hashLookup.empty(entry)) {
                e.readExistingEntry(hashLookup.value(entry));
                if (shouldTestEntry()) {
                    initEntryRemovedOnThisIteration(false);
                    try {
                        if (!predicate.test((T) entryForIteration())) {
                            interrupted = true;
                            break;
                        } else {
                            if (--leftEntries == 0)
                                break;
                        }
                    } finally {
                        // if doReplaceValue() -> relocation() -> alloc() -> nextTier()
                        // was called, restore the tier we were iterating over
                        if (s.tier != currentTier) {
                            s.initSegmentTier_WithBaseAddr(
                                    currentTier, currentTierBaseAddr, tierIndex);
                            // To cover shift deleted slot, at the next step forward.
                            // hash lookup entry is relocated to the next chained tier, and the
                            // slot in _current_ tier's hash lookup is shift deleted, see
                            // relocation()
                            currentHashLookupPos = hashLookup.stepBack(currentHashLookupPos);
                            steps--;
                            hlp.initHashLookupPos(currentHashLookupPos);
                        }
                        s.innerWriteLock.unlock();
                        // force entry checksum update (delayedUpdateChecksum depends on keyOffset)
                        e.closeKeyOffset();
                    }
                }
            }
            // the `steps == 0` condition and this variable updates in the loop fix the bug, when
            // shift deletion occurs on the first entry of the tier, and the currentHashLookupPos
            // becomes equal to start pos without making the whole loop, but only visiting a single
            // entry
        } while (currentHashLookupPos != startPos || steps == 0);
        if (!interrupted && leftEntries > 0) {
            throw new IllegalStateException("We a tier without interruption, " +
                    "but according to tier counters there should be " + leftEntries +
                    " more entries. Size diverged?");
        }
        return interrupted;
    }

    @Override
    public void forEachSegmentEntry(Consumer<? super E> action) {
        forEachSegmentEntryWhile(e -> {
            action.accept(e);
            return true;
{'fraction_non_alphanumeric': 0.07402413537382863, 'fraction_numerical': 0.0017528483786152498, 'mean_word_length': 1.9991912656692277, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4559032', 'n_tokens_mistral': 3738, 'n_tokens_neox': 3583, 'n_words': 1017}