/**
* Copyright 2011-2012 Akiban Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.persistit;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.persistit.Accumulator.Delta;
class TransactionStatus {
/**
* Distinguished commit timestamp for any value that has become primordial,
* meaning that there are no longer any active concurrent transactions.
*/
final static long PRIMORDIAL = 0;
/**
* Distinguished commit timestamp for any value created by a transaction
* which subsequently aborted.
*/
final static long ABORTED = Long.MIN_VALUE;
/**
* Distinguished commit timestamp for any value created by a transaction
* that is still running.
*/
final static long UNCOMMITTED = Long.MAX_VALUE;
/**
* Distinguished synthetic timestamp signifying that a thread waiting for a
* stable result timed out.
*/
final static long TIMED_OUT = Long.MIN_VALUE + 1;
/**
* The bucket to which this <code>TransactionStatus</code> belongs.
*/
private final TransactionIndexBucket _bucket;
/**
* Start timestamp. This value may only be assigned by
* {@link TransactionIndex#registerTransaction(Transaction)}.
*/
private volatile long _ts;
/**
* Commit timestamp and status. Value is one of
* <dl>
* <dt>PRIMORDIAL</dt>
* <dd>The transaction committed long ago, meaning before any concurrent
* activity. A TrasactionStatus will never have this value, but it is used
* in other places to communicate the fact that a transaction committed
* before any recent history.</dd>
* <dt>ABORTED</dt>
* <dd>the transaction aborted.</dd>
* <dt>UNCOMMITTED</dt>
* <dd>The transaction is running and has not yet asked to commit.</dd>
* <dt>v < 0</dt>
* <dd>Any negative value other than Long.MIN_VALUE indicates the timestamp
* value at which the transaction started to commit; the commit process is
* incomplete.</dd>
* <dt>v > 0</dt>
* <dd>Any positive value other than Long.MAX_VALUE indicates the commit
* timestamp; the transaction commit is complete.</dd>
* </dl>
*/
private volatile long _tc;
/**
* Timestamp at which the last MMV version written by this transaction has
* been pruned (if the transaction aborted). The
* <code>TransactionStatus</code> may not be removed until there are no
* currently active transactions have start timestamps older than this
* value.
*/
private volatile long _ta;
/**
* Count of MVV versions created by associated transaction.
*/
private AtomicInteger _mvvCount = new AtomicInteger();
/**
* Semaphore used to manage ww dependencies. An attempt to update an MVV
* that already contains a value version from a concurrently executing
* transaction must wait for that other transaction to commit or abort. The
* protocol uses a Semaphore rather than a ReentrantLock because it may be
* acquired in one thread but released in another.
*/
private final Semaphore _wwLock = new Semaphore(1);
/**
* Pointer to next member of singly-linked list.
*/
private TransactionStatus _next;
/**
* Pointer to TransactionStatus on which we intend to claim a permit. (For
* deadlock detection.)
*/
private volatile TransactionStatus _depends;
/**
* Pointer to first member of a list of Delta instances contributed through
* the associated transaction.
*/
private volatile Delta _delta;
/**
* Indicates whether the transaction has called
* {@link TransactionIndexBucket#notifyCompleted(long)}. Until then the
* <code>TransactionStatus</code> may not be placed on the free list.
*/
private volatile boolean _notified;
TransactionStatus(final TransactionIndexBucket bucket) {
_bucket = bucket;
}
/**
* Constructs a partial copy. Used only in diagnostic code.
*
* @param status
*/
TransactionStatus(final TransactionStatus status) {
this._bucket = status._bucket;
this._mvvCount = status._mvvCount;
this._notified = status._notified;
this._ta = status._ta;
this._tc = status._tc;
this._ts = status._ts;
}
/**
* @return The next TransactionStatus on linked list, or <code>null</code>
* if there is none
*/
TransactionStatus getNext() {
return _next;
}
/**
* Link another TransactionStatus to this one.
*
* @param next
* the TransactionStatus to link
*/
void setNext(final TransactionStatus next) {
_next = next;
}
/**
* @return The next TransactionStatus on dependency linked list, or
* <code>null</code> if there is none
*/
TransactionStatus getDepends() {
return _depends;
}
/**
* Link another TransactionStatus this one depends on.
*
* @param next
* the TransactionStatus to link
*/
void setDepends(final TransactionStatus depends) {
_depends = depends;
}
/**
*
* @return The associated transaction's start timestamp
*/
long getTs() {
return _ts;
}
/**
* @return the commit status of the associated transaction.
*/
long getTc() {
return _tc;
}
/**
* @return the abort cleanup timestamp
*/
long getTa() {
return _ta;
}
/**
* @return whether the {@link TransactionIndexBucket#notifyCompleted(long)}
* has been called.
*/
boolean isNotified() {
return _notified;
}
/**
* Start commit processing. This method leaves the
* <code>TransactionStatus</code> in a state indicating commit processing is
* underway. The {@link #commit(long)} method completes the process. Note
* that until we implement SSI this method is unnecessary, but is included
* so that unit tests can test the interim state.
*
* @param timestamp
*/
void commit(final long timestamp) {
if (timestamp < _ts || timestamp == UNCOMMITTED) {
throw new IllegalArgumentException("Attempt to commit before start: " + this);
}
if (_tc != UNCOMMITTED) {
throw new IllegalArgumentException("Already committed or aborted: " + this);
}
_tc = -timestamp;
}
void abort() {
if (_tc != UNCOMMITTED) {
throw new IllegalArgumentException("Already committed or aborted: " + this);
}
_tc = ABORTED;
}
void complete(final long timestamp) {
if (_tc > 0 || -_tc > timestamp && timestamp != ABORTED) {
throw new IllegalStateException("Transaction not ready to complete: " + this);
}
if (_tc < 0 && _tc != ABORTED) {
_tc = timestamp;
}
_notified = true;
}
void completeAndUnlock(final long timestamp) {
complete(timestamp);
wwUnlock();
}
Delta getDelta() {
return _delta;
}
void addDelta(final Delta delta) {
delta.setNext(_delta);
_delta = delta;
}
Delta takeDelta() {
final Delta delta = _delta;
_delta = null;
return delta;
}
long accumulate(final long value, final Accumulator accumulator, final int step) {
long result = value;
for (Delta delta = _delta; delta != null; delta = delta.getNext()) {
if (delta.getAccumulator() == accumulator && delta.getStep() < step) {
result = accumulator.applyValue(result, delta.getValue());
}
}
return result;
}
/**
* Increment the count of MVV modifications made by the associated
* transaction. This is done each time a transaction modifies a value. The
* counter is used to determine when all values modified by an aborted
* transaction have been pruned.
*
* @return the updated count
*/
int incrementMvvCount() {
_ta = Long.MAX_VALUE;
return _mvvCount.incrementAndGet();
}
/**
* Decrement the count of MVV modifications made by the associated
* transaction. This is done each time a version is removed by pruning. When
* the count becomes zero, then if the associated transaction aborted its
* TransactionStatus can be removed from the abort set.
*
* @return the updated count
*/
int decrementMvvCount() {
assert _tc == ABORTED : "can only decrement MVVs for an aborted transaction";
final int count = _mvvCount.decrementAndGet();
assert count >= 0 : "mvvCount is negative";
if (count == 0) {
_ta = _bucket.getTimestampAllocator().getCurrentTimestamp();
}
return count;
}
/**
* @return The count of MVV modifications made by the associated
* transaction.
*/
int getMvvCount() {
return _mvvCount.get();
}
/**
* Sets the MVV modification count. In recovery, this is initially set to
* Integer.MAX_VALUE to prevent pruning code from removing aborted
* transactions from the abort set prematurely. Note that we do not attempt
* to recover an accurate count after a crash.
*/
void setMvvCount(final int count) {
_mvvCount.set(count);
}
/**
* Block briefly until another thread transiently holding the wwLock
* vacates.
*
* @param timeout
* in milliseconds
* @throws InterruptedException
*/
void briefLock(final long timeout) throws InterruptedException {
boolean locked = false;
try {
locked = wwLock(timeout);
} catch (final InterruptedException ie) {
Thread.currentThread().interrupt();
} finally {
if (locked) {
wwUnlock();
}
}
}
/**
* <p>
* Acquire a permit on this TransactionStatus. This supports the
* {@link TransactionIndex#wwDependency(long, long, long)} method. The
* permit is acquired when the transaction is registered (see
* {@link TransactionIndex#registerTransaction(Transaction)} and released
* once the transaction is either committed or aborted.
* </p>
* <p>
* The <code>wwDependency</code> method also attempts to acquire, and then
* immediately release this permit. This stalls the thread calling
* wwDependency until the commit/abort status of the current transaction is
* known.
*
* @param timeout
* @return
* @throws InterruptedException
*/
boolean wwLock(final long timeout) throws InterruptedException {
return _wwLock.tryAcquire(timeout, TimeUnit.MILLISECONDS);
}
/**
* Release the permit acquired by {@link #wwLock(long)}.
*/
void wwUnlock() {
_wwLock.release();
}
/**
* Indicate whether this TransactionStatus has been locked. Tested by assert
* statements in various places.
*
* @return true if a thread has acquired a claim on this TransactionStatus.
*/
boolean isLocked() {
return _wwLock.availablePermits() == 0;
}
/**
* Initialize this <code>TransactionStatus</code> instance for a new
* transaction.
*
* @param ts
* Start time of this status.
*/
void initialize(final long ts) {
_ts = ts;
_tc = UNCOMMITTED;
_ta = PRIMORDIAL;
_next = null;
_delta = null;
_mvvCount.set(0);
_notified = false;
}
/**
* Initialize this <code>TransactionStatus</code> instance for an artificial
* transaction known to be aborted. The initial state is aborted, infinite
* MVV count, and notified.
*
* @param ts
* Start time of this status.
*/
void initializeAsAborted(final long ts) {
initialize(ts);
abort();
setMvvCount(Integer.MAX_VALUE);
_notified = true;
}
@Override
public String toString() {
return String.format("<ts=%,d tc=%s mvv=%,d>", _ts, tcString(_tc), _mvvCount.get());
}
static String versionString(final long version) {
final StringBuilder sb = new StringBuilder();
sb.append(String.format("%,d", TransactionIndex.vh2ts(version)));
final int step = TransactionIndex.vh2step(version);
if (step > 0) {
sb.append(String.format("#%02d", step));
}
return sb.toString();
}
static String tcString(final long ts) {
if (ts == ABORTED) {
return "ABORTED";
} else if (ts == UNCOMMITTED) {
return "UNCOMMITTED";
} else {
return String.format("%,d", ts);
}
}
}