public long innerEntrySize(long sizeOfEverythingBeforeValue, long valueSize) { if (mh.m().constantlySizedEntry) { return mh.m().alignment.alignAddr(sizeOfEverythingBeforeValue + valueSize); } else if (mh.m().couldNotDetermineAlignmentBeforeAllocation) { return sizeOfEverythingBeforeValue + mh.m().worstAlignment + valueSize; } else { return mh.m().alignment.alignAddr(sizeOfEverythingBeforeValue) + valueSize; } }
public final void freeExtraAllocatedChunks() { // fast path if (!mh.m().constantlySizedEntry && mh.m().couldNotDetermineAlignmentBeforeAllocation && entrySizeInChunks < allocatedChunks.allocatedChunks) { s.free(pos + entrySizeInChunks, allocatedChunks.allocatedChunks - entrySizeInChunks); } else { initTheEntrySizeInChunks(allocatedChunks.allocatedChunks); } }
private void shiftHashLookupEntries() { VanillaChronicleHash<?, ?, ?, ?> h = mh.h(); CompactOffHeapLinearHashTable hl = h.hashLookup; long hlAddr = s.tierBaseAddr; long hlPos = 0; long steps = 0; do { long hlEntry = hl.readEntry(hlAddr, hlPos); if (!hl.empty(hlEntry)) { long searchKey = hl.key(hlEntry); long hlHolePos = hl.hlPos(searchKey); while (hlHolePos != hlPos) { long hlHoleEntry = hl.readEntry(hlAddr, hlHolePos); if (hl.empty(hlHoleEntry)) { hl.writeEntry(hlAddr, hlHolePos, hlEntry); if (hl.remove(hlAddr, hlPos) != hlPos) { hlPos = hl.stepBack(hlPos); steps--; } break; } hlHolePos = hl.step(hlHolePos); } } hlPos = hl.step(hlPos); steps++; } while (hlPos != 0 || steps == 0); }
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); } }
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 = ((MapEntry) c).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 != null ? ((MapEntry) c).value() : "<deleted>", !e.entryDeleted() ? e.value() : "<deleted>"); 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(); }
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 validEntries = 0; long hlPos = 0; do { long hlEntry = hl.readEntry(hlAddr, hlPos); nextHlPos: if (!hl.empty(hlEntry)) { // (*) hl.clearEntry(hlAddr, hlPos); if (validEntries >= h.maxEntriesPerHashLookup) { log.error( "Too many entries in tier with index {}, max is {}", s.tierIndex, h.maxEntriesPerHashLookup); break nextHlPos; } long searchKey = hl.key(hlEntry); long entryPos = hl.value(hlEntry); int si = checkEntry(searchKey, entryPos, segmentIndex); if (si < 0) { break nextHlPos; } else { s.freeList.setRange(entryPos, entryPos + e.entrySizeInChunks); 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)); validEntries++; break nextHlPos; } if (insertPos == hlPos) { // means we made a whole loop, without finding a hole to re-insert entry, // even if hashLookup was corrupted and all slots are dirty now, at least // the slot cleared at (*) should be clear, if it is dirty, only // a concurrent modification thread could occupy it throw new ChronicleHashRecoveryFailedException( "Concurrent modification of ChronicleMap at " + h.file() + " while recovery procedure is in progress"); } checkDuplicateKeys: if (hl.key(hlInsertEntry) == searchKey) { long anotherEntryPos = hl.value(hlInsertEntry); if (anotherEntryPos == entryPos) { validEntries++; break nextHlPos; } long currentKeyOffset = e.keyOffset; long currentKeySize = e.keySize; int currentEntrySizeInChunks = e.entrySizeInChunks; if (insertPos >= 0 && insertPos < hlPos) { // insertPos already checked e.readExistingEntry(anotherEntryPos); } else if (checkEntry(searchKey, anotherEntryPos, segmentIndex) < 0) { break checkDuplicateKeys; } 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); shiftHashLookupEntries(); return segmentIndex; }
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); return -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 { // e.entryEnd() implicitly reads the value size, to be computed 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()); return -1; } 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; } } }
void initValSize() { entryBytes.position(valueSizeOffset); valueSize = mh.m().readValueSize(entryBytes); countValueOffset(); }
void initValSize(long valueSize) { this.valueSize = valueSize; entryBytes.position(valueSizeOffset); mh.m().valueSizeMarshaller.writeSize(entryBytes, valueSize); countValueOffset(); }
@Stage("ValSize") private void countValueOffset() { mh.m().alignment.alignPositionAddr(entryBytes); valueOffset = entryBytes.position(); }
long sizeOfEverythingBeforeValue(long keySize, long valueSize) { return mh.m().metaDataBytes + mh.m().keySizeMarshaller.sizeEncodingSize(keySize) + keySize + mh.m().valueSizeMarshaller.sizeEncodingSize(valueSize); }
public void innerDefaultReplaceValue(Data<V> newValue) { assert s.innerUpdateLock.isHeldByCurrentThread(); boolean newValueSizeIsDifferent = newValue.size() != this.valueSize; if (newValueSizeIsDifferent) { long newSizeOfEverythingBeforeValue = newSizeOfEverythingBeforeValue(newValue); long entryStartOffset = keySizeOffset; VanillaChronicleMap<?, ?, ?, ?, ?, ?, ?> m = mh.m(); long newValueOffset = m.alignment.alignAddr(entryStartOffset + newSizeOfEverythingBeforeValue); long newEntrySize = newValueOffset + newValue.size() - entryStartOffset; int newSizeInChunks = m.inChunks(newEntrySize); newValueDoesNotFit: if (newSizeInChunks > entrySizeInChunks) { if (newSizeInChunks > m.maxChunksPerEntry) { throw new IllegalArgumentException( "Value too large: " + "entry takes " + newSizeInChunks + " chunks, " + m.maxChunksPerEntry + " is maximum."); } if (s.freeList.allClear(pos + entrySizeInChunks, pos + newSizeInChunks)) { s.freeList.set(pos + entrySizeInChunks, pos + newSizeInChunks); break newValueDoesNotFit; } relocation(newValue, newSizeOfEverythingBeforeValue); return; } else if (newSizeInChunks < entrySizeInChunks) { // Freeing extra chunks s.freeList.clear(pos + newSizeInChunks, pos + entrySizeInChunks); // Do NOT reset nextPosToSearchFrom, because if value // once was larger it could easily became larger again, // But if these chunks will be taken by that time, // this entry will need to be relocated. } // new size != old size => size is not constant => size is actually written => // to prevent (at least) this execution: // 1. concurrent reader thread reads the size // 2. this thread updates the size and the value // 3. concurrent reader reads the value // We MUST upgrade to exclusive lock } else { // TODO to turn the following block on, JLANG-46 is required. Also unclear what happens // if the value is DataValue generated with 2, 4 or 8 distinct bytes, putting on-heap // implementation of such value is also not atomic currently, however there is a way // to make it atomic, we should identify such cases and make a single write: // state = UNSAFE.getLong(onHeapValueObject, offsetToTheFirstField); // bytes.writeLong(state); // boolean newValueSizeIsPowerOf2 = ((newValueSize - 1L) & newValueSize) != 0; // if (!newValueSizeIsPowerOf2 || newValueSize > 8L) { // // if the new value size is 1, 2, 4, or 8, it is written not atomically only // if // // the user provided own marshaller and writes value byte-by-byte, that is // very // // unlikely. in this case the user should update acquire write lock before // write // // updates himself // upgradeToWriteLock(); // } } s.innerWriteLock.lock(); if (newValueSizeIsDifferent) { initValue(newValue); } else { writeValue(newValue); } hashLookup.putValueVolatile(hlp.hashLookupPos, pos); }
public long newSizeOfEverythingBeforeValue(Data<V> newValue) { return valueSizeOffset + mh.m().valueSizeMarshaller.sizeEncodingSize(newValue.size()) - keySizeOffset; }
@Override public long size() { return mh.m().valueSizeMarshaller.minEncodableSize(); }