mirror of
https://github.com/OpenHFT/Chronicle-Map.git
synced 2021-09-13 01:26:12 +00:00
HCOLL-378 #36 Add ChronicleHashBuilder.recoverPersistedTo() and ChronicleHashInstanceBuilder.recover() functionality
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
|
||||
package net.openhft.chronicle.hash;
|
||||
|
||||
import net.openhft.chronicle.bytes.RandomDataInput;
|
||||
|
||||
/**
|
||||
* Defines reasonable defaults for {@code Data}'s {@code equals()}, {@code hashCode()} and
|
||||
* {@code toString()}. They should be default implementations in the {@code Data} interface itself,
|
||||
@@ -47,10 +49,26 @@ public abstract class AbstractData<T> implements Data<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@code Data}'s <i>object</i> {@code toString()}.
|
||||
* Delegates to {@code Data}'s <i>object</i> {@code toString()}. If deserialization fails with
|
||||
* exception (e. g. if data bytes are corrupted, and represent not a valid serialized form of
|
||||
* an object), traces the data's bytes and the exception.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return get().toString();
|
||||
T object;
|
||||
try {
|
||||
object = get();
|
||||
} catch (Exception e) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("failed to deserialize object from data with bytes: [");
|
||||
RandomDataInput bs = bytes();
|
||||
for (long off = offset(), lim = offset() + size(); off < lim; off++) {
|
||||
sb.append(bs.printable(off));
|
||||
}
|
||||
sb.append("], exception: ");
|
||||
sb.append(e);
|
||||
return sb.toString();
|
||||
}
|
||||
return object.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import net.openhft.chronicle.set.ChronicleSetBuilder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -615,11 +616,54 @@ public interface ChronicleHashBuilder<K, H extends ChronicleHash<K, ?, ?, ?>,
|
||||
* @see ChronicleHash#close()
|
||||
* @see #create()
|
||||
* @see ChronicleHashInstanceBuilder#persistedTo(File)
|
||||
* @see #recoverPersistedTo(File, boolean)
|
||||
*/
|
||||
H createPersistedTo(File file) throws IOException;
|
||||
|
||||
/**
|
||||
* Recovers and opens the hash container, persisted to the specified file, using {@link
|
||||
* #createPersistedTo(File)} method. This method should be used to open a persisted Chronicle
|
||||
* Map after accessor process crash (that might leave some locks "acquired" therefore some
|
||||
* segments inaccessible), or accessor process external termination (that, in addition to
|
||||
* inaccessible segments, might lead to leaks in the Chronicle Hash memory), or sudden power
|
||||
* loss, or file corruption (that, in addition to the already mentioned consequences, might lead
|
||||
* to data corruption, i. e. presence of entries which were never put into the Chronicle Hash).
|
||||
*
|
||||
* <p>This method, unlike {@link #createPersistedTo(File)}, expects the mapping file already
|
||||
* exists.
|
||||
*
|
||||
* <p>At the moment this method is called and executed, no other thread or process should be
|
||||
* mapping to the given file, and trying to access the given file.
|
||||
*
|
||||
* <p>It is strongly recommended to configure this builder with the same configurations, as
|
||||
* the builder, that created the given file for the first time, and pass {@code true} as the
|
||||
* {@code sameBuilderConfig} argument (another requirement is running the same version of the
|
||||
* Chronicle Map library). Otherwise, if the header of the given persisted Chronicle Hash file
|
||||
* is corrupted, this method is likely to be unable to recover and throw {@link
|
||||
* ChronicleHashRecoveryFailedException}, or even worse, to corrupt the file further. However,
|
||||
* the header shouldn't be corrupted on process crash/termination or power loss, only on direct
|
||||
* file corruption.
|
||||
*
|
||||
* @param file a hash container was mapped to the given file
|
||||
* @param sameBuilderConfig if this builder is configured with the same configurations, as the
|
||||
* builder, which created the file (the persisted Chronicle Hash instance) for the first time,
|
||||
* and with the same version of Chronicle Map library. In this case, the header of the file is
|
||||
* overridden (with presumably the same configurations), protecting from {@link
|
||||
* ChronicleHashRecoveryFailedException}, if the header is corrupted.
|
||||
* @return a recovered Chronicle Hash instance, mapped to the given file
|
||||
* @throws FileNotFoundException if the file doesn't exist
|
||||
* @throws IOException if any IO error occurs on reading data from the file, or related to
|
||||
* off-heap memory allocation or file mapping, or establishing replication connections. Probably
|
||||
* the file is corrupted on OS level, and should be recovered on that level first, before
|
||||
* calling this procedure.
|
||||
* @throws ChronicleHashRecoveryFailedException if recovery is impossible
|
||||
* @see #createPersistedTo(File)
|
||||
*/
|
||||
H recoverPersistedTo(File file, boolean sameBuilderConfig) throws IOException;
|
||||
|
||||
/**
|
||||
* @deprecated don't use private API in the client code
|
||||
*/
|
||||
@Deprecated
|
||||
ChronicleHashBuilderPrivateAPI<K> privateAPI();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import net.openhft.chronicle.map.ChronicleMap;
|
||||
import net.openhft.chronicle.set.ChronicleSet;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
@@ -101,17 +102,47 @@ public interface ChronicleHashInstanceBuilder<H extends ChronicleHash> {
|
||||
|
||||
/**
|
||||
* Creates (or opens existing, if {@link #persistedTo(File)} is configured with the existing
|
||||
* file) a Chronicle Hash from this builder. After this method is called once the builder
|
||||
* couldn't be used to create new Chronicle Hash instances, a new {@code
|
||||
* file) and returns a Chronicle Hash from this builder. After this method is called once the
|
||||
* builder couldn't be used to create new Chronicle Hash instances, a new {@code
|
||||
* ChronicleHashInstanceBuilder} should be created via {@link ChronicleHashBuilder#instance()}.
|
||||
*
|
||||
* @return a new in-memory or persisted Chronicle Hash instance
|
||||
* @throws IOException if any IO error, related to off-heap memory allocation or file mapping,
|
||||
* or establishing replication connections, occurs
|
||||
* @throws IllegalStateException if {@code create()} method has already been called on this
|
||||
* @throws IllegalStateException if {@code create()} or {@link #recover(boolean)} method has
|
||||
* already been called on this
|
||||
* {@code ChronicleHashInstanceBuilder}
|
||||
* @see ChronicleHashBuilder#create()
|
||||
* @see ChronicleHashBuilder#createPersistedTo(File)
|
||||
*/
|
||||
H create() throws IOException;
|
||||
|
||||
/**
|
||||
* Recovers and returns a Chronicle Hash instance, persisted to the file, configured via {@link
|
||||
* #persistedTo(File)} method. See documentation to {@link
|
||||
* ChronicleHashBuilder#recoverPersistedTo(File, boolean)} for more information of Chronicle
|
||||
* Hash instance recovery.
|
||||
*
|
||||
* @param sameBuilderConfig if the {@link ChronicleHashBuilder}, from which this {@code
|
||||
* ChronicleHashInstanceBuilder} inherits (i. e. this {@code ChronicleHashInstanceBuilder} is
|
||||
* created via calling {@link ChronicleHashBuilder#instance()} on that {@link
|
||||
* ChronicleHashBuilder}) is configured with the same configurations, as the builder, which
|
||||
* created the persistence file (the persisted Chronicle Hash instance) for the first time, and
|
||||
* with the same version of the Chronicle Map library. In this case, the header of the file is
|
||||
* overridden (with presumably the same configurations), protecting from {@link
|
||||
* ChronicleHashRecoveryFailedException}, if the header is corrupted.
|
||||
* @return a recovered Chronicle Hash instance, mapped to the specified file
|
||||
* @throws IllegalStateException if persistence file is not configured (or explicitly configured
|
||||
* to {@code null}) for this {@code ChronicleHashInstanceBuilder}, using {@link
|
||||
* #persistedTo(File)} method, or if {@link #create()} or {@code recover()} method has already
|
||||
* been called on this builder
|
||||
* @throws FileNotFoundException if the specified persistence file doesn't exist
|
||||
* @throws IOException if any IO error occurs on reading data from the file, or related to
|
||||
* off-heap memory allocation or file mapping, or establishing replication connections. Probably
|
||||
* the file is corrupted on OS level, and should be recovered on that level first, before
|
||||
* calling this procedure.
|
||||
* @throws ChronicleHashRecoveryFailedException if recovery is impossible
|
||||
* @see ChronicleHashBuilder#recoverPersistedTo(File, boolean)
|
||||
*/
|
||||
H recover(boolean sameBuilderConfig) throws IOException;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.openhft.chronicle.hash;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* This exception is thrown, when a Chronicle Hash recovery using {@link
|
||||
* ChronicleHashBuilder#recoverPersistedTo(File, boolean)} or {@link
|
||||
* ChronicleHashInstanceBuilder#recover(boolean)} method is impossible, for example, if the
|
||||
* persistence file is corrupted too much.
|
||||
*
|
||||
* @see ChronicleHashBuilder#recoverPersistedTo(File, boolean)
|
||||
* @see ChronicleHashInstanceBuilder#recover(boolean)
|
||||
*/
|
||||
public final class ChronicleHashRecoveryFailedException extends RuntimeException {
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ChronicleHashRecoveryFailedException} with the specified cause.
|
||||
*
|
||||
* @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method)
|
||||
*/
|
||||
public ChronicleHashRecoveryFailedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ChronicleHashRecoveryFailedException} with the specified detail
|
||||
* message.
|
||||
*
|
||||
* @param message the detail message. The detail message is saved for later retrieval by the
|
||||
* {@link #getMessage()} method.
|
||||
*/
|
||||
public ChronicleHashRecoveryFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -377,4 +377,24 @@ public final class BigSegmentHeader implements SegmentHeader {
|
||||
public void downgradeWriteToReadLock(long address) {
|
||||
LOCK.downgradeWriteToReadLock(A, null, address + LOCK_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetLock(long address) {
|
||||
LOCK.reset(A, null, address + LOCK_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long resetLockState() {
|
||||
return LOCK.resetState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLockState(long address) {
|
||||
return LOCK.getState(A, null, address + LOCK_OFFSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lockStateToString(long lockState) {
|
||||
return LOCK.toString(lockState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ public abstract class CompactOffHeapLinearHashTable {
|
||||
return (entry >>> keyBits) & valueMask;
|
||||
}
|
||||
|
||||
long entry(long key, long value) {
|
||||
public long entry(long key, long value) {
|
||||
return key | (value << keyBits);
|
||||
}
|
||||
|
||||
@@ -143,9 +143,9 @@ public abstract class CompactOffHeapLinearHashTable {
|
||||
writeEntryVolatile(addr, pos, key(currentEntry), value);
|
||||
}
|
||||
|
||||
abstract void writeEntry(long addr, long pos, long newEntry);
|
||||
public abstract void writeEntry(long addr, long pos, long newEntry);
|
||||
|
||||
abstract void clearEntry(long addr, long pos);
|
||||
public abstract void clearEntry(long addr, long pos);
|
||||
|
||||
/**
|
||||
* Returns "insert" position in terms of consequent putValue()
|
||||
@@ -176,4 +176,17 @@ public abstract class CompactOffHeapLinearHashTable {
|
||||
clearEntry(addr, posToRemove);
|
||||
return posToRemove;
|
||||
}
|
||||
|
||||
public void insert(long addr, long key, long value) {
|
||||
long startPos = hlPos(key);
|
||||
long pos = startPos;
|
||||
do {
|
||||
if (empty(readEntry(addr, pos))) {
|
||||
writeEntry(addr, pos, entry(key, value));
|
||||
return;
|
||||
}
|
||||
pos = step(pos);
|
||||
} while (pos != startPos);
|
||||
throw new AssertionError("HashLookup overflow should never occur");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,12 +52,12 @@ public final class IntCompactOffHeapLinearHashTable extends CompactOffHeapLinear
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeEntry(long addr, long pos, long newEntry) {
|
||||
public void writeEntry(long addr, long pos, long newEntry) {
|
||||
OS.memory().writeInt(addr + pos, (int) newEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
void clearEntry(long addr, long pos) {
|
||||
public void clearEntry(long addr, long pos) {
|
||||
OS.memory().writeInt(addr + pos, 0);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -52,12 +52,12 @@ public final class LongCompactOffHeapLinearHashTable extends CompactOffHeapLinea
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeEntry(long addr, long pos, long newEntry) {
|
||||
public void writeEntry(long addr, long pos, long newEntry) {
|
||||
OS.memory().writeLong(addr + pos, newEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
void clearEntry(long addr, long pos) {
|
||||
public void clearEntry(long addr, long pos) {
|
||||
OS.memory().writeLong(addr + pos, 0L);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,4 +59,9 @@ public interface SegmentHeader {
|
||||
void writeUnlock(long address);
|
||||
void downgradeWriteToUpdateLock(long address);
|
||||
void downgradeWriteToReadLock(long address);
|
||||
|
||||
void resetLock(long address);
|
||||
long resetLockState();
|
||||
long getLockState(long address);
|
||||
String lockStateToString(long lockState);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package net.openhft.chronicle.hash.impl;
|
||||
|
||||
import net.openhft.chronicle.algo.bytes.Access;
|
||||
import net.openhft.chronicle.algo.locks.*;
|
||||
import net.openhft.chronicle.bytes.BytesStore;
|
||||
import net.openhft.chronicle.bytes.MappedBytesStoreFactory;
|
||||
@@ -30,6 +29,7 @@ import net.openhft.chronicle.hash.serialization.SizeMarshaller;
|
||||
import net.openhft.chronicle.hash.serialization.SizedReader;
|
||||
import net.openhft.chronicle.hash.serialization.impl.SerializationBuilder;
|
||||
import net.openhft.chronicle.map.ChronicleMapBuilder;
|
||||
import net.openhft.chronicle.map.impl.IterationContext;
|
||||
import net.openhft.chronicle.values.Values;
|
||||
import net.openhft.chronicle.wire.Marshallable;
|
||||
import net.openhft.chronicle.wire.WireIn;
|
||||
@@ -43,12 +43,15 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static java.lang.Long.numberOfTrailingZeros;
|
||||
import static java.lang.Math.max;
|
||||
import static java.nio.channels.FileChannel.MapMode.READ_WRITE;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static net.openhft.chronicle.algo.MemoryUnit.*;
|
||||
import static net.openhft.chronicle.algo.bytes.Access.nativeAccess;
|
||||
import static net.openhft.chronicle.bytes.NativeBytesStore.lazyNativeBytesStoreWithFixedCapacity;
|
||||
import static net.openhft.chronicle.core.OS.pageAlign;
|
||||
import static net.openhft.chronicle.hash.impl.CompactOffHeapLinearHashTable.*;
|
||||
@@ -120,7 +123,7 @@ public abstract class VanillaChronicleHash<K,
|
||||
long maxExtraTiers;
|
||||
long tierBulkSizeInBytes;
|
||||
long tierBulkInnerOffsetToTiers;
|
||||
protected long tiersInBulk;
|
||||
public long tiersInBulk;
|
||||
protected int log2TiersInBulk;
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
@@ -325,7 +328,7 @@ public abstract class VanillaChronicleHash<K,
|
||||
return Values.newNativeReference(VanillaGlobalMutableState.class);
|
||||
}
|
||||
|
||||
protected VanillaGlobalMutableState globalMutableState() {
|
||||
public VanillaGlobalMutableState globalMutableState() {
|
||||
return globalMutableState;
|
||||
}
|
||||
|
||||
@@ -383,7 +386,8 @@ public abstract class VanillaChronicleHash<K,
|
||||
}
|
||||
}
|
||||
|
||||
public final void initBeforeMapping(FileChannel ch, long headerSize) throws IOException {
|
||||
public final void initBeforeMapping(File file, FileChannel ch, long headerSize)
|
||||
throws IOException {
|
||||
this.headerSize = roundUpMapHeaderSize(headerSize);
|
||||
if (!createdOrInMemory) {
|
||||
// This block is for reading segmentHeadersOffset before main mapping
|
||||
@@ -394,7 +398,7 @@ public abstract class VanillaChronicleHash<K,
|
||||
if (ch.read(globalMutableStateBuffer,
|
||||
this.headerSize + GLOBAL_MUTABLE_STATE_VALUE_OFFSET +
|
||||
globalMutableStateBuffer.position()) == -1) {
|
||||
throw new RuntimeException();
|
||||
throw new RuntimeException(file + " truncated");
|
||||
}
|
||||
}
|
||||
globalMutableStateBuffer.flip();
|
||||
@@ -411,15 +415,7 @@ public abstract class VanillaChronicleHash<K,
|
||||
}
|
||||
|
||||
public final void createMappedStoreAndSegments(BytesStore bytesStore) throws IOException {
|
||||
if (bytesStore.start() != 0) {
|
||||
throw new AssertionError("bytes store " + bytesStore + " starts from " +
|
||||
bytesStore.start() + ", 0 expected");
|
||||
}
|
||||
this.bs = bytesStore;
|
||||
globalMutableState.bytesStore(bs, headerSize + GLOBAL_MUTABLE_STATE_VALUE_OFFSET,
|
||||
globalMutableState.maxSize());
|
||||
|
||||
onHeaderCreated();
|
||||
initBytesStoreAndHeadersViews(bytesStore);
|
||||
|
||||
segmentHeadersOffset = segmentHeadersOffset();
|
||||
|
||||
@@ -431,13 +427,29 @@ public abstract class VanillaChronicleHash<K,
|
||||
// write the segment headers offset after zeroing out
|
||||
globalMutableState.setSegmentHeadersOffset(segmentHeadersOffset);
|
||||
} else {
|
||||
if (globalMutableState.getAllocatedExtraTierBulks() > 0) {
|
||||
appendBulkData(0, globalMutableState.getAllocatedExtraTierBulks() - 1,
|
||||
bs, sizeInBytesWithoutTiers());
|
||||
}
|
||||
initBulks();
|
||||
}
|
||||
}
|
||||
|
||||
private void initBulks() {
|
||||
if (globalMutableState.getAllocatedExtraTierBulks() > 0) {
|
||||
appendBulkData(0, globalMutableState.getAllocatedExtraTierBulks() - 1,
|
||||
bs, sizeInBytesWithoutTiers());
|
||||
}
|
||||
}
|
||||
|
||||
private void initBytesStoreAndHeadersViews(BytesStore bytesStore) {
|
||||
if (bytesStore.start() != 0) {
|
||||
throw new AssertionError("bytes store " + bytesStore + " starts from " +
|
||||
bytesStore.start() + ", 0 expected");
|
||||
}
|
||||
this.bs = bytesStore;
|
||||
globalMutableState.bytesStore(bs, headerSize + GLOBAL_MUTABLE_STATE_VALUE_OFFSET,
|
||||
globalMutableState.maxSize());
|
||||
|
||||
onHeaderCreated();
|
||||
}
|
||||
|
||||
public final void createMappedStoreAndSegments(File file, RandomAccessFile raf)
|
||||
throws IOException {
|
||||
// TODO this method had been moved -- not clear where
|
||||
@@ -447,6 +459,50 @@ public abstract class VanillaChronicleHash<K,
|
||||
createMappedStoreAndSegments(map(raf, mapSize, 0));
|
||||
}
|
||||
|
||||
public final void basicRecover(File file, RandomAccessFile raf) throws IOException {
|
||||
this.file = file;
|
||||
long segmentHeadersOffset = computeSegmentHeadersOffset();
|
||||
long sizeInBytesWithoutTiers = computeSizeInBytesWithoutTiers(segmentHeadersOffset);
|
||||
long sizeBeyondSegments = Math.max(raf.length() - sizeInBytesWithoutTiers, 0);
|
||||
int allocatedExtraTierBulks = (int) (sizeBeyondSegments / tierBulkSizeInBytes);
|
||||
long mapSize = pageAlign(sizeInBytesWithoutTiers +
|
||||
allocatedExtraTierBulks * tierBulkSizeInBytes);
|
||||
initBytesStoreAndHeadersViews(map(raf, mapSize, 0));
|
||||
|
||||
resetGlobalMutableStateLock(file);
|
||||
recoverAllocatedExtraTierBulks(file, allocatedExtraTierBulks);
|
||||
recoverSegmentHeadersOffset(file, segmentHeadersOffset);
|
||||
initBulks();
|
||||
}
|
||||
|
||||
private void resetGlobalMutableStateLock(File file) {
|
||||
long lockAddr = globalMutableStateAddress() + GLOBAL_MUTABLE_STATE_LOCK_OFFSET;
|
||||
LockingStrategy lockingStrategy = globalMutableStateLockingStrategy;
|
||||
long lockState = lockingStrategy.getState(nativeAccess(), null, lockAddr);
|
||||
if (lockState != lockingStrategy.resetState()) {
|
||||
LOG.error("global mutable state lock of map at {} is not clear: {}",
|
||||
file, lockingStrategy.toString(lockState));
|
||||
lockingStrategy.reset(nativeAccess(), null, lockAddr);
|
||||
}
|
||||
}
|
||||
|
||||
private void recoverAllocatedExtraTierBulks(File file, int allocatedExtraTierBulks) {
|
||||
if (globalMutableState.getAllocatedExtraTierBulks() != allocatedExtraTierBulks) {
|
||||
LOG.error("allocated extra tier bulks counter corrupted, or the map file {} " +
|
||||
"is truncated. stored: {}, should be: {}", file,
|
||||
globalMutableState.getAllocatedExtraTierBulks(), allocatedExtraTierBulks);
|
||||
globalMutableState.setAllocatedExtraTierBulks(allocatedExtraTierBulks);
|
||||
}
|
||||
}
|
||||
|
||||
private void recoverSegmentHeadersOffset(File file, long segmentHeadersOffset) {
|
||||
if (globalMutableState.getSegmentHeadersOffset() != segmentHeadersOffset) {
|
||||
LOG.error("segment headers offset of map at {} corrupted. stored: {}, should be: {}",
|
||||
file, globalMutableState.getSegmentHeadersOffset(), segmentHeadersOffset);
|
||||
globalMutableState.setSegmentHeadersOffset(segmentHeadersOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean persisted() {
|
||||
return file != null;
|
||||
}
|
||||
@@ -480,7 +536,7 @@ public abstract class VanillaChronicleHash<K,
|
||||
}
|
||||
|
||||
private void zeroOutNewlyMappedTier(BytesStore bytesStore, long tierOffset) {
|
||||
// Zero out hash lookup, tier data and free list bit set. Leave entry space.
|
||||
// Zero out hash lookup, tier data and free list bit set. Leave entry space dirty.
|
||||
bytesStore.zeroOut(tierOffset, tierOffset + tierSize - tierEntrySpaceOuterSize);
|
||||
}
|
||||
|
||||
@@ -501,15 +557,19 @@ public abstract class VanillaChronicleHash<K,
|
||||
|
||||
private long segmentHeadersOffset() {
|
||||
if (createdOrInMemory) {
|
||||
// Align segment headers on page boundary to minimize number of pages that
|
||||
// segment headers span
|
||||
long reserved = RESERVED_GLOBAL_MUTABLE_STATE_BYTES - globalMutableStateTotalUsedSize();
|
||||
return pageAlign(mapHeaderInnerSize() + reserved);
|
||||
return computeSegmentHeadersOffset();
|
||||
} else {
|
||||
return globalMutableState.getSegmentHeadersOffset();
|
||||
}
|
||||
}
|
||||
|
||||
private long computeSegmentHeadersOffset() {
|
||||
long reserved = RESERVED_GLOBAL_MUTABLE_STATE_BYTES - globalMutableStateTotalUsedSize();
|
||||
// Align segment headers on page boundary to minimize number of pages that
|
||||
// segment headers span
|
||||
return pageAlign(mapHeaderInnerSize() + reserved);
|
||||
}
|
||||
|
||||
public long mapHeaderInnerSize() {
|
||||
return headerSize + globalMutableStateTotalUsedSize();
|
||||
}
|
||||
@@ -520,7 +580,11 @@ public abstract class VanillaChronicleHash<K,
|
||||
}
|
||||
|
||||
public final long sizeInBytesWithoutTiers() {
|
||||
return segmentHeadersOffset() + actualSegments * (segmentHeaderSize + tierSize);
|
||||
return computeSizeInBytesWithoutTiers(segmentHeadersOffset());
|
||||
}
|
||||
|
||||
private long computeSizeInBytesWithoutTiers(long segmentHeadersOffset) {
|
||||
return segmentHeadersOffset + actualSegments * (segmentHeaderSize + tierSize);
|
||||
}
|
||||
|
||||
public final long expectedFileSize() {
|
||||
@@ -632,12 +696,12 @@ public abstract class VanillaChronicleHash<K,
|
||||
public void globalMutableStateLock() {
|
||||
globalMutableStateLockAcquisitionStrategy.acquire(
|
||||
globalMutableStateLockTryAcquireOperation, globalMutableStateLockingStrategy,
|
||||
Access.nativeAccess(), null,
|
||||
nativeAccess(), null,
|
||||
globalMutableStateAddress() + GLOBAL_MUTABLE_STATE_LOCK_OFFSET);
|
||||
}
|
||||
|
||||
public void globalMutableStateUnlock() {
|
||||
globalMutableStateLockingStrategy.unlock(Access.nativeAccess(), null,
|
||||
globalMutableStateLockingStrategy.unlock(nativeAccess(), null,
|
||||
globalMutableStateAddress() + GLOBAL_MUTABLE_STATE_LOCK_OFFSET);
|
||||
}
|
||||
|
||||
@@ -687,7 +751,7 @@ public abstract class VanillaChronicleHash<K,
|
||||
mapTierBulks(allocatedExtraTierBulks);
|
||||
|
||||
// integer overflow aware
|
||||
long firstTierIndex = actualSegments + 1L + ((long) allocatedExtraTierBulks) * tiersInBulk;
|
||||
long firstTierIndex = extraTierIndexToTierIndex(allocatedExtraTierBulks * tiersInBulk);
|
||||
BytesStore tierBytesStore = tierBytesStore(firstTierIndex);
|
||||
long firstTierOffset = tierBytesOffset(firstTierIndex);
|
||||
if (tierBulkInnerOffsetToTiers > 0) {
|
||||
@@ -695,10 +759,20 @@ public abstract class VanillaChronicleHash<K,
|
||||
tierBytesStore.zeroOut(firstTierOffset - tierBulkInnerOffsetToTiers, firstTierOffset);
|
||||
}
|
||||
|
||||
// Link newly allocated tiers into free tiers list
|
||||
long lastTierIndex = firstTierIndex + tiersInBulk - 1;
|
||||
linkAndZeroOutFreeTiers(firstTierIndex, lastTierIndex);
|
||||
|
||||
// TODO HCOLL-397 insert msync here!
|
||||
|
||||
// after we are sure the new bulk is initialized, update the global mutable state
|
||||
globalMutableState.setAllocatedExtraTierBulks(allocatedExtraTierBulks + 1);
|
||||
globalMutableState.setFirstFreeTierIndex(firstTierIndex);
|
||||
}
|
||||
|
||||
public void linkAndZeroOutFreeTiers(long firstTierIndex, long lastTierIndex) {
|
||||
for (long tierIndex = firstTierIndex; tierIndex <= lastTierIndex; tierIndex++) {
|
||||
long tierOffset = tierBytesOffset(tierIndex);
|
||||
BytesStore tierBytesStore = tierBytesStore(tierIndex);
|
||||
zeroOutNewlyMappedTier(tierBytesStore, tierOffset);
|
||||
if (tierIndex < lastTierIndex) {
|
||||
long tierCountersAreaOffset = tierOffset + tierHashLookupOuterSize;
|
||||
@@ -706,12 +780,10 @@ public abstract class VanillaChronicleHash<K,
|
||||
tierIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO HCOLL-397 insert msync here!
|
||||
|
||||
// after we are sure the new bulk is initialized, update the global mutable state
|
||||
globalMutableState.setAllocatedExtraTierBulks(allocatedExtraTierBulks + 1);
|
||||
globalMutableState.setFirstFreeTierIndex(firstTierIndex);
|
||||
public long extraTierIndexToTierIndex(long extraTierIndex) {
|
||||
return actualSegments + extraTierIndex + 1;
|
||||
}
|
||||
|
||||
public long tierIndexToBaseAddr(long tierIndex) {
|
||||
|
||||
@@ -24,5 +24,9 @@ public interface ChecksumStrategy {
|
||||
|
||||
boolean innerCheckSum();
|
||||
|
||||
int computeChecksum();
|
||||
|
||||
int storedChecksum();
|
||||
|
||||
long extraEntryBytes();
|
||||
}
|
||||
|
||||
+8
-2
@@ -35,7 +35,8 @@ public class HashEntryChecksumStrategy implements ChecksumStrategy {
|
||||
s.segmentBS.writeInt(e.entryEnd(), checksum);
|
||||
}
|
||||
|
||||
private int computeChecksum() {
|
||||
@Override
|
||||
public int computeChecksum() {
|
||||
long keyHashCode = h.keyHashCode();
|
||||
|
||||
long keyEnd = e.keyEnd();
|
||||
@@ -55,11 +56,16 @@ public class HashEntryChecksumStrategy implements ChecksumStrategy {
|
||||
|
||||
@Override
|
||||
public boolean innerCheckSum() {
|
||||
int oldChecksum = s.segmentBS.readInt(e.entryEnd());
|
||||
int oldChecksum = storedChecksum();
|
||||
int checksum = computeChecksum();
|
||||
return oldChecksum == checksum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int storedChecksum() {
|
||||
return s.segmentBS.readInt(e.entryEnd());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long extraEntryBytes() {
|
||||
return CHECKSUM_STORED_BYTES;
|
||||
|
||||
@@ -108,7 +108,7 @@ public abstract class HashEntryStages<K> implements HashEntry<K>, ChecksumEntry
|
||||
return keyOffset + keySize;
|
||||
}
|
||||
|
||||
protected long entryEnd() {
|
||||
public long entryEnd() {
|
||||
return keyEnd();
|
||||
}
|
||||
|
||||
|
||||
@@ -67,9 +67,7 @@ public abstract class HashLookupSearch {
|
||||
return hl().value(entry);
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("MultiMap is full, that most likely means you " +
|
||||
"misconfigured entrySize/chunkSize, and entries tend to take less chunks than " +
|
||||
"expected");
|
||||
throw new IllegalStateException("HashLookup overflow should never occur");
|
||||
}
|
||||
|
||||
public void found() {
|
||||
|
||||
@@ -29,6 +29,16 @@ public enum NoChecksumStrategy implements ChecksumStrategy {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int computeChecksum() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int storedChecksum() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long extraEntryBytes() {
|
||||
return 0; // no extra bytes to store checksum
|
||||
|
||||
@@ -58,8 +58,8 @@ public abstract class SegmentStages implements SegmentLock, LocksInterface {
|
||||
|
||||
public abstract boolean segmentIndexInit();
|
||||
|
||||
@Stage("SegmentHeader") long segmentHeaderAddress;
|
||||
@Stage("SegmentHeader") SegmentHeader segmentHeader = null;
|
||||
@Stage("SegmentHeader") public long segmentHeaderAddress;
|
||||
@Stage("SegmentHeader") public SegmentHeader segmentHeader = null;
|
||||
|
||||
private void initSegmentHeader() {
|
||||
segmentHeaderAddress = hh.h().segmentHeaderAddress(segmentIndex);
|
||||
@@ -82,7 +82,7 @@ public abstract class SegmentStages implements SegmentLock, LocksInterface {
|
||||
}
|
||||
}
|
||||
|
||||
long lowestPossiblyFreeChunk() {
|
||||
public long lowestPossiblyFreeChunk() {
|
||||
if (tier == 0) {
|
||||
return segmentHeader.lowestPossiblyFreeChunk(segmentHeaderAddress);
|
||||
} else {
|
||||
@@ -513,7 +513,7 @@ public abstract class SegmentStages implements SegmentLock, LocksInterface {
|
||||
tier = 0;
|
||||
}
|
||||
|
||||
private void initSegmentTier(int tier, long tierIndex) {
|
||||
public void initSegmentTier(int tier, long tierIndex) {
|
||||
this.tier = tier;
|
||||
this.tierIndex = tierIndex;
|
||||
assert tierIndex > 0;
|
||||
@@ -584,7 +584,7 @@ public abstract class SegmentStages implements SegmentLock, LocksInterface {
|
||||
|
||||
@Stage("Segment") public final PointerBytesStore segmentBS = new PointerBytesStore();
|
||||
@Stage("Segment") public final Bytes segmentBytes = new VanillaBytes(segmentBS);
|
||||
@Stage("Segment") private final ReusableBitSet freeList = new ReusableBitSet(
|
||||
@Stage("Segment") public final ReusableBitSet freeList = new ReusableBitSet(
|
||||
new SingleThreadedFlatBitSetFrame(LONGS.align(hh.h().actualChunksPerSegmentTier, BITS)),
|
||||
Access.nativeAccess(), null, 0);
|
||||
@Stage("Segment") long entrySpaceOffset = 0;
|
||||
|
||||
@@ -98,7 +98,7 @@ public abstract class HashSegmentIteration<K, E extends HashEntry<K>>
|
||||
}
|
||||
}
|
||||
|
||||
private <T> boolean forEachTierEntryWhile(
|
||||
public <T> boolean forEachTierEntryWhile(
|
||||
Predicate<? super T> predicate,
|
||||
int currentTier, long currentTierBaseAddr, long tierIndex) {
|
||||
long leftEntries = tierEntriesForIteration();
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.openhft.chronicle.hash.impl.stage.iter;
|
||||
|
||||
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.hash.impl.stage.hash.LogHolder;
|
||||
import net.openhft.chronicle.map.impl.IterationContext;
|
||||
import net.openhft.sg.StageRef;
|
||||
import net.openhft.sg.Staged;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@Staged
|
||||
public abstract class SegmentsRecovery implements IterationContext {
|
||||
|
||||
@StageRef VanillaChronicleHashHolder<?> hh;
|
||||
@StageRef SegmentStages s;
|
||||
@StageRef TierRecovery tierRecovery;
|
||||
@StageRef LogHolder lh;
|
||||
@StageRef HashSegmentIteration iteration;
|
||||
|
||||
@Override
|
||||
public void recoverSegments() {
|
||||
Logger log = lh.LOG;
|
||||
VanillaChronicleHash<?, ?, ?, ?> h = hh.h();
|
||||
for (int segmentIndex = 0; segmentIndex < h.actualSegments; segmentIndex++) {
|
||||
s.initSegmentIndex(segmentIndex);
|
||||
resetSegmentLock();
|
||||
zeroOutFirstSegmentTierCountersArea();
|
||||
tierRecovery.recoverTier(segmentIndex);
|
||||
}
|
||||
|
||||
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);
|
||||
if (segmentIndex > 0) {
|
||||
long tierCountersAreaAddr = s.tierCountersAreaAddr();
|
||||
int storedSegmentIndex = TierCountersArea.segmentIndex(tierCountersAreaAddr);
|
||||
if (storedSegmentIndex != segmentIndex) {
|
||||
log.error("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) {
|
||||
log.error("wrong number of actual tiers in use in global mutable state, stored: {}, " +
|
||||
"should be: {}", storedExtraTiersInUse, actualExtraTiersInUse);
|
||||
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) {
|
||||
log.error("wrong first free tier index in global mutable state, stored: {}, " +
|
||||
"should be: {}", storedFirstFreeTierIndex, firstFreeTierIndex);
|
||||
}
|
||||
|
||||
removeDuplicatesInSegments();
|
||||
}
|
||||
|
||||
private void removeDuplicatesInSegments() {
|
||||
VanillaChronicleHash<?, ?, ?, ?> h = hh.h();
|
||||
for (int segmentIndex = 0; segmentIndex < h.actualSegments; segmentIndex++) {
|
||||
s.initSegmentIndex(segmentIndex);
|
||||
s.initSegmentTier();
|
||||
s.goToLastTier();
|
||||
while (true) {
|
||||
tierRecovery.removeDuplicatesInSegment();
|
||||
if (s.tier > 0) {
|
||||
s.prevTier();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resetSegmentLock() {
|
||||
long lockState = s.segmentHeader.getLockState(s.segmentHeaderAddress);
|
||||
if (lockState != s.segmentHeader.resetLockState()) {
|
||||
lh.LOG.error("lock of segment {} is not clear: {}",
|
||||
s.segmentIndex, s.segmentHeader.lockStateToString(lockState));
|
||||
s.segmentHeader.resetLock(s.segmentHeaderAddress);
|
||||
}
|
||||
}
|
||||
|
||||
private void zeroOutFirstSegmentTierCountersArea() {
|
||||
s.nextTierIndex(0);
|
||||
if (s.prevTierIndex() != 0) {
|
||||
lh.LOG.error("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) {
|
||||
lh.LOG.error("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) {
|
||||
lh.LOG.error("stored tier in first tier of segment {}: {}, should be 0",
|
||||
s.segmentIndex, TierCountersArea.tier(tierCountersAreaAddr));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.openhft.chronicle.hash.impl.stage.iter;
|
||||
|
||||
import net.openhft.chronicle.bytes.BytesUtil;
|
||||
import net.openhft.chronicle.hash.ChronicleHashRecoveryFailedException;
|
||||
import net.openhft.chronicle.hash.Data;
|
||||
import net.openhft.chronicle.hash.ExternalHashQueryContext;
|
||||
import net.openhft.chronicle.hash.HashEntry;
|
||||
import net.openhft.chronicle.hash.impl.CompactOffHeapLinearHashTable;
|
||||
import net.openhft.chronicle.hash.impl.VanillaChronicleHash;
|
||||
import net.openhft.chronicle.hash.impl.stage.entry.SegmentStages;
|
||||
import net.openhft.chronicle.hash.impl.stage.hash.LogHolder;
|
||||
import net.openhft.chronicle.map.ExternalMapQueryContext;
|
||||
import net.openhft.chronicle.map.MapEntry;
|
||||
import net.openhft.chronicle.map.VanillaChronicleMap;
|
||||
import net.openhft.chronicle.map.impl.VanillaChronicleMapHolder;
|
||||
import net.openhft.chronicle.map.impl.stage.entry.MapEntryStages;
|
||||
import net.openhft.sg.StageRef;
|
||||
import net.openhft.sg.Staged;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@Staged
|
||||
public class TierRecovery {
|
||||
|
||||
@StageRef LogHolder lh;
|
||||
@StageRef VanillaChronicleMapHolder<?, ?, ?> mh;
|
||||
@StageRef SegmentStages s;
|
||||
@StageRef MapEntryStages<?, ?> e;
|
||||
@StageRef IterationKeyHashCode khc;
|
||||
|
||||
public int recoverTier(int segmentIndex) {
|
||||
s.freeList.clearAll();
|
||||
|
||||
Logger log = lh.LOG;
|
||||
VanillaChronicleHash<?, ?, ?, ?> h = mh.h();
|
||||
CompactOffHeapLinearHashTable hl = h.hashLookup;
|
||||
long hlAddr = s.tierBaseAddr;
|
||||
|
||||
long hlPos = 0;
|
||||
do {
|
||||
long hlEntry = hl.readEntry(hlAddr, hlPos);
|
||||
nextHlPos:
|
||||
if (!hl.empty(hlEntry)) {
|
||||
hl.clearEntry(hlAddr, hlPos);
|
||||
|
||||
long searchKey = hl.key(hlEntry);
|
||||
long entryPos = hl.value(hlEntry);
|
||||
int si = checkEntry(searchKey, entryPos, segmentIndex);
|
||||
if (si < 0) {
|
||||
break nextHlPos;
|
||||
} else {
|
||||
segmentIndex = si;
|
||||
}
|
||||
|
||||
// The entry has passed all checks, re-insert:
|
||||
long startInsertPos = hl.hlPos(searchKey);
|
||||
long insertPos = startInsertPos;
|
||||
do {
|
||||
long hlInsertEntry = hl.readEntry(hlAddr, insertPos);
|
||||
if (hl.empty(hlInsertEntry)) {
|
||||
hl.writeEntry(hlAddr, insertPos, hl.entry(searchKey, entryPos));
|
||||
break nextHlPos;
|
||||
}
|
||||
if (insertPos == hlPos) {
|
||||
throw new ChronicleHashRecoveryFailedException(
|
||||
"Concurrent modification of ChronicleMap at " + h.file() +
|
||||
" while recovery procedure is in progress");
|
||||
}
|
||||
if (hl.key(hlInsertEntry) == searchKey) {
|
||||
long anotherEntryPos = hl.value(hlInsertEntry);
|
||||
if (anotherEntryPos == entryPos)
|
||||
break nextHlPos;
|
||||
long currentKeyOffset = e.keyOffset;
|
||||
long currentKeySize = e.keySize;
|
||||
int currentEntrySizeInChunks = e.entrySizeInChunks;
|
||||
if (checkEntry(searchKey, anotherEntryPos, segmentIndex) > 0) {
|
||||
if (e.keySize == currentKeySize &&
|
||||
BytesUtil.bytesEqual(s.segmentBS, currentKeyOffset,
|
||||
s.segmentBS, e.keyOffset, currentKeySize)) {
|
||||
log.error("Entries with duplicate keys within a tier: " +
|
||||
"at pos {} and {} with key {}, first value is {}",
|
||||
entryPos, anotherEntryPos, e.key(), e.value());
|
||||
s.freeList.clearRange(
|
||||
entryPos, entryPos + currentEntrySizeInChunks);
|
||||
break nextHlPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
insertPos = hl.step(insertPos);
|
||||
} while (insertPos != startInsertPos);
|
||||
throw new ChronicleHashRecoveryFailedException(
|
||||
"HashLookup overflow should never occur. " +
|
||||
"It might also be concurrent access to ChronicleMap at " +
|
||||
h.file() + " while recovery procedure is in progress");
|
||||
}
|
||||
hlPos = hl.step(hlPos);
|
||||
} while (hlPos != 0);
|
||||
return segmentIndex;
|
||||
}
|
||||
|
||||
public void removeDuplicatesInSegment() {
|
||||
long startHlPos = 0L;
|
||||
VanillaChronicleMap<?, ?, ?> m = mh.m();
|
||||
CompactOffHeapLinearHashTable hashLookup = m.hashLookup;
|
||||
long currentTierBaseAddr = s.tierBaseAddr;
|
||||
while (!hashLookup.empty(hashLookup.readEntry(currentTierBaseAddr, startHlPos))) {
|
||||
startHlPos = hashLookup.step(startHlPos);
|
||||
}
|
||||
long hlPos = startHlPos;
|
||||
int steps = 0;
|
||||
long entries = 0;
|
||||
tierIteration:
|
||||
do {
|
||||
hlPos = hashLookup.step(hlPos);
|
||||
steps++;
|
||||
long entry = hashLookup.readEntry(currentTierBaseAddr, hlPos);
|
||||
if (!hashLookup.empty(entry)) {
|
||||
e.readExistingEntry(hashLookup.value(entry));
|
||||
Data key = (Data) e.key();
|
||||
try (ExternalMapQueryContext<?, ?, ?> c = m.queryContext(key)) {
|
||||
MapEntry<?, ?> entry2 = c.entry();
|
||||
Data<?> key2 = entry2.key();
|
||||
if (key2.bytes().address(key2.offset()) != key.bytes().address(key.offset())) {
|
||||
lh.LOG.error("entries with duplicate key {} in segment {}: " +
|
||||
"with values {} and {}, removing the latter",
|
||||
key, c.segmentIndex(), entry2.value(), e.value());
|
||||
if (hashLookup.remove(currentTierBaseAddr, hlPos) != hlPos) {
|
||||
hlPos = hashLookup.stepBack(hlPos);
|
||||
steps--;
|
||||
}
|
||||
continue tierIteration;
|
||||
}
|
||||
}
|
||||
entries++;
|
||||
}
|
||||
// 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 hlPos
|
||||
// becomes equal to start pos without making the whole loop, but only visiting a single
|
||||
// entry
|
||||
} while (hlPos != startHlPos || steps == 0);
|
||||
|
||||
recoverTierEntriesCounter(entries);
|
||||
recoverLowestPossibleFreeChunkTiered();
|
||||
}
|
||||
|
||||
private void recoverTierEntriesCounter(long entries) {
|
||||
if (s.tierEntries() != entries) {
|
||||
lh.LOG.error("Wrong number of entries counter for tier with index {}, " +
|
||||
"stored: {}, should be: {}", s.tierIndex, s.tierEntries(), entries);
|
||||
s.tierEntries(entries);
|
||||
}
|
||||
}
|
||||
|
||||
private void recoverLowestPossibleFreeChunkTiered() {
|
||||
long lowestFreeChunk = s.freeList.nextClearBit(0);
|
||||
if (lowestFreeChunk == -1)
|
||||
lowestFreeChunk = mh.m().actualChunksPerSegmentTier;
|
||||
if (s.lowestPossiblyFreeChunk() != lowestFreeChunk) {
|
||||
lh.LOG.error("wrong lowest free chunk for tier with index {}, " +
|
||||
"stored: {}, should be: {}",
|
||||
s.tierIndex, s.lowestPossiblyFreeChunk(), lowestFreeChunk);
|
||||
s.lowestPossiblyFreeChunk(lowestFreeChunk);
|
||||
}
|
||||
}
|
||||
|
||||
private int checkEntry(long searchKey, long entryPos, int segmentIndex) {
|
||||
Logger log = lh.LOG;
|
||||
VanillaChronicleHash<?, ?, ?, ?> h = mh.h();
|
||||
if (entryPos < 0 || entryPos >= h.actualChunksPerSegmentTier) {
|
||||
log.error("Entry pos is out of range: {}, should be 0-{}",
|
||||
entryPos, h.actualChunksPerSegmentTier - 1);
|
||||
}
|
||||
try {
|
||||
e.readExistingEntry(entryPos);
|
||||
} catch (Exception e) {
|
||||
log.error("Exception while reading entry key size: {}", e);
|
||||
return -1;
|
||||
}
|
||||
if (e.keyEnd() > s.segmentBytes.capacity()) {
|
||||
log.error("Wrong key size: {}", e.keySize);
|
||||
return -1;
|
||||
}
|
||||
|
||||
long keyHashCode = khc.keyHashCode();
|
||||
int segmentIndexFromKey = h.hashSplitting.segmentIndex(keyHashCode);
|
||||
if (segmentIndexFromKey < 0 || segmentIndexFromKey >= h.actualSegments) {
|
||||
log.error("Segment index from the entry key hash code is out of range: {}, " +
|
||||
"should be 0-{}, entry key: {}",
|
||||
segmentIndexFromKey, h.actualSegments - 1, e.key());
|
||||
return -1;
|
||||
}
|
||||
|
||||
long segmentHashFromKey = h.hashSplitting.segmentHash(keyHashCode);
|
||||
long searchKeyFromKey = h.hashLookup.maskUnsetKey(segmentHashFromKey);
|
||||
if (searchKey != searchKeyFromKey) {
|
||||
log.error("HashLookup searchKey: {}, HashLookup searchKey " +
|
||||
"from the entry key hash code: {}, entry key: {}",
|
||||
searchKey, searchKeyFromKey, e.key());
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
long entryAndChecksumEnd = e.entryEnd() + e.checksumStrategy.extraEntryBytes();
|
||||
if (entryAndChecksumEnd > s.segmentBytes.capacity()) {
|
||||
log.error("Wrong value size: {}, key: {}", e.valueSize, e.key());
|
||||
return -1;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.error("Exception while reading entry value size: {}, key: {}", ex, e.key());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int storedChecksum = e.checksumStrategy.storedChecksum();
|
||||
int checksumFromEntry = e.checksumStrategy.computeChecksum();
|
||||
if (storedChecksum != checksumFromEntry) {
|
||||
log.error("Checksum doesn't match, stored: {}, should be from " +
|
||||
"the entry bytes: {}, key: {}, value: {}",
|
||||
storedChecksum, checksumFromEntry, e.key(), e.value());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!s.freeList.isRangeClear(entryPos, entryPos + e.entrySizeInChunks)) {
|
||||
log.error("Overlapping entry: positions {}-{}, key: {}, value: {}",
|
||||
entryPos, entryPos + e.entrySizeInChunks - 1, e.key(), e.value());
|
||||
}
|
||||
s.freeList.setRange(entryPos, entryPos + e.entrySizeInChunks);
|
||||
|
||||
if (segmentIndex < 0) {
|
||||
return segmentIndexFromKey;
|
||||
} else {
|
||||
if (segmentIndex != segmentIndexFromKey) {
|
||||
log.error("Expected segment index: {}, segment index from the entry key: {}, " +
|
||||
"key: {}, value: {}", segmentIndex, searchKeyFromKey, e.key(),
|
||||
e.value());
|
||||
return -1;
|
||||
} else {
|
||||
return segmentIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import net.openhft.chronicle.core.OS;
|
||||
import net.openhft.chronicle.hash.ChronicleHashBuilder;
|
||||
import net.openhft.chronicle.hash.ChronicleHashBuilderPrivateAPI;
|
||||
import net.openhft.chronicle.hash.ChronicleHashInstanceBuilder;
|
||||
import net.openhft.chronicle.hash.ChronicleHashRecoveryFailedException;
|
||||
import net.openhft.chronicle.hash.impl.CompactOffHeapLinearHashTable;
|
||||
import net.openhft.chronicle.hash.impl.stage.entry.ChecksumStrategy;
|
||||
import net.openhft.chronicle.hash.impl.util.math.PoissonDistribution;
|
||||
@@ -47,7 +48,6 @@ import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static java.lang.Double.isNaN;
|
||||
import static java.lang.Math.round;
|
||||
@@ -267,6 +267,7 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
* @deprecated don't use private API in the client code
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public ChronicleHashBuilderPrivateAPI<K> privateAPI() {
|
||||
return privateAPI;
|
||||
}
|
||||
@@ -1358,8 +1359,7 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
|
||||
@Override
|
||||
public ChronicleHashInstanceBuilder<ChronicleMap<K, V>> instance() {
|
||||
return new MapInstanceBuilder<>(this.clone(), singleHashReplication, null, null, null,
|
||||
new AtomicBoolean(false));
|
||||
return new MapInstanceBuilder<>(this.clone(), singleHashReplication);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1367,7 +1367,13 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
// clone() to make this builder instance thread-safe, because createWithFile() method
|
||||
// computes some state based on configurations, but doesn't synchronize on configuration
|
||||
// changes.
|
||||
return clone().createWithFile(file, singleHashReplication, null);
|
||||
return clone().createWithFile(file, singleHashReplication, null, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChronicleMap<K, V> recoverPersistedTo(File file, boolean sameBuilderConfig)
|
||||
throws IOException {
|
||||
return clone().createWithFile(file, singleHashReplication, null, true, sameBuilderConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1378,9 +1384,12 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
return clone().createWithoutFile(singleHashReplication, null);
|
||||
}
|
||||
|
||||
ChronicleMap<K, V> create(MapInstanceBuilder<K, V> ib) throws IOException {
|
||||
ChronicleMap<K, V> create(
|
||||
MapInstanceBuilder<K, V> ib, boolean recover, boolean sameBuilderConfig)
|
||||
throws IOException {
|
||||
if (ib.file != null) {
|
||||
return createWithFile(ib.file, ib.singleHashReplication, ib.channel);
|
||||
return createWithFile(
|
||||
ib.file, ib.singleHashReplication, ib.channel, recover, sameBuilderConfig);
|
||||
} else {
|
||||
return createWithoutFile(ib.singleHashReplication, ib.channel);
|
||||
}
|
||||
@@ -1388,7 +1397,10 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
|
||||
ChronicleMap<K, V> createWithFile(
|
||||
File file, SingleChronicleHashReplication singleHashReplication,
|
||||
ReplicationChannel channel) throws IOException {
|
||||
ReplicationChannel channel,
|
||||
boolean recover, boolean overrideBuilderConfig) throws IOException {
|
||||
if (overrideBuilderConfig && !recover)
|
||||
throw new AssertionError("recover -> overrideBuilderConfig");
|
||||
replicated = singleHashReplication != null || channel != null;
|
||||
persisted = true;
|
||||
|
||||
@@ -1397,6 +1409,12 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
if (fileLength > 0) {
|
||||
try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
|
||||
FileChannel fileChannel = raf.getChannel();
|
||||
int headerSize = 0;
|
||||
if (overrideBuilderConfig) {
|
||||
VanillaChronicleMap<K, V, ?> mapObjectForHeaderOverwrite =
|
||||
newMap(singleHashReplication, channel);
|
||||
headerSize = writeHeader(fileChannel, mapObjectForHeaderOverwrite);
|
||||
}
|
||||
ByteBuffer headerBuffer = ByteBuffer.allocate(MAX_BOOTSTRAPPING_HEADER_SIZE);
|
||||
while (headerBuffer.remaining() > 0 &&
|
||||
headerBuffer.position() < fileChannel.size()) {
|
||||
@@ -1408,24 +1426,42 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
headerBytes.readLimit(headerBuffer.limit());
|
||||
Wire wire = new BinaryWire(headerBytes);
|
||||
VanillaChronicleMap<K, V, ?> map = wire.getValueIn().typedMarshallable();
|
||||
map.initTransientsFromBuilder(this);
|
||||
initTransientsFromReplication(map, singleHashReplication, channel);
|
||||
|
||||
map.initBeforeMapping(fileChannel, headerBytes.readPosition());
|
||||
long readHeaderSize = headerBytes.readPosition();
|
||||
if (overrideBuilderConfig && headerSize != readHeaderSize) {
|
||||
throw new ChronicleHashRecoveryFailedException("read header size " +
|
||||
readHeaderSize + " != overwritten header size " + headerSize);
|
||||
}
|
||||
map.initBeforeMapping(file, fileChannel, readHeaderSize);
|
||||
long expectedFileLength = map.expectedFileSize();
|
||||
if (expectedFileLength != fileLength) {
|
||||
if (!recover && expectedFileLength != fileLength) {
|
||||
throw new IOException("The file " + file + " the map is serialized from " +
|
||||
"has unexpected length " + fileLength + ", probably corrupted. " +
|
||||
"Expected length is " + expectedFileLength);
|
||||
}
|
||||
map.createMappedStoreAndSegments(file, raf);
|
||||
|
||||
map.initTransientsFromBuilder(this);
|
||||
initTransientsFromReplication(map, singleHashReplication, channel);
|
||||
|
||||
if (!recover) {
|
||||
map.createMappedStoreAndSegments(file, raf);
|
||||
} else {
|
||||
map.recover(file, raf);
|
||||
}
|
||||
|
||||
establishReplication(map, singleHashReplication, channel);
|
||||
|
||||
fileChannel.force(true);
|
||||
// TODO according to Self Boostrapping Data spec, should write "init complete"
|
||||
// byte *after* the above force instruction, see HCOLL-396
|
||||
return map;
|
||||
} catch (Exception e) {
|
||||
if (recover && !(e instanceof IOException))
|
||||
throw new ChronicleHashRecoveryFailedException(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
if (recover)
|
||||
throw new FileNotFoundException("file " + file + " should exist for recovery");
|
||||
if (file.createNewFile() || fileLength == 0) {
|
||||
break;
|
||||
}
|
||||
@@ -1443,21 +1479,28 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
|
||||
try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
|
||||
FileChannel fileChannel = raf.getChannel();
|
||||
ByteBuffer headerBuffer = ByteBuffer.allocate(MAX_BOOTSTRAPPING_HEADER_SIZE);
|
||||
Bytes<ByteBuffer> headerBytes = Bytes.wrapForWrite(headerBuffer);
|
||||
Wire wire = new BinaryWire(headerBytes);
|
||||
wire.getValueOut().typedMarshallable(map);
|
||||
int headerSize = writeHeader(fileChannel, map);
|
||||
map.initBeforeMapping(file, fileChannel, headerSize);
|
||||
|
||||
int headerSize = (int) headerBytes.writePosition();
|
||||
headerBuffer.limit(headerSize);
|
||||
while (headerBuffer.remaining() > 0) {
|
||||
fileChannel.write(headerBuffer, headerBuffer.position());
|
||||
}
|
||||
map.initBeforeMapping(fileChannel, headerSize);
|
||||
map.createMappedStoreAndSegments(file, raf);
|
||||
establishReplication(map, singleHashReplication, channel);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
return establishReplication(map, singleHashReplication, channel);
|
||||
private static <K, V> int writeHeader(FileChannel fileChannel, VanillaChronicleMap<K, V, ?> map)
|
||||
throws IOException {
|
||||
ByteBuffer headerBuffer = ByteBuffer.allocate(MAX_BOOTSTRAPPING_HEADER_SIZE);
|
||||
Bytes<ByteBuffer> headerBytes = Bytes.wrapForWrite(headerBuffer);
|
||||
Wire wire = new BinaryWire(headerBytes);
|
||||
wire.getValueOut().typedMarshallable(map);
|
||||
|
||||
int headerSize = (int) headerBytes.writePosition();
|
||||
headerBuffer.limit(headerSize);
|
||||
while (headerBuffer.remaining() > 0) {
|
||||
fileChannel.write(headerBuffer, headerBuffer.position());
|
||||
}
|
||||
return headerSize;
|
||||
}
|
||||
|
||||
private static void initTransientsFromReplication(
|
||||
@@ -1492,7 +1535,8 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
BytesStore bytesStore =
|
||||
lazyNativeBytesStoreWithFixedCapacity(map.sizeInBytesWithoutTiers());
|
||||
map.createMappedStoreAndSegments(bytesStore);
|
||||
return establishReplication(map, singleHashReplication, channel);
|
||||
establishReplication(map, singleHashReplication, channel);
|
||||
return map;
|
||||
} catch (IOException e) {
|
||||
// file-less version should never trigger an IOException.
|
||||
throw new AssertionError(e);
|
||||
@@ -1552,7 +1596,7 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
&& actualChunkSize > 0;
|
||||
}
|
||||
|
||||
private ChronicleMap<K, V> establishReplication(
|
||||
private void establishReplication(
|
||||
VanillaChronicleMap<K, V, ?> map,
|
||||
SingleChronicleHashReplication singleHashReplication,
|
||||
ReplicationChannel channel) throws IOException {
|
||||
@@ -1591,7 +1635,6 @@ public final class ChronicleMapBuilder<K, V> implements
|
||||
result.addCloseable(token);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private void establishCleanupThread(ReplicatedChronicleMap map) {
|
||||
|
||||
@@ -24,7 +24,6 @@ import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
final class MapInstanceBuilder<K, V>
|
||||
implements ChronicleHashInstanceBuilder<ChronicleMap<K, V>>, Serializable {
|
||||
@@ -35,18 +34,12 @@ final class MapInstanceBuilder<K, V>
|
||||
transient File file;
|
||||
String name;
|
||||
|
||||
final AtomicBoolean used;
|
||||
volatile boolean used = false;
|
||||
|
||||
MapInstanceBuilder(ChronicleMapBuilder<K, V> mapBuilder,
|
||||
SingleChronicleHashReplication singleHashReplication,
|
||||
ReplicationChannel channel,
|
||||
File file, String name, AtomicBoolean used) {
|
||||
SingleChronicleHashReplication singleHashReplication) {
|
||||
this.mapBuilder = mapBuilder;
|
||||
this.singleHashReplication = singleHashReplication;
|
||||
this.channel = channel;
|
||||
this.file = file;
|
||||
this.name = name;
|
||||
this.used = used;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,14 +77,35 @@ final class MapInstanceBuilder<K, V>
|
||||
|
||||
@Override
|
||||
public synchronized ChronicleMap<K, V> create() throws IOException {
|
||||
if (!used.getAndSet(true)) {
|
||||
return mapBuilder.create(this);
|
||||
if (!used) {
|
||||
ChronicleMap<K, V> map = mapBuilder.create(this, false, false);
|
||||
// Set used = true, if map created successfully.
|
||||
// If exception is thrown in mapBuilder.create(), the next line is not executed.
|
||||
used = true;
|
||||
return map;
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"A ChronicleMap has already been created using this instance config chain. " +
|
||||
"Create a new instance config (builder.instance()) to create a new " +
|
||||
"ChronicleMap instance");
|
||||
throw alreadyCreated();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ChronicleMap<K, V> recover(boolean sameBuilderConfig) throws IOException {
|
||||
if (file == null)
|
||||
throw new IllegalStateException("persistedTo(file) must be configured for " +
|
||||
"ChronicleHashInstanceBuilder, for recover()");
|
||||
if (!used) {
|
||||
ChronicleMap<K, V> map = mapBuilder.create(this, true, sameBuilderConfig);
|
||||
used = true;
|
||||
return map;
|
||||
} else {
|
||||
throw alreadyCreated();
|
||||
}
|
||||
}
|
||||
|
||||
private static IllegalStateException alreadyCreated() {
|
||||
return new IllegalStateException(
|
||||
"A ChronicleHash has already been created using this " +
|
||||
"ChronicleHashInstanceBuilder. Create a new instance builder " +
|
||||
"(chronicleHashBuilder.instance()) to create another Chronicle Hash");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReplicatedGlobalMutableState globalMutableState() {
|
||||
public ReplicatedGlobalMutableState globalMutableState() {
|
||||
return (ReplicatedGlobalMutableState) super.globalMutableState();
|
||||
}
|
||||
|
||||
@@ -340,6 +340,15 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
|
||||
}
|
||||
}
|
||||
|
||||
public ModificationIterator[] acquireAllModificationIterators() {
|
||||
for (int remoteIdentifier = 0; remoteIdentifier < 128; remoteIdentifier++) {
|
||||
if (globalMutableState().getModificationIteratorInitAt(remoteIdentifier)) {
|
||||
acquireModificationIterator((byte) remoteIdentifier);
|
||||
}
|
||||
}
|
||||
return assignedModificationIterators;
|
||||
}
|
||||
|
||||
public void raiseChange(long tierIndex, long pos) {
|
||||
// -1 is invalid remoteIdentifier => raise change for all
|
||||
raiseChangeForAllExcept(tierIndex, pos, (byte) -1);
|
||||
@@ -601,7 +610,7 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
|
||||
*
|
||||
* @author Rob Austin.
|
||||
*/
|
||||
class ModificationIterator implements Replica.ModificationIterator {
|
||||
public class ModificationIterator implements Replica.ModificationIterator {
|
||||
private final byte remoteIdentifier;
|
||||
private final long segmentBitSetsAddr;
|
||||
private final long offsetToBitSetsWithinATierBulk;
|
||||
@@ -926,5 +935,22 @@ public class ReplicatedChronicleMap<K, V, R> extends VanillaChronicleMap<K, V, R
|
||||
dropChangeInTierBulk(bulkIndex, offsetToTierBitSet, pos);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearRange0(long tierIndex, long pos, long endPosExclusive) {
|
||||
if (tierIndex <= actualSegments) {
|
||||
long segmentIndex = tierIndex - 1;
|
||||
long offsetToTierBitSet = segmentIndex * tierModIterBitSetOuterSize;
|
||||
tierModIterFrame.clearRange(nativeAccess(), null,
|
||||
segmentBitSetsAddr + offsetToTierBitSet, pos, endPosExclusive);
|
||||
} else {
|
||||
long extraTierIndex = tierIndex - 1 - actualSegments;
|
||||
int bulkIndex = (int) (extraTierIndex >> log2TiersInBulk);
|
||||
long offsetToTierBitSet =
|
||||
(extraTierIndex & (tiersInBulk - 1)) * tierModIterBitSetOuterSize;
|
||||
TierBulkData tierBulkData = tierBulkOffsets.get(bulkIndex);
|
||||
long bitSetAddr = bitSetsAddr(tierBulkData) + offsetToTierBitSet;
|
||||
tierModIterFrame.clearRange(nativeAccess(), null, bitSetAddr, pos, endPosExclusive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,8 +36,10 @@ import net.openhft.chronicle.wire.WireIn;
|
||||
import net.openhft.chronicle.wire.WireOut;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
@@ -148,6 +150,11 @@ public class VanillaChronicleMap<K, V, R>
|
||||
initOwnTransients();
|
||||
}
|
||||
|
||||
public void recover(File file, RandomAccessFile raf) throws IOException {
|
||||
basicRecover(file, raf);
|
||||
iterationContext().recoverSegments();
|
||||
}
|
||||
|
||||
private void initOwnTransients() {
|
||||
couldNotDetermineAlignmentBeforeAllocation =
|
||||
greatestCommonDivisor((int) chunkSize, alignment) != alignment;
|
||||
|
||||
@@ -19,8 +19,13 @@ package net.openhft.chronicle.map.impl;
|
||||
import net.openhft.chronicle.map.MapEntry;
|
||||
import net.openhft.chronicle.map.MapSegmentContext;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public interface IterationContext<K, V, R> extends MapEntry<K, V>, MapSegmentContext<K, V, R> {
|
||||
long pos();
|
||||
|
||||
void initSegmentIndex(int segmentIndex);
|
||||
|
||||
void recoverSegments();
|
||||
}
|
||||
|
||||
@@ -21,9 +21,7 @@ import net.openhft.chronicle.hash.impl.stage.entry.*;
|
||||
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.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.iter.*;
|
||||
import net.openhft.chronicle.hash.impl.stage.query.KeySearch;
|
||||
import net.openhft.chronicle.map.impl.stage.data.bytes.EntryValueBytesData;
|
||||
import net.openhft.chronicle.map.impl.stage.data.bytes.WrappedValueBytesData;
|
||||
@@ -61,6 +59,9 @@ import net.openhft.sg.Staged;
|
||||
MapEntryOperationsDelegation.class,
|
||||
|
||||
IterationAlloc.class,
|
||||
|
||||
TierRecovery.class,
|
||||
SegmentsRecovery.class,
|
||||
}, nested = {
|
||||
ReadLock.class,
|
||||
UpdateLock.class,
|
||||
|
||||
@@ -24,16 +24,14 @@ import net.openhft.chronicle.hash.impl.stage.hash.OwnerThreadHolder;
|
||||
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.iter.SegmentsRecovery;
|
||||
import net.openhft.chronicle.hash.impl.stage.query.KeySearch;
|
||||
import net.openhft.chronicle.map.impl.stage.data.DummyValueZeroData;
|
||||
import net.openhft.chronicle.map.impl.stage.data.bytes.EntryValueBytesData;
|
||||
import net.openhft.chronicle.map.impl.stage.data.bytes.WrappedValueBytesData;
|
||||
import net.openhft.chronicle.map.impl.stage.data.instance.WrappedValueInstanceDataHolder;
|
||||
import net.openhft.chronicle.map.impl.stage.entry.ReplicatedMapEntryStages;
|
||||
import net.openhft.chronicle.map.impl.stage.iter.IterationCheckOnEachPublicOperation;
|
||||
import net.openhft.chronicle.map.impl.stage.iter.ReplicatedMapAbsentDelegatingForIteration;
|
||||
import net.openhft.chronicle.map.impl.stage.iter.ReplicatedMapEntryDelegating;
|
||||
import net.openhft.chronicle.map.impl.stage.iter.ReplicatedMapSegmentIteration;
|
||||
import net.openhft.chronicle.map.impl.stage.iter.*;
|
||||
import net.openhft.chronicle.map.impl.stage.map.*;
|
||||
import net.openhft.chronicle.map.impl.stage.replication.ReplicationUpdate;
|
||||
import net.openhft.sg.Context;
|
||||
@@ -68,6 +66,9 @@ import net.openhft.sg.Staged;
|
||||
DefaultValue.class,
|
||||
|
||||
IterationAlloc.class,
|
||||
|
||||
ReplicatedTierRecovery.class,
|
||||
SegmentsRecovery.class,
|
||||
}, nested = {
|
||||
ReadLock.class,
|
||||
UpdateLock.class,
|
||||
|
||||
@@ -112,7 +112,7 @@ public abstract class MapEntryStages<K, V> extends HashEntryStages<K>
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long entryEnd() {
|
||||
public long entryEnd() {
|
||||
return valueOffset + valueSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ import net.openhft.sg.StageRef;
|
||||
import net.openhft.sg.Staged;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Staged
|
||||
public abstract class MapSegmentIteration<K, V, R> extends HashSegmentIteration<K, MapEntry<K, V>>
|
||||
implements MapEntry<K, V>, IterationContext<K, V, R> {
|
||||
@@ -48,4 +53,15 @@ public abstract class MapSegmentIteration<K, V, R> extends HashSegmentIteration<
|
||||
public WrappedValueInstanceDataHolderAccess<K, V, ?> context() {
|
||||
return wrappedValueInstanceDataHolderAccess;
|
||||
}
|
||||
//
|
||||
// @Override
|
||||
// public void recover(List<Set<Long>> usedTierIndexesBySegment) {
|
||||
// for (int visitedSegmentIndex = 0; visitedSegmentIndex < usedTierIndexesBySegment.size();
|
||||
// visitedSegmentIndex++) {
|
||||
// Set<Long> intersection = new HashSet<>(
|
||||
// usedTierIndexesBySegment.get(visitedSegmentIndex));
|
||||
// intersection.retainAll(usedTierIndexes);
|
||||
// if (Collections.dis)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.openhft.chronicle.map.impl.stage.iter;
|
||||
|
||||
import net.openhft.chronicle.algo.bitset.ReusableBitSet;
|
||||
import net.openhft.chronicle.hash.impl.CompactOffHeapLinearHashTable;
|
||||
import net.openhft.chronicle.hash.impl.VanillaChronicleHash;
|
||||
import net.openhft.chronicle.hash.impl.stage.entry.SegmentStages;
|
||||
import net.openhft.chronicle.hash.impl.stage.hash.LogHolder;
|
||||
import net.openhft.chronicle.hash.impl.stage.iter.TierRecovery;
|
||||
import net.openhft.chronicle.map.ReplicatedChronicleMap;
|
||||
import net.openhft.chronicle.map.impl.ReplicatedChronicleMapHolder;
|
||||
import net.openhft.chronicle.map.impl.stage.entry.ReplicatedMapEntryStages;
|
||||
import net.openhft.sg.StageRef;
|
||||
import net.openhft.sg.Staged;
|
||||
|
||||
@Staged
|
||||
public class ReplicatedTierRecovery extends TierRecovery {
|
||||
|
||||
@StageRef ReplicatedChronicleMapHolder<?, ?, ?> rh;
|
||||
@StageRef SegmentStages s;
|
||||
@StageRef ReplicatedMapEntryStages<?, ?> e;
|
||||
@StageRef LogHolder lh;
|
||||
|
||||
@Override
|
||||
public void removeDuplicatesInSegment() {
|
||||
super.removeDuplicatesInSegment();
|
||||
recoverTierDeleted();
|
||||
cleanupModificationIterationBits();
|
||||
}
|
||||
|
||||
private void recoverTierDeleted() {
|
||||
VanillaChronicleHash<?, ?, ?, ?> h = rh.h();
|
||||
CompactOffHeapLinearHashTable hl = h.hashLookup;
|
||||
long hlAddr = s.tierBaseAddr;
|
||||
|
||||
long deleted = 0;
|
||||
long hlPos = 0;
|
||||
do {
|
||||
long hlEntry = hl.readEntry(hlAddr, hlPos);
|
||||
if (!hl.empty(hlEntry)) {
|
||||
e.readExistingEntry(hl.value(hlEntry));
|
||||
if (e.entryDeleted()) {
|
||||
deleted++;
|
||||
}
|
||||
}
|
||||
hlPos = hl.step(hlPos);
|
||||
} while (hlPos != 0);
|
||||
if (s.tierDeleted() != deleted) {
|
||||
lh.LOG.error("wrong deleted counter for tier with index {}, stored: {}, should be: {}",
|
||||
s.tierIndex, s.tierDeleted(), deleted);
|
||||
s.tierDeleted(deleted);
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupModificationIterationBits() {
|
||||
ReplicatedChronicleMap<?, ?, ?> m = rh.m();
|
||||
ReplicatedChronicleMap<?, ?, ?>.ModificationIterator[] its =
|
||||
m.acquireAllModificationIterators();
|
||||
ReusableBitSet freeList = s.freeList;
|
||||
for (long pos = 0;;) {
|
||||
long nextPos = freeList.nextSetBit(pos);
|
||||
if (nextPos > pos) {
|
||||
for (ReplicatedChronicleMap<?, ?, ?>.ModificationIterator it : its) {
|
||||
it.clearRange0(s.tierIndex, pos, nextPos);
|
||||
}
|
||||
}
|
||||
if (nextPos > 0) {
|
||||
e.readExistingEntry(nextPos);
|
||||
if (e.entrySizeInChunks > 1) {
|
||||
for (ReplicatedChronicleMap<?, ?, ?>.ModificationIterator it : its) {
|
||||
it.clearRange0(s.tierIndex, nextPos + 1, nextPos + e.entrySizeInChunks);
|
||||
}
|
||||
}
|
||||
pos = nextPos + e.entrySizeInChunks;
|
||||
} else {
|
||||
if (pos < m.actualChunksPerSegmentTier) {
|
||||
for (ReplicatedChronicleMap<?, ?, ?>.ModificationIterator it : its) {
|
||||
it.clearRange0(s.tierIndex, pos, m.actualChunksPerSegmentTier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,12 +333,21 @@ public final class ChronicleSetBuilder<K>
|
||||
return new SetFromMap<>((VanillaChronicleMap<K, DummyValue, ?>) map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChronicleSet<K> recoverPersistedTo(File file, boolean sameBuilderConfig)
|
||||
throws IOException {
|
||||
ChronicleMap<K, DummyValue> map =
|
||||
chronicleMapBuilder.recoverPersistedTo(file, sameBuilderConfig);
|
||||
return new SetFromMap<>((VanillaChronicleMap<K, DummyValue, ?>) map);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated don't use private API in the client code
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public ChronicleHashBuilderPrivateAPI<K> privateAPI() {
|
||||
//noinspection deprecation
|
||||
return chronicleMapBuilder.privateAPI();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,17 +69,12 @@ final class SetInstanceBuilder<E> implements ChronicleHashInstanceBuilder<Chroni
|
||||
|
||||
@Override
|
||||
public synchronized ChronicleSet<E> create() throws IOException {
|
||||
try {
|
||||
return new SetFromMap<>((VanillaChronicleMap<E, DummyValue, ?>) mapBuilder.create());
|
||||
} catch (IllegalStateException e) {
|
||||
if (e.getMessage() != null && e.getMessage().startsWith("A ChronicleMap")) {
|
||||
throw new IllegalStateException(
|
||||
"A ChronicleSet has already been created using this instance config. " +
|
||||
"Create a new instance config (builder.instance()) to create " +
|
||||
"a new ChronicleSet instance");
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return new SetFromMap<>((VanillaChronicleMap<E, DummyValue, ?>) mapBuilder.create());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized ChronicleSet<E> recover(boolean sameBuilderConfig) throws IOException {
|
||||
ChronicleMap<E, DummyValue> map = mapBuilder.recover(sameBuilderConfig);
|
||||
return new SetFromMap<>((VanillaChronicleMap<E, DummyValue, ?>) map);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user