/** * This test checks for the bug described in SR11123. If an IN and its child-subtree is deleted, * an INDeleteInfo is written to the log. If there is a BINDelta in the log for a BIN-child of the * removed subtree (i.e. compressed), then recovery will apply it to the compressed IN. Since the * IN has no data in * it, that is not necessarily a problem. However, reinstantiating the * obsolete IN may cause a parent IN to split which is not allowed during IN recovery. * * <p>Here's the case: * * <p>| IN1 +---------------------------------+ | | IN2 IN6 / | / | \ BIN3 BIN4 BIN7 BIN8 BIN9 * * <p>IN2 and the subtree below are compressed away. During recovery replay, after the pass where * INs and INDeleteINfos are processed, the in-memory tree looks like this: * * <p>IN1 | IN6 / | \ BIN7 BIN8 BIN9 * * <p>However, let's assume that BINDeltas were written for BIN3, BIN4, BIN5 within the recovery * part of the log, before the subtree was compressed. We'll replay those BINDeltas in the * following pass, and in the faulty implementation, they cause the ghosts of BIN3, BIN4 to be * resurrected and applied to IN6. Let's assume that the max node size is 4 -- we won't be able to * connect BIN3, BIN4 because IN6 doesn't have the capacity, and we don't expect to have to do * splits. */ private void addData(Database db) throws DatabaseException { DatabaseEntry key = new DatabaseEntry(); DatabaseEntry data = new DatabaseEntry(); /* Populate a tree so there are 3 levels. */ for (int i = 0; i < 140; i += 10) { IntegerBinding.intToEntry(i, key); IntegerBinding.intToEntry(i, data); assertEquals(OperationStatus.SUCCESS, db.put(null, key, data)); } CheckpointConfig ckptConfig = new CheckpointConfig(); ckptConfig.setForce(true); env.checkpoint(ckptConfig); Tree tree = DbInternal.dbGetDatabaseImpl(db).getTree(); com.sleepycat.je.tree.Key.DUMP_TYPE = com.sleepycat.je.tree.Key.DumpType.BINARY; com.sleepycat.je.tree.Key.DUMP_INT_BINDING = true; if (DEBUG) { tree.dump(); } /* * Update a key on the BIN3 and a key on BIN4, to create reason for * a BINDelta. Force a BINDelta for BIN3 and BIN4 out to the log. */ IntegerBinding.intToEntry(0, key); IntegerBinding.intToEntry(100, data); assertEquals(OperationStatus.SUCCESS, db.put(null, key, data)); IntegerBinding.intToEntry(20, key); assertEquals(OperationStatus.SUCCESS, db.put(null, key, data)); EnvironmentImpl envImpl = DbInternal.envGetEnvironmentImpl(env); BIN bin = (BIN) tree.getFirstNode(); bin.log(envImpl.getLogManager(), true, false, false, false, null); bin = tree.getNextBin(bin, false /* traverseWithinDupTree */); bin.log(envImpl.getLogManager(), true, false, false, false, null); bin.releaseLatch(); /* * Delete all of left hand side of the tree, so that the subtree root * headed by IN2 is compressed. */ for (int i = 0; i < 50; i += 10) { IntegerBinding.intToEntry(i, key); assertEquals(OperationStatus.SUCCESS, db.delete(null, key)); } /* force a compression */ env.compress(); if (DEBUG) { tree.dump(); } }
/** * Converts the given DBIN slot, leaving bin/index set to the inserted BIN slot. * * <p>Enter/leave with bin field latched, although bin field may change. * * <p>If slot is inserted into current bin, leave bin field unchanged and set index field to * inserted slot. * * <p>If slot is inserted into a different bin, set bin/index fields to inserted slot. */ private void convertDbinSlot(final DBIN dbin, final int dbinIndex, final byte[] binKey) { final byte[] newKey = DupKeyData.replaceData(binKey, dbin.getKey(dbinIndex)); if (DEBUG) { System.out.println("DupConvert DBIN LN " + Key.dumpString(newKey, 0)); } /* * If the current BIN can hold the new slot, don't bother to do a * search to find it. */ if (bin.needsSplitting() || !bin.isKeyInBounds(newKey)) { /* Compact keys after finishing with a BIN. */ bin.compactMemory(); /* Evict without latches, before moving to a new BIN. */ bin.releaseLatch(); envImpl.daemonEviction(false /*backgroundIO*/); /* Find a BIN for insertion, split if necessary. */ bin = dbin.getDatabase().getTree().searchSplitsAllowed(newKey, CacheMode.UNCHANGED); } final int newIndex = bin.insertEntry1( null /*ln*/, newKey, null /*data*/, dbin.getLsn(dbinIndex), dbin.getState(dbinIndex), false); if ((newIndex & IN.INSERT_SUCCESS) == 0) { throw EnvironmentFailureException.unexpectedState( "Key not inserted: " + Key.dumpString(newKey, 0) + " DB: " + dbin.getDatabase().getId()); } index = newIndex & ~IN.INSERT_SUCCESS; /* * Evict LN from DBIN slot. Although we don't explicitly load DBIN LNs, * it may have been loaded by recovery. */ dbin.detachNode(dbinIndex, false /*updateLsn*/, -1 /*lsn*/); nConverted += 1; }
public void testEntryData() throws Throwable { try { ByteBuffer buffer = ByteBuffer.allocate(1000); database = new DatabaseImpl("foo", new DatabaseId(1), env, new DatabaseConfig()); /* * For each loggable object, can we write the entry data out? */ /* * Tracer records. */ Tracer dMsg = new Tracer("Hello there"); writeAndRead(buffer, LogEntryType.LOG_TRACE, dMsg, new Tracer()); /* * LNs */ String data = "abcdef"; LN ln = new LN(data.getBytes()); LN lnFromLog = new LN(); writeAndRead(buffer, LogEntryType.LOG_LN, ln, lnFromLog); lnFromLog.verify(null); assertTrue(LogEntryType.LOG_LN.marshallOutsideLatch()); FileSummaryLN fsLN = new FileSummaryLN(new FileSummary()); FileSummaryLN fsLNFromLog = new FileSummaryLN(); writeAndRead(buffer, LogEntryType.LOG_FILESUMMARYLN, fsLN, fsLNFromLog); assertFalse(LogEntryType.LOG_FILESUMMARYLN.marshallOutsideLatch()); /* * INs */ IN in = new IN(database, new byte[] {1, 0, 1, 0}, 7, 5); in.latch(); in.insertEntry(new ChildReference(null, new byte[] {1, 0, 1, 0}, DbLsn.makeLsn(12, 200))); in.insertEntry(new ChildReference(null, new byte[] {1, 1, 1, 0}, DbLsn.makeLsn(29, 300))); in.insertEntry(new ChildReference(null, new byte[] {0, 0, 1, 0}, DbLsn.makeLsn(35, 400))); /* Write it. */ IN inFromLog = new IN(); inFromLog.latch(); writeAndRead(buffer, LogEntryType.LOG_IN, in, inFromLog); inFromLog.releaseLatch(); in.releaseLatch(); /* * IN - long form */ in = new IN(database, new byte[] {1, 0, 1, 0}, 7, 5); in.latch(); in.insertEntry(new ChildReference(null, new byte[] {1, 0, 1, 0}, DbLsn.makeLsn(12, 200))); in.insertEntry(new ChildReference(null, new byte[] {1, 1, 1, 0}, DbLsn.makeLsn(29, 300))); in.insertEntry(new ChildReference(null, new byte[] {0, 0, 1, 0}, DbLsn.makeLsn(1235, 400))); in.insertEntry( new ChildReference(null, new byte[] {0, 0, 1, 0}, DbLsn.makeLsn(0xFFFFFFF0L, 400))); /* Write it. */ inFromLog = new IN(); inFromLog.latch(); writeAndRead(buffer, LogEntryType.LOG_IN, in, inFromLog); inFromLog.releaseLatch(); in.releaseLatch(); /* * BINs */ BIN bin = new BIN(database, new byte[] {3, 2, 1}, 8, 5); bin.latch(); bin.insertEntry(new ChildReference(null, new byte[] {1, 0, 1, 0}, DbLsn.makeLsn(212, 200))); bin.insertEntry(new ChildReference(null, new byte[] {1, 1, 1, 0}, DbLsn.makeLsn(229, 300))); bin.insertEntry(new ChildReference(null, new byte[] {0, 0, 1, 0}, DbLsn.makeLsn(235, 400))); BIN binFromLog = new BIN(); binFromLog.latch(); writeAndRead(buffer, LogEntryType.LOG_BIN, bin, binFromLog); binFromLog.verify(null); binFromLog.releaseLatch(); bin.releaseLatch(); /* * DINs */ DIN din = new DIN( database, new byte[] {1, 0, 0, 1}, 7, new byte[] {0, 1, 1, 0}, new ChildReference(null, new byte[] {1, 0, 0, 1}, DbLsn.makeLsn(10, 100)), 5); din.latch(); din.insertEntry(new ChildReference(null, new byte[] {1, 0, 1, 0}, DbLsn.makeLsn(12, 200))); din.insertEntry(new ChildReference(null, new byte[] {1, 1, 1, 0}, DbLsn.makeLsn(29, 300))); din.insertEntry(new ChildReference(null, new byte[] {0, 0, 1, 0}, DbLsn.makeLsn(35, 400))); /* Write it. */ DIN dinFromLog = new DIN(); dinFromLog.latch(); writeAndRead(buffer, LogEntryType.LOG_DIN, din, dinFromLog); din.releaseLatch(); dinFromLog.releaseLatch(); /* * DBINs */ DBIN dbin = new DBIN(database, new byte[] {3, 2, 1}, 8, new byte[] {1, 2, 3}, 5); dbin.latch(); dbin.insertEntry(new ChildReference(null, new byte[] {1, 0, 1, 0}, DbLsn.makeLsn(212, 200))); dbin.insertEntry(new ChildReference(null, new byte[] {1, 1, 1, 0}, DbLsn.makeLsn(229, 300))); dbin.insertEntry(new ChildReference(null, new byte[] {0, 0, 1, 0}, DbLsn.makeLsn(235, 400))); DBIN dbinFromLog = new DBIN(); dbinFromLog.latch(); writeAndRead(buffer, LogEntryType.LOG_DBIN, dbin, dbinFromLog); dbinFromLog.verify(null); dbin.releaseLatch(); dbinFromLog.releaseLatch(); /* * Root */ DbTree dbTree = new DbTree(env); DbTree dbTreeFromLog = new DbTree(); writeAndRead(buffer, LogEntryType.LOG_ROOT, dbTree, dbTreeFromLog); /* * MapLN */ MapLN mapLn = new MapLN(database); MapLN mapLnFromLog = new MapLN(); writeAndRead(buffer, LogEntryType.LOG_MAPLN, mapLn, mapLnFromLog); /* * UserTxn */ /* * Disabled for now because these txns don't compare equal, * because one has a name of "main" and the other has a name of * null because it was read from the log. Txn txn = new Txn(env, new TransactionConfig()); Txn txnFromLog = new Txn(); writeAndRead(buffer, LogEntryType.TXN_COMMIT, txn, txnFromLog); txn.commit(); */ /* * TxnCommit */ TxnCommit commit = new TxnCommit(111, DbLsn.makeLsn(10, 10)); TxnCommit commitFromLog = new TxnCommit(); writeAndRead(buffer, LogEntryType.LOG_TXN_COMMIT, commit, commitFromLog); /* * TxnAbort */ TxnAbort abort = new TxnAbort(111, DbLsn.makeLsn(11, 11)); TxnAbort abortFromLog = new TxnAbort(); writeAndRead(buffer, LogEntryType.LOG_TXN_ABORT, abort, abortFromLog); /* * TxnPrepare */ byte[] gid = new byte[64]; byte[] bqual = new byte[64]; TxnPrepare prepare = new TxnPrepare(111, new LogUtils.XidImpl(1, gid, bqual)); TxnPrepare prepareFromLog = new TxnPrepare(); writeAndRead(buffer, LogEntryType.LOG_TXN_PREPARE, prepare, prepareFromLog); prepare = new TxnPrepare(111, new LogUtils.XidImpl(1, null, bqual)); prepareFromLog = new TxnPrepare(); writeAndRead(buffer, LogEntryType.LOG_TXN_PREPARE, prepare, prepareFromLog); prepare = new TxnPrepare(111, new LogUtils.XidImpl(1, gid, null)); prepareFromLog = new TxnPrepare(); writeAndRead(buffer, LogEntryType.LOG_TXN_PREPARE, prepare, prepareFromLog); /* * IN delete info */ INDeleteInfo info = new INDeleteInfo(77, new byte[1], new DatabaseId(100)); INDeleteInfo infoFromLog = new INDeleteInfo(); writeAndRead(buffer, LogEntryType.LOG_IN_DELETE_INFO, info, infoFromLog); /* * Checkpoint start */ CheckpointStart start = new CheckpointStart(177, "test"); CheckpointStart startFromLog = new CheckpointStart(); writeAndRead(buffer, LogEntryType.LOG_CKPT_START, start, startFromLog); /* * Checkpoint end */ CheckpointEnd end = new CheckpointEnd( "test", DbLsn.makeLsn(20, 55), env.getRootLsn(), env.getTxnManager().getFirstActiveLsn(), Node.getLastId(), env.getDbMapTree().getLastDbId(), env.getTxnManager().getLastTxnId(), 177); CheckpointEnd endFromLog = new CheckpointEnd(); writeAndRead(buffer, LogEntryType.LOG_CKPT_END, end, endFromLog); } catch (Throwable t) { t.printStackTrace(); throw t; } }