/* * Copyright (C) 2012, 2016 higherfrequencytrading.com * Copyright (C) 2016 Roman Leventov * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package net.openhft.chronicle.map; import net.openhft.chronicle.core.values.LongValue; import net.openhft.chronicle.hash.ChecksumEntry; import net.openhft.chronicle.hash.ChronicleHashBuilderPrivateAPI; import net.openhft.chronicle.hash.ChronicleHashCorruption; import net.openhft.chronicle.values.Values; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import static net.openhft.chronicle.map.ChronicleMapTest.getPersistenceFile; import static org.junit.Assert.*; public class RecoverTest { Logger LOG = LoggerFactory.getLogger(RecoverTest.class); ReplicatedChronicleMap<Integer, Integer, ?> map; @Ignore("HCOLL-422") @Test public void recoverTest() throws IOException, ExecutionException, InterruptedException { File mapFile = File.createTempFile("recoverTestFile", ".map"); mapFile.deleteOnExit(); ChronicleMapBuilder<Integer, Integer> builder = ChronicleMap .of(Integer.class, Integer.class) .entries(2) .actualSegments(1) .checksumEntries(true); ChronicleHashBuilderPrivateAPI<?, ?> privateAPI = (ChronicleHashBuilderPrivateAPI<?, ?>) builder.privateAPI(); privateAPI.replication((byte) 1); privateAPI.cleanupRemovedEntries(false); map = (ReplicatedChronicleMap<Integer, Integer, ?>) builder.createPersistedTo(mapFile); map.acquireModificationIterator((byte) 2); // acquires read lock successfully assertNull(map.get(0)); ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.submit(() -> { ExternalMapQueryContext<Integer, Integer, ?> c = map.queryContext(0); c.writeLock().lock(); }).get(); try { map.get(0); throw new AssertionError("Expected dead lock exception"); } catch (Exception expected) { // do nothing } map.close(); map = (ReplicatedChronicleMap<Integer, Integer, ?>) builder.recoverPersistedTo(mapFile, true); // acquires read lock successfully assertNull(map.get(0)); map.put(1, 1); map.put(2, 2); map.remove(1); long segmentHeadersOffset = this.map.segmentHeadersOffset; map.close(); try (RandomAccessFile raf = new RandomAccessFile(mapFile, "rw")) { FileChannel ch = raf.getChannel(); MappedByteBuffer mapBB = ch.map(FileChannel.MapMode.READ_WRITE, 0, mapFile.length()); for (long offset = segmentHeadersOffset; offset < mapFile.length(); offset += 8) { for (int bit = 0; bit < 64; bit++) { LOG.error("flip bit {} of word at {}", bit, offset); mapBB.putLong((int) offset, mapBB.getLong((int) offset) ^ (1L << bit)); ChronicleMapBuilder<Integer, Integer> recoverBuilder = ChronicleMap .of(Integer.class, Integer.class); ChronicleHashBuilderPrivateAPI<?, ?> recoverPrivateAPI = (ChronicleHashBuilderPrivateAPI<?, ?>) recoverBuilder.privateAPI(); recoverPrivateAPI.replication((byte) 1); recoverPrivateAPI.cleanupRemovedEntries(false); try (ChronicleMap<Integer, Integer> recovered = recoverBuilder.recoverPersistedTo(mapFile, false)) { recovered.put(1, 1); recovered.put(2, 2); recovered.remove(1); } } } } } @Test public void testCorruptedEntryRecovery() throws IOException { File file = getPersistenceFile(); try (ChronicleMap<Integer, LongValue> map = ChronicleMap .of(Integer.class, LongValue.class) .entries(1) .createPersistedTo(file)) { LongValue value = Values.newHeapInstance(LongValue.class); value.setValue(42); map.put(1, value); try (ExternalMapQueryContext<Integer, LongValue, ?> c = map.queryContext(1)) { // Update lock required for calling ChecksumEntry.checkSum() c.updateLock().lock(); MapEntry<Integer, LongValue> entry = c.entry(); assertNotNull(entry); ChecksumEntry checksumEntry = (ChecksumEntry) entry; assertTrue(checksumEntry.checkSum()); // to access off-heap bytes, should call value().getUsing() with Native value // provided. Simple get() return Heap value by default LongValue nativeValue = entry.value().getUsing(Values.newNativeReference(LongValue.class)); // This value bytes update bypass Chronicle Map internals, so checksum is not // updated automatically nativeValue.setValue(43); Assert.assertFalse(checksumEntry.checkSum()); } } AtomicInteger corruptionCounter = new AtomicInteger(0); ChronicleHashCorruption.Listener corruptionListener = corruption -> corruptionCounter.incrementAndGet(); //noinspection EmptyTryBlock try (ChronicleMap<Integer, LongValue> ignore = ChronicleMap .of(Integer.class, LongValue.class) .entries(1) .createOrRecoverPersistedTo(file, true, corruptionListener)) { } assertTrue(corruptionCounter.get() > 0); } }