Example #1
0
  /**
   * 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();
    }
  }
  /** Flush the target IN. */
  private void flushIN(
      IN target, LogManager logManager, Map dirtyMap, boolean logProvisionally, boolean allowDeltas)
      throws DatabaseException {

    DatabaseImpl db = target.getDatabase();
    Tree tree = db.getTree();
    boolean targetWasRoot = false;

    if (target.isDbRoot()) {
      /* We're trying to flush the root. */
      target.releaseLatch();
      RootFlusher flusher = new RootFlusher(db, logManager, target);
      tree.withRootLatched(flusher);
      boolean flushed = flusher.getFlushed();

      /*
       * We have to check if the root split between target.releaseLatch
       * and the execution of the root flusher. If it did split, this
       * target has to get handled like a regular node.
       */
      targetWasRoot = flusher.stillRoot();

      /*
       * Update the tree's owner, whether it's the env root or the
       * dbmapping tree.
       */
      if (flushed) {
        DbTree dbTree = db.getDbEnvironment().getDbMapTree();
        dbTree.modifyDbRoot(db);
        nFullINFlushThisRun++;
        nFullINFlush++;
      }
      if (!targetWasRoot) {
        /*
         * re-latch for another attempt, now that this is no longer
         * the root.
         */
        target.latch();
      }
    }

    if (!targetWasRoot) {
      SearchResult result = tree.getParentINForChildIN(target, true);

      /*
       * Found a parent, do the flush. If no parent found, the
       * compressor deleted this item before we got to processing it.
       */
      if (result.exactParentFound) {
        try {
          ChildReference entry = result.parent.getEntry(result.index);
          IN renewedTarget = (IN) entry.fetchTarget(db, result.parent);
          renewedTarget.latch();
          DbLsn newLsn = null;
          try {

            /* Still dirty? */
            if (renewedTarget.getDirty()) {
              if (allowDeltas) {
                newLsn = renewedTarget.logAllowDeltas(logManager, logProvisionally);
                if (newLsn == null) {
                  nDeltaINFlushThisRun++;
                  nDeltaINFlush++;
                }
              } else {
                newLsn = renewedTarget.log(logManager, logProvisionally);
              }
            }
          } finally {
            renewedTarget.releaseLatch();
          }

          /* Update parent if logging occurred */
          if (newLsn != null) {
            nFullINFlushThisRun++;
            nFullINFlush++;
            if (renewedTarget instanceof BIN) {
              nFullBINFlush++;
            }
            result.parent.updateEntry(result.index, newLsn);
            addToDirtyMap(dirtyMap, result.parent);
          }
        } finally {
          result.parent.releaseLatch();
        }
      }
    }
  }