@Test public void open_ignores_rollback() { File f = TT.tempDbFile(); WriteAheadLog wal = new WriteAheadLog(f.getPath()); wal.walPutLong(1L, 11L); wal.commit(); wal.walPutLong(2L, 33L); wal.rollback(); wal.walPutLong(3L, 33L); wal.commit(); wal.seal(); wal.close(); wal = new WriteAheadLog(f.getPath()); wal.open( new WALSequence( new Object[] {WALSequence.beforeReplayStart}, new Object[] {WALSequence.writeLong, 1L, 11L}, new Object[] {WALSequence.commit}, // 2L is ignored, rollback section is skipped on hard replay new Object[] {WALSequence.writeLong, 3L, 33L}, new Object[] {WALSequence.commit})); wal.destroyWalFiles(); wal.close(); f.delete(); }
@Override public void close() { commitLock.lock(); try { if (closed) { return; } if (hasUncommitedData()) { LOG.warning("Closing storage with uncommited data, this data will be discarded."); } headVol.putData(0, headVolBackup, 0, headVolBackup.length); if (!readonly) { replaySoft(); wal.destroyWalFiles(); } wal.close(); vol.close(); vol = null; headVol.close(); headVol = null; headVolBackup = null; uncommittedStackPages.clear(); if (caches != null) { for (Cache c : caches) { c.close(); } Arrays.fill(caches, null); } if (fileLockHeartbeat != null) { fileLockHeartbeat.unlock(); fileLockHeartbeat = null; } closed = true; } finally { commitLock.unlock(); } }
@Test public void overflow_record() { File f = TT.tempDbFile(); f.delete(); File f0 = new File(f.getPath() + ".wal.0"); File f1 = new File(f.getPath() + ".wal.1"); WriteAheadLog wal = new WriteAheadLog(f.getPath()); wal.open(WriteAheadLog.NOREPLAY); long lastPos = 0; while (!f1.exists()) { lastPos = wal.fileOffset; wal.walPutRecord(111L, new byte[100], 0, 100); assertTrue(f0.exists()); } assertTrue(WriteAheadLog.MAX_FILE_SIZE - 1000 < lastPos); assertTrue(WriteAheadLog.MAX_FILE_SIZE + 120 > lastPos); wal.destroyWalFiles(); }
@Override public void commit() { commitLock.lock(); try { // flush write caches into write ahead log flushWriteCache(); // move uncommited data to committed for (int segment = 0; segment < locks.length; segment++) { locks[segment].writeLock().lock(); try { // dump index vals into WAL long[] table = uncommittedIndexTable[segment].table; for (int i = 0; i < table.length; ) { long offset = table[i++]; long val = table[i++]; if (offset == 0) continue; wal.walPutLong(offset, val); } moveAndClear(uncommittedIndexTable[segment], committedIndexTable[segment]); moveAndClear(uncommittedDataLongs[segment], committedDataLongs[segment]); } finally { locks[segment].writeLock().unlock(); } } structuralLock.lock(); try { // flush modified Long Stack pages into WAL long[] set = uncommittedStackPages.set; longStackPagesLoop: for (int i = 0; i < set.length; i++) { long offset = set[i]; if (offset == 0) continue longStackPagesLoop; byte[] val = (byte[]) uncommittedStackPages.values[i]; if (val == LONG_STACK_PAGE_TOMBSTONE) committedPageLongStack.put(offset, -1); else { if (CC.ASSERT) assertLongStackPage(offset, val); long walPointer = wal.walPutByteArray(offset, val, 0, val.length); committedPageLongStack.put(offset, walPointer); } } uncommittedStackPages.clear(); // update checksum headVol.putInt(HEAD_CHECKSUM, headChecksum(headVol)); // take backup of headVol headVol.getData(0, headVolBackup, 0, headVolBackup.length); wal.walPutByteArray(0, headVolBackup, 0, headVolBackup.length); wal.commit(); wal.seal(); replaySoft(); realVol.sync(); wal.destroyWalFiles(); } finally { structuralLock.unlock(); } } finally { commitLock.unlock(); } }
@Override public void initOpen() { // TODO disable readonly feature for this store realVol = vol; if (readonly && !Volume.isEmptyFile(fileName + ".wal.0")) throw new DBException.WrongConfig( "There is dirty WAL file, but storage is read-only. Can not replay file"); wal.open( new WriteAheadLog.WALReplay() { @Override public void beforeReplayStart() {} @Override public void writeLong(long offset, long value) { if (CC.ASSERT && offset % 8 != 0) throw new AssertionError(); realVol.ensureAvailable(Fun.roundUp(offset + 8, StoreDirect.PAGE_SIZE)); realVol.putLong(offset, value); } @Override public void writeRecord(long recid, long walId, Volume vol, long volOffset, int length) { throw new DBException.DataCorruption(); } @Override public void writeByteArray( long offset, long walId, Volume vol, long volOffset, int length) { if (CC.ASSERT && offset % 8 != 0) throw new AssertionError(); realVol.ensureAvailable(Fun.roundUp(offset + length, StoreDirect.PAGE_SIZE)); vol.transferInto(volOffset, realVol, offset, length); } @Override public void beforeDestroyWAL() {} @Override public void commit() {} @Override public void rollback() { throw new DBException.DataCorruption(); } @Override public void writeTombstone(long recid) { throw new DBException.DataCorruption(); } @Override public void writePreallocate(long recid) { throw new DBException.DataCorruption(); } }); realVol.sync(); wal.destroyWalFiles(); initOpenPost(); // TODO reenable this assertion // if(CC.PARANOID) // storeCheck(); }