diff --git a/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/Alloc.java b/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/Alloc.java new file mode 100644 index 000000000..3ff9bee52 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/Alloc.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 higherfrequencytrading.com + * + * 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 . + */ + +package net.openhft.chronicle.hash.impl.stage.entry; + +public interface Alloc { + + /** + * Allocates a position in a segment tier. + * + * @param chunks chunks to allocate + * @return the position, if allocation is successful, or -1 + */ + long alloc(int chunks); +} diff --git a/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/AllocatedChunks.java b/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/AllocatedChunks.java index 77988a950..268946336 100644 --- a/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/AllocatedChunks.java +++ b/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/AllocatedChunks.java @@ -26,6 +26,7 @@ public class AllocatedChunks { @StageRef public VanillaChronicleHashHolder hh; @StageRef public SegmentStages s; @StageRef public HashEntryStages entry; + @StageRef public Alloc alloc; public int allocatedChunks = 0; @@ -50,8 +51,8 @@ public class AllocatedChunks { long oldKeySizeAddr = oldSegmentTierBaseAddr + entry.keySizeOffset; long oldKeyAddr = oldSegmentTierBaseAddr + entry.keyOffset; int tierBeforeAllocation = s.segmentTier; - long pos = s.alloc(allocatedChunks); + long pos = alloc.alloc(allocatedChunks); entry.copyExistingEntry(pos, bytesToCopy, oldKeyAddr, oldKeySizeAddr); - return s.segmentTier > tierBeforeAllocation; + return s.segmentTier != tierBeforeAllocation; } } diff --git a/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/SegmentStages.java b/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/SegmentStages.java index 53d727d46..ca4320bcb 100644 --- a/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/SegmentStages.java +++ b/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/SegmentStages.java @@ -524,6 +524,18 @@ public abstract class SegmentStages implements SegmentLock, LocksInterface { throw new IllegalStateException("first tier doesn't have previous"); initSegmentTier(segmentTier - 1, prevTierIndex()); } + + public void goToLastTier() { + while (hasNextTier()) { + nextTier(); + } + } + + public void goToFirstTier() { + while (segmentTier != 0) { + prevTier(); + } + } @Stage("Segment") public final PublicMultiStoreBytes segmentBytes = new PublicMultiStoreBytes(); @Stage("Segment") public final PointerBytesStore segmentBS = new PointerBytesStore(); @@ -589,17 +601,6 @@ public abstract class SegmentStages implements SegmentLock, LocksInterface { return ret; } - public long alloc(int chunks) { - while (true) { - long ret = allocReturnCode(chunks); - if (ret >= 0) { - return ret; - } else { - nextTier(); - } - } - } - public void free(long fromPos, int chunks) { freeList.clearRange(fromPos, fromPos + chunks); if (fromPos < nextPosToSearchFrom()) diff --git a/src/main/java/net/openhft/chronicle/hash/impl/stage/iter/HashSegmentIteration.java b/src/main/java/net/openhft/chronicle/hash/impl/stage/iter/HashSegmentIteration.java index cd35cc651..55552b6eb 100644 --- a/src/main/java/net/openhft/chronicle/hash/impl/stage/iter/HashSegmentIteration.java +++ b/src/main/java/net/openhft/chronicle/hash/impl/stage/iter/HashSegmentIteration.java @@ -62,12 +62,6 @@ public abstract class HashSegmentIteration> abstract void closeHashLookupEntry(); - private void goToLastTier() { - while (s.hasNextTier()) { - s.nextTier(); - } - } - @Override public boolean forEachSegmentEntryWhile(Predicate action) { s.innerUpdateLock.lock(); @@ -75,7 +69,7 @@ public abstract class HashSegmentIteration> long size = s.size(); if (size == 0) return true; - goToLastTier(); + s.goToLastTier(); while (true) { int currentTier = s.segmentTier; long currentTierBaseAddr = s.segmentBaseAddr; diff --git a/src/main/java/net/openhft/chronicle/hash/impl/stage/iter/IterationAlloc.java b/src/main/java/net/openhft/chronicle/hash/impl/stage/iter/IterationAlloc.java new file mode 100644 index 000000000..97a73098f --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/impl/stage/iter/IterationAlloc.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015 higherfrequencytrading.com + * + * 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 . + */ + +package net.openhft.chronicle.hash.impl.stage.iter; + +import net.openhft.chronicle.hash.impl.stage.entry.Alloc; +import net.openhft.chronicle.hash.impl.stage.entry.SegmentStages; +import net.openhft.sg.StageRef; +import net.openhft.sg.Staged; + +@Staged +public class IterationAlloc implements Alloc { + + @StageRef public SegmentStages s; + + /** + * Move only to next tiers, to avoid double visiting of relocated entries during iteration + */ + @Override + public long alloc(int chunks) { + while (true) { + long ret = s.allocReturnCode(chunks); + if (ret >= 0) { + return ret; + } else { + s.nextTier(); + } + } + } +} diff --git a/src/main/java/net/openhft/chronicle/hash/impl/stage/query/QueryAlloc.java b/src/main/java/net/openhft/chronicle/hash/impl/stage/query/QueryAlloc.java new file mode 100644 index 000000000..687082c19 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/impl/stage/query/QueryAlloc.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 higherfrequencytrading.com + * + * 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 . + */ + +package net.openhft.chronicle.hash.impl.stage.query; + +import net.openhft.chronicle.hash.impl.stage.entry.Alloc; +import net.openhft.chronicle.hash.impl.stage.entry.SegmentStages; +import net.openhft.sg.StageRef; +import net.openhft.sg.Staged; + +@Staged +public class QueryAlloc implements Alloc { + + @StageRef public SegmentStages s; + + @Override + public long alloc(int chunks) { + long ret = s.allocReturnCode(chunks); + if (ret >= 0) + return ret; + int alreadyAttemptedTier = s.segmentTier; + s.goToFirstTier(); + while (true) { + if (s.segmentTier != alreadyAttemptedTier) { + ret = s.allocReturnCode(chunks); + if (ret >= 0) + return ret; + } + s.nextTier(); + } + } +} diff --git a/src/main/java/net/openhft/chronicle/hash/impl/stage/query/SearchAllocatedChunks.java b/src/main/java/net/openhft/chronicle/hash/impl/stage/query/SearchAllocatedChunks.java index d15b47938..24ad23d91 100644 --- a/src/main/java/net/openhft/chronicle/hash/impl/stage/query/SearchAllocatedChunks.java +++ b/src/main/java/net/openhft/chronicle/hash/impl/stage/query/SearchAllocatedChunks.java @@ -44,8 +44,8 @@ public class SearchAllocatedChunks extends AllocatedChunks { // clears out searchState, and it performs the search again, but in inconsistent state incrementSegmentEntriesIfNeeded(); int tierBeforeAllocation = s.segmentTier; - long pos = s.alloc(allocatedChunks); + long pos = alloc.alloc(allocatedChunks); entry.writeNewEntry(pos, ks.inputKey); - return s.segmentTier > tierBeforeAllocation; + return s.segmentTier != tierBeforeAllocation; } } diff --git a/src/main/java/net/openhft/chronicle/map/impl/MapIterationContext.java b/src/main/java/net/openhft/chronicle/map/impl/MapIterationContext.java index 2b231e42c..dd03d40f1 100644 --- a/src/main/java/net/openhft/chronicle/map/impl/MapIterationContext.java +++ b/src/main/java/net/openhft/chronicle/map/impl/MapIterationContext.java @@ -22,6 +22,7 @@ import net.openhft.chronicle.hash.impl.stage.hash.KeyBytesInterop; import net.openhft.chronicle.hash.impl.stage.hash.LogHolder; import net.openhft.chronicle.hash.impl.stage.hash.OwnerThreadHolder; import net.openhft.chronicle.hash.impl.stage.hash.ThreadLocalCopiesHolder; +import net.openhft.chronicle.hash.impl.stage.iter.IterationAlloc; import net.openhft.chronicle.hash.impl.stage.iter.IterationKeyHashCode; import net.openhft.chronicle.hash.impl.stage.iter.IterationSegmentStages; import net.openhft.chronicle.hash.impl.stage.query.KeySearch; @@ -61,6 +62,8 @@ import net.openhft.sg.Staged; MapEntryStages.class, ValueBytesInterop.class, MapEntryOperationsDelegation.class, + + IterationAlloc.class, }, nested = { ReadLock.class, UpdateLock.class, diff --git a/src/main/java/net/openhft/chronicle/map/impl/MapQueryContext.java b/src/main/java/net/openhft/chronicle/map/impl/MapQueryContext.java index 8e2e62e99..896f9ff32 100644 --- a/src/main/java/net/openhft/chronicle/map/impl/MapQueryContext.java +++ b/src/main/java/net/openhft/chronicle/map/impl/MapQueryContext.java @@ -70,6 +70,8 @@ import net.openhft.sg.Staged; HashInputBytes.class, MapInputBytesValues.class, + + QueryAlloc.class, }, nested = { ReadLock.class, diff --git a/src/main/java/net/openhft/chronicle/map/impl/ReplicatedMapIterationContext.java b/src/main/java/net/openhft/chronicle/map/impl/ReplicatedMapIterationContext.java index 6dbc88cee..5f53b05a0 100644 --- a/src/main/java/net/openhft/chronicle/map/impl/ReplicatedMapIterationContext.java +++ b/src/main/java/net/openhft/chronicle/map/impl/ReplicatedMapIterationContext.java @@ -22,6 +22,7 @@ import net.openhft.chronicle.hash.impl.stage.hash.KeyBytesInterop; import net.openhft.chronicle.hash.impl.stage.hash.LogHolder; import net.openhft.chronicle.hash.impl.stage.hash.OwnerThreadHolder; import net.openhft.chronicle.hash.impl.stage.hash.ThreadLocalCopiesHolder; +import net.openhft.chronicle.hash.impl.stage.iter.IterationAlloc; import net.openhft.chronicle.hash.impl.stage.iter.IterationKeyHashCode; import net.openhft.chronicle.hash.impl.stage.iter.IterationSegmentStages; import net.openhft.chronicle.hash.impl.stage.query.KeySearch; @@ -65,6 +66,8 @@ import net.openhft.sg.Staged; ReplicationUpdate.class, DefaultValue.class, + + IterationAlloc.class, }, nested = { ReadLock.class, UpdateLock.class, diff --git a/src/main/java/net/openhft/chronicle/map/impl/ReplicatedMapQueryContext.java b/src/main/java/net/openhft/chronicle/map/impl/ReplicatedMapQueryContext.java index 6e1fbd54f..e728b8d65 100644 --- a/src/main/java/net/openhft/chronicle/map/impl/ReplicatedMapQueryContext.java +++ b/src/main/java/net/openhft/chronicle/map/impl/ReplicatedMapQueryContext.java @@ -25,10 +25,7 @@ import net.openhft.chronicle.hash.impl.stage.hash.LogHolder; import net.openhft.chronicle.hash.impl.stage.hash.OwnerThreadHolder; import net.openhft.chronicle.hash.impl.stage.hash.ThreadLocalCopiesHolder; import net.openhft.chronicle.hash.impl.stage.input.HashInputBytes; -import net.openhft.chronicle.hash.impl.stage.query.KeySearch; -import net.openhft.chronicle.hash.impl.stage.query.QueryHashLookupPos; -import net.openhft.chronicle.hash.impl.stage.query.QueryHashLookupSearch; -import net.openhft.chronicle.hash.impl.stage.query.SearchAllocatedChunks; +import net.openhft.chronicle.hash.impl.stage.query.*; import net.openhft.chronicle.map.impl.stage.data.DummyValueZeroData; import net.openhft.chronicle.map.impl.stage.data.bytes.*; import net.openhft.chronicle.map.impl.stage.data.instance.InputValueInstanceData; @@ -38,6 +35,7 @@ import net.openhft.chronicle.map.impl.stage.input.MapInputBytesValues; import net.openhft.chronicle.map.impl.stage.input.ReplicatedInput; import net.openhft.chronicle.map.impl.stage.map.*; import net.openhft.chronicle.map.impl.stage.query.*; +import net.openhft.chronicle.map.impl.stage.replication.ReplicatedQueryAlloc; import net.openhft.chronicle.map.impl.stage.replication.ReplicationUpdate; import net.openhft.chronicle.map.impl.stage.ret.BytesReturnValue; import net.openhft.chronicle.map.impl.stage.ret.DefaultReturnValue; @@ -56,7 +54,7 @@ import net.openhft.sg.Staged; ReplicatedChronicleMapHolderImpl.class, KeyBytesInterop.class, - ReplicatedQuerySegmentStages.class, + QuerySegmentStages.class, KeySearch.class, InputKeyHashCode.class, QueryHashLookupSearch.class, @@ -78,6 +76,8 @@ import net.openhft.sg.Staged; HashInputBytes.class, MapInputBytesValues.class, + + ReplicatedQueryAlloc.class, }, nested = { ReadLock.class, diff --git a/src/main/java/net/openhft/chronicle/map/impl/stage/query/ReplicatedQuerySegmentStages.java b/src/main/java/net/openhft/chronicle/map/impl/stage/query/ReplicatedQuerySegmentStages.java deleted file mode 100644 index 26a2b82d6..000000000 --- a/src/main/java/net/openhft/chronicle/map/impl/stage/query/ReplicatedQuerySegmentStages.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2015 higherfrequencytrading.com - * - * 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 . - */ - -package net.openhft.chronicle.map.impl.stage.query; - -import net.openhft.chronicle.hash.ReplicatedHashSegmentContext; -import net.openhft.chronicle.hash.impl.stage.query.QuerySegmentStages; -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.ReplicatedChronicleMapHolder; -import net.openhft.sg.StageRef; -import net.openhft.sg.Staged; - -import java.util.function.Consumer; - -@Staged -public abstract class ReplicatedQuerySegmentStages extends QuerySegmentStages { - - @StageRef - ReplicatedChronicleMapHolder mh; - - final Consumer cleanupAction = e -> { - ReplicatedChronicleMap map = mh.m(); - if (e instanceof MapAbsentEntry) { - long deleteTimeout = map.timeProvider.currentTime() - e.originTimestamp(); - if (deleteTimeout > map.cleanupTimeout && !e.isChanged()) - e.doRemoveCompletely(); - } - }; - - @Override - public long alloc(int chunks) { - while (true) { - long ret = allocReturnCode(chunks); - if (ret >= 0) { - return ret; - } else { - ReplicatedChronicleMap map = mh.m(); - try (MapSegmentContext sc = map.segmentContext(segmentIndex)) { - ((ReplicatedHashSegmentContext) sc) - .forEachSegmentReplicableEntry(cleanupAction); - } - ret = allocReturnCode(chunks); - if (ret >= 0) { - return ret; - } else { - nextTier(); - } - } - } - } -} diff --git a/src/main/java/net/openhft/chronicle/map/impl/stage/replication/ReplicatedQueryAlloc.java b/src/main/java/net/openhft/chronicle/map/impl/stage/replication/ReplicatedQueryAlloc.java new file mode 100644 index 000000000..73ae1bea5 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/impl/stage/replication/ReplicatedQueryAlloc.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2015 higherfrequencytrading.com + * + * 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 . + */ + +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.ReplicatedChronicleMapHolder; +import net.openhft.sg.StageRef; +import net.openhft.sg.Staged; + +import java.util.function.Consumer; + +@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. + */ + public boolean forcedOldDeletedEntriesCleanup() { + ReplicatedChronicleMap map = mh.m(); + try (MapSegmentContext sc = map.segmentContext(s.segmentIndex)) { + cleanupAction.removedCompletely = 0; + ((ReplicatedHashSegmentContext) sc) + .forEachSegmentReplicableEntry(cleanupAction); + return cleanupAction.removedCompletely > 0; + } + } + + private class CleanupAction implements Consumer { + int removedCompletely; + + @Override + public void accept(ReplicableEntry e) { + ReplicatedChronicleMap map = mh.m(); + if (e instanceof MapAbsentEntry) { + long deleteTimeout = map.timeProvider.currentTime() - e.originTimestamp(); + map.timeProvider.systemTimeIntervalBetween(e.originTimestamp(), + map.timeProvider.currentTime(), map.cleanupTimeoutUnit); + if (deleteTimeout > map.cleanupTimeout && !e.isChanged()) { + e.doRemoveCompletely(); + removedCompletely++; + } + } + } + } + + @Override + public long alloc(int chunks) { + long ret = s.allocReturnCode(chunks); + if (ret >= 0) + return ret; + int alreadyAttemptedTier; + if (!forcedOldDeletedEntriesCleanup()) { + alreadyAttemptedTier = s.segmentTier; + } else { + // If forced old deleted entries cleanup deleted something, don't skip the first queried + // tier, because it could have new empty chunks now. segment tier is >= 0, so set to -1 + // to make the condition with alreadyAttemptedTier always false + alreadyAttemptedTier = -1; + } + s.goToFirstTier(); + while (true) { + if (s.segmentTier != alreadyAttemptedTier) { + ret = s.allocReturnCode(chunks); + if (ret >= 0) + return ret; + } + s.nextTier(); + } + } +}