/** * Returns statistical information about the sequence. * * <p>In the presence of multiple threads or processes accessing an active sequence, the * information returned by this method may be out-of-date. * * <p>The getStats method cannot be transaction-protected. For this reason, it should be called in * a thread of control that has no open cursors or active transactions. * * @param config The statistics returned; if null, default statistics are returned. * @return Sequence statistics. * @throws SequenceIntegrityException if the sequence record has been deleted. */ public SequenceStats getStats(StatsConfig config) throws DatabaseException { if (config == null) { config = StatsConfig.DEFAULT; } if (!config.getFast()) { /* * storedValue may have been updated by another handle since it * was last read by this handle. Fetch the last written value. * READ_UNCOMMITTED must be used to avoid lock conflicts. */ Cursor cursor = db.openCursor(null, null); try { readDataRequired(cursor, LockMode.READ_UNCOMMITTED); } finally { cursor.close(); } } StatGroup stats = new StatGroup(SequenceStatDefinition.GROUP_NAME, SequenceStatDefinition.GROUP_DESC); new IntStat(stats, SEQUENCE_GETS, nGets); new IntStat(stats, SEQUENCE_CACHED_GETS, nCachedGets); new IntStat(stats, SEQUENCE_CACHE_SIZE, cacheSize); new LongStat(stats, SEQUENCE_STORED_VALUE, storedValue); new LongStat(stats, SEQUENCE_CACHE_VALUE, cacheValue); new LongStat(stats, SEQUENCE_CACHE_LAST, cacheLast); new LongStat(stats, SEQUENCE_RANGE_MIN, rangeMin); new LongStat(stats, SEQUENCE_RANGE_MAX, rangeMax); SequenceStats seqStats = new SequenceStats(stats); if (config.getClear()) { nGets = 0; nCachedGets = 0; } return seqStats; }
/** * 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; }