Пример #1
0
  /**
   * Opens a sequence handle, adding the sequence record if appropriate.
   *
   * @throws IllegalArgumentException via Database.openSequence.
   * @throws IllegalStateException via Database.openSequence.
   */
  Sequence(Database db, Transaction txn, DatabaseEntry key, SequenceConfig config)
      throws SequenceNotFoundException, SequenceExistsException {

    if (db.getDatabaseImpl().getSortedDuplicates()) {
      throw new UnsupportedOperationException(
          "Sequences not supported in databases configured for " + "duplicates");
    }

    SequenceConfig useConfig = (config != null) ? config : SequenceConfig.DEFAULT;

    if (useConfig.getRangeMin() >= useConfig.getRangeMax()) {
      throw new IllegalArgumentException("Minimum sequence value must be less than the maximum");
    }

    if (useConfig.getInitialValue() > useConfig.getRangeMax()
        || useConfig.getInitialValue() < useConfig.getRangeMin()) {
      throw new IllegalArgumentException("Initial sequence value is out of range");
    }

    if (useConfig.getRangeMin() > useConfig.getRangeMax() - useConfig.getCacheSize()) {
      throw new IllegalArgumentException("The cache size is larger than the sequence range");
    }

    if (useConfig.getAutoCommitNoSync()) {
      autoCommitConfig = DbInternal.getDefaultTxnConfig(db.getEnvironment()).clone();
      autoCommitConfig.overrideDurability(Durability.COMMIT_NO_SYNC);
    } else {
      /* Use the environment's default transaction config. */
      autoCommitConfig = null;
    }

    this.db = db;
    this.key = copyEntry(key);
    logger = db.getEnvironment().getEnvironmentImpl().getLogger();

    /* Perform an auto-commit transaction to create the sequence. */
    Locker locker = null;
    Cursor cursor = null;
    OperationStatus status = OperationStatus.NOTFOUND;
    try {
      locker =
          LockerFactory.getReadableLocker(
              db.getEnvironment(),
              txn,
              db.isTransactional(),
              false /*retainNonTxnLocks*/,
              false /*readCommitedIsolation*/);

      cursor = new Cursor(db, locker, null);

      boolean sequenceExists = readData(cursor, null);
      boolean isWritableLocker =
          !db.getConfig().getTransactional()
              || (locker.isTransactional()
                  && !DbInternal.getEnvironmentImpl(db.getEnvironment()).isReplicated());

      if (sequenceExists) {
        if (useConfig.getAllowCreate() && useConfig.getExclusiveCreate()) {
          throw new SequenceExistsException(
              "ExclusiveCreate=true and the sequence record " + "already exists.");
        }
      } else {
        if (useConfig.getAllowCreate()) {
          if (!isWritableLocker) {
            if (cursor != null) {
              cursor.close();
            }
            locker.operationEnd(OperationStatus.SUCCESS);

            locker =
                LockerFactory.getWritableLocker(
                    db.getEnvironment(),
                    txn,
                    db.isTransactional(),
                    false,
                    db.getDatabaseImpl().isReplicated(),
                    autoCommitConfig);
            cursor = new Cursor(db, locker, null);
          }

          /* Get the persistent fields from the config. */
          rangeMin = useConfig.getRangeMin();
          rangeMax = useConfig.getRangeMax();
          increment = !useConfig.getDecrement();
          wrapAllowed = useConfig.getWrap();
          storedValue = useConfig.getInitialValue();

          /*
           * To avoid dependence on SerializableIsolation, try
           * putNoOverwrite first.  If it fails, then try to get an
           * existing record.
           */
          status = cursor.putNoOverwrite(key, makeData());

          if (!readData(cursor, null)) {
            /* A retry loop should be performed here. */
            throw new IllegalStateException("Sequence record removed during openSequence.");
          }
          status = OperationStatus.SUCCESS;
        } else {
          throw new SequenceNotFoundException(
              "AllowCreate=false and the sequence record " + "does not exist.");
        }
      }
    } finally {
      if (cursor != null) {
        cursor.close();
      }
      if (locker != null) {
        locker.operationEnd(status);
      }
    }

    /*
     * cacheLast is initialized such that the cache will be considered
     * empty the first time get() is called.
     */
    cacheSize = useConfig.getCacheSize();
    cacheValue = storedValue;
    cacheLast = increment ? (storedValue - 1) : (storedValue + 1);
  }
Пример #2
0
  /**
   * Returns the next available element in the sequence and changes the sequence value by <code>
   * delta</code>. The value of <code>delta</code> must be greater than zero. If there are enough
   * cached values in the sequence handle then they will be returned. Otherwise the next value will
   * be fetched from the database and incremented (decremented) by enough to cover the <code>delta
   * </code> and the next batch of cached values.
   *
   * <p>This method is synchronized to protect updating of the cached value, since multiple threads
   * may share a single handle. Multiple handles for the same database/key may be used to increase
   * concurrency.
   *
   * <p>The <code>txn</code> handle must be null if the sequence handle was opened with a non-zero
   * cache size.
   *
   * <p>For maximum concurrency, a non-zero cache size should be specified prior to opening the
   * sequence handle, the <code>txn</code> handle should be <code>null</code>, and {@link
   * com.sleepycat.je.SequenceConfig#setAutoCommitNoSync SequenceConfig.setAutoCommitNoSync} should
   * be called to disable log flushes.
   *
   * @param txn For a transactional database, an explicit transaction may be specified, or null may
   *     be specified to use auto-commit. For a non-transactional database, null must be specified.
   * @param delta the amount by which to increment or decrement the sequence
   * @return the next available element in the sequence
   * @throws SequenceOverflowException if the end of the sequence is reached and wrapping is not
   *     configured.
   * @throws SequenceIntegrityException if the sequence record has been deleted.
   * @throws OperationFailureException if one of the <a
   *     href="../je/OperationFailureException.html#writeFailures">Write Operation Failures</a>
   *     occurs.
   * @throws EnvironmentFailureException if an unexpected, internal or environment-wide failure
   *     occurs.
   * @throws IllegalArgumentException if the delta is less than or equal to zero, or larger than the
   *     size of the sequence's range.
   */
  public synchronized long get(Transaction txn, int delta) throws DatabaseException {

    /* Check parameters, being careful of overflow. */
    if (delta <= 0) {
      throw new IllegalArgumentException("Sequence delta must be greater than zero");
    }
    if (rangeMin > rangeMax - delta) {
      throw new IllegalArgumentException("Sequence delta is larger than the range");
    }

    /* Status variables for tracing. */
    boolean cached = true;
    boolean wrapped = false;

    /*
     * Determine whether we have exceeded the cache.  The cache size is
     * always <= Integer.MAX_VALUE, so we don't have to worry about
     * overflow here as long as we subtract the two long values first.
     */
    if ((increment && delta > ((cacheLast - cacheValue) + 1))
        || (!increment && delta > ((cacheValue - cacheLast) + 1))) {

      cached = false;

      /*
       * We need to allocate delta or cacheSize values, whichever is
       * larger, by incrementing or decrementing the stored value by
       * adjust.
       */
      int adjust = (delta > cacheSize) ? delta : cacheSize;

      /* Perform an auto-commit transaction to update the sequence. */
      Locker locker = null;
      Cursor cursor = null;
      OperationStatus status = OperationStatus.NOTFOUND;
      try {
        locker =
            LockerFactory.getWritableLocker(
                db.getEnvironment(),
                txn,
                db.isTransactional(),
                false, // retainNonTxnLocks
                db.getDatabaseImpl().isReplicated(),
                // autoTxnIsReplicated
                autoCommitConfig);

        cursor = new Cursor(db, locker, null);

        /* Get the existing record. */
        readDataRequired(cursor, LockMode.RMW);

        /* If we would have wrapped when not allowed, overflow. */
        if (overflow) {
          throw new SequenceOverflowException("Sequence overflow " + storedValue);
        }

        /*
         * Handle wrapping.  The range size can be larger than a long
         * can hold, so to avoid arithmetic overflow we use BigInteger
         * arithmetic.  Since we are going to write, the BigInteger
         * overhead is acceptable.
         */
        BigInteger availBig;
        if (increment) {
          /* Available amount: rangeMax - storedValue */
          availBig = BigInteger.valueOf(rangeMax).subtract(BigInteger.valueOf(storedValue));
        } else {
          /* Available amount: storedValue - rangeMin */
          availBig = BigInteger.valueOf(storedValue).subtract(BigInteger.valueOf(rangeMin));
        }

        if (availBig.compareTo(BigInteger.valueOf(adjust)) < 0) {
          /* If availBig < adjust then availBig fits in an int. */
          int availInt = (int) availBig.longValue();
          if (availInt < delta) {
            if (wrapAllowed) {
              /* Wrap to the opposite range end point. */
              storedValue = increment ? rangeMin : rangeMax;
              wrapped = true;
            } else {
              /* Signal an overflow next time. */
              overflow = true;
              adjust = 0;
            }
          } else {

            /*
             * If the delta fits in the cache available, don't wrap
             * just to allocate the full cacheSize; instead,
             * allocate as much as is available.
             */
            adjust = availInt;
          }
        }

        /* Negate the adjustment for decrementing. */
        if (!increment) {
          adjust = -adjust;
        }

        /* Set the stored value one past the cached amount. */
        storedValue += adjust;

        /* Write the new stored value. */
        cursor.put(key, makeData());
        status = OperationStatus.SUCCESS;
      } finally {
        if (cursor != null) {
          cursor.close();
        }
        if (locker != null) {
          locker.operationEnd(status);
        }
      }

      /* The cache now contains the range: [cacheValue, storedValue) */
      cacheValue = storedValue - adjust;
      cacheLast = storedValue + (increment ? (-1) : 1);
    }

    /* Return the current value and increment/decrement it by delta. */
    long retVal = cacheValue;
    if (increment) {
      cacheValue += delta;
    } else {
      cacheValue -= delta;
    }

    /* Increment stats. */
    nGets += 1;
    if (cached) {
      nCachedGets += 1;
    }

    /* Trace this method at the FINEST level. */
    if (logger.isLoggable(Level.FINEST)) {
      LoggerUtils.finest(
          logger,
          db.getEnvironment().getEnvironmentImpl(),
          "Sequence.get" + " value=" + retVal + " cached=" + cached + " wrapped=" + wrapped);
    }

    return retVal;
  }