package org.deuce.transaction.estmmvcc;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
import org.deuce.transaction.TransactionException;
import org.deuce.transaction.estmmvcc.field.ReadFieldAccess.Field;
import org.deuce.transaction.estmmvcc.field.ReadFieldAccess.Field.Type;
import org.deuce.transaction.estmmvcc.ReadSet;
import org.deuce.transaction.estmmvcc.WriteSet;
import org.deuce.transform.Exclude;
import contention.benchmark.Statistics;
/**
* E-STM w/ MVCC and snapshot
* An STM implementing regular and elastic transactions
* and using Multi-Version Concurrency Control
*
* See the companion papers:
* Reusable Concurrent Data Types [ECOOP '14]
* Democratizing Transactional Progrmming [CACM '14]
*
* @author Vincent Gramoli
*/
@Exclude
final public class Context implements org.deuce.transaction.Context {
/** Type of the tx, (!elastic) means regular type */
private boolean elastic;
/** roregular means read-only regular type (atomic snapshot alike) */
private boolean roregular = false;
/** The time lower bound above which the tx can be serialized */
private int lb;
/** The time upper bound below which the tx can be serialized */
private int ub;
/**
* The transaction exceptions indicating that the current transaction
* must be restarted
*/
final private static TransactionException BETWEEN_SUCCESSIVE_READS_EXCEPTION =
new TransactionException("Fail to read successively.");
final private static TransactionException BETWEEN_READ_AND_WRITE_EXCEPTION =
new TransactionException("Fail to read successively.");
final private static TransactionException EXTEND_ON_READ_EXCEPTION =
new TransactionException("Fail to extend (regular mode).");
final private static TransactionException WRITE_AFTER_READ_EXCEPTION =
new TransactionException("Fail to lock (already read value has changed).");
final private static TransactionException LOCKED_BEFORE_READ_EXCEPTION =
new TransactionException("Fail before reading (already locked by other).");
final private static TransactionException LOCKED_BEFORE_ELASTIC_READ_EXCEPTION =
new TransactionException("Fail while reading (already locked by other).");
final private static TransactionException LOCKED_ON_READ_EXCEPTION =
new TransactionException("Fail while reading (already locked by other).");
final private static TransactionException LOCKED_ON_WRITE_EXCEPTION =
new TransactionException("Fail to write (already locked by other).");
final private static TransactionException INVALIDATED_SNASPHOT_EXCEPTION =
new TransactionException("Fail to write (already locked by other).");
/** Unique monotonically increasing transaction identifier */
final private static AtomicInteger clock = new AtomicInteger(0);
private static final AtomicInteger threadIdCounter = new AtomicInteger(1);
private final int threadId = threadIdCounter.getAndIncrement();
/**
* The last-read-entry set contains up to k=2 entries.
*
* This is a hack of the C-based E-STM to check that the previous read is not
* being made inconsistent by the garbage collector.
*/
final private LastReadEntries lreSet = new LastReadEntries();
/** Read set, used by any regular reads or after an elastic write */
final private ReadSet readSet = new ReadSet(1024);
/** Write set, usual redo-log */
final private WriteSet writeSet = new WriteSet(32);
private int attempts=0;
//private final Statistics stats = new Statistics(threadId);
private static final Context[] threads = new Context[256];
private int readHash;
private int readLock;
private Object readValue;
public Context() {
// Unique identifier among active threads
threads[threadId] = this;
}
/**
* The begin delimiter of the transaction
*/
public void init(int blockId, String metainf) {
elastic = (metainf.indexOf("elastic") != -1);
if (!elastic) roregular = (metainf.indexOf("roregular") != -1);
writeSet.clear();
readSet.clear();
lreSet.clear();
lb = ub = clock.get();
// stats
//this.stats.reportTxStart();
//this.attempts++;
}
/**
* Gets the Id of this thread
* @return Id of this thread
*/
public final int getThreadId() {
return threadId;
}
public final Statistics getStatistics() {
return null;//stats;
}
/* ---------------- TM interface -------------- */
/**
* The end delimiter of the transaction
*/
public boolean commit() {
if (!writeSet.isEmpty()) {
int newClock = clock.incrementAndGet();
if (newClock != lb + 1 && !readSet.validate(threadId)) {
rollback();
//this.stats.reportAbort(AbortType.INVALID_COMMIT);
return false;
}
// Write values and release locks
writeSet.commit(newClock, threadId);
}
//this.stats.reportCommit(attempts);
//if (elastic) { this.stats.reportCommit(CommitType.ELASTIC); }
//else if (roregular) { this.stats.reportCommit(CommitType.READONLY); }
//else { this.stats.reportCommit(CommitType.UPDATE); }
//this.stats.reportOnCommit(readSet.size(), writeSet.size());
return true;
}
/**
* Call upon abort
*/
public void rollback() {
// Release locks
writeSet.rollback();
}
/**
* Extend the time interval [lb; ub]
*/
private boolean extend() {
final int now = clock.get();
if (readSet.validate(threadId)) {
ub = now;
return true;
}
return false;
}
public void beforeReadAccess(Object obj, long field) {
readHash = LockTable.hash(obj, field);
// Check if the field is locked (may throw an exception)
readLock = LockTable.checkLock(readHash, threadId);
if (readLock == -2) {
if (elastic) {
//this.stats.reportAbort(AbortType.LOCKED_BEFORE_ELASTIC_READ);
throw LOCKED_BEFORE_ELASTIC_READ_EXCEPTION;
} else {
//this.stats.reportAbort(AbortType.LOCKED_BEFORE_READ);
throw LOCKED_BEFORE_READ_EXCEPTION;
}
}
}
/**
* Upon reading
*
* @param obj the object of the field
* @param field the field to access
* @param type the type
* @return
*/
private boolean onReadAccess(Object obj, long field, Type type) {
if (readLock < 0) {
// We already own that lock
Object v = writeSet.getValue(readHash, obj, field);
if (v == null)
return false;
readValue = v;
return true;
}
boolean b = false;
while (true) {
// check timestamp
while (readLock <= ub) {
// check version value version
int lock = LockTable.checkLock(readHash, threadId);
if (lock == -2) {
//stats.reportAbort(AbortType.LOCKED_ON_READ);
throw LOCKED_ON_READ_EXCEPTION;
}
if (lock != readLock) {
readLock = lock;
readValue = Field.getValue(obj, field, type);
b = true;
continue;
}
// We have read a valid value (in snapshot)
// Save to read set
if (elastic && writeSet.isEmpty()) lreSet.add(obj, field, readHash, lock);
else readSet.add(obj, field, readHash, lock);
return b;
}
if (elastic && writeSet.isEmpty()) { // elastic transaction
// check if last read entries have been updated
if (!lreSet.validate(threadId, ub)) {
//stats.reportAbort(AbortType.BETWEEN_SUCCESSIVE_READS);
throw BETWEEN_SUCCESSIVE_READS_EXCEPTION;
}
ub = readLock;
return b;
} else if (roregular) { // regular read-only transaction
int oldVersion = BackUpTable.getVersion(obj, field);
/*if (oldVersion < 0) {
stats.reportAbort(AbortType.INVALID_SNAPSHOT);
throw INVALIDATED_SNASPHOT_EXCEPTION;
}*/
if (oldVersion <= ub && oldVersion != -1) {
if ((readValue = BackUpTable.getValue(obj, field)) != null) {
lb = (oldVersion > lb ? oldVersion : lb); // update lower bound
return true;
}
}
//stats.reportAbort(AbortType.INVALID_SNAPSHOT);
throw INVALIDATED_SNASPHOT_EXCEPTION;
} else { // regular update transaction
// Try to extend snapshot
if (!extend()) {
// stats
//stats.reportAbort(AbortType.EXTEND_ON_READ);
throw EXTEND_ON_READ_EXCEPTION;
}
}
}
}
private void onWriteAccess(Object obj, long field, Object value, Type type) {
int hash = LockTable.hash(obj, field);
int timestamp = LockTable.lock(hash, threadId);
if (timestamp == -2) {
//stats.reportAbort(AbortType.LOCKED_ON_WRITE);
throw LOCKED_ON_WRITE_EXCEPTION;
}
if (timestamp < 0) {
// We already own that lock
writeSet.append(hash, obj, field, value, type);
return;
}
if (timestamp > ub) {
// Handle write-after-read
if ((elastic && lreSet.contains(obj, field)) || readSet.contains(obj, field)) {
// Abort
LockTable.setAndReleaseLock(hash, timestamp);
// stats
//stats.reportAbort(AbortType.WRITE_AFTER_READ);
throw WRITE_AFTER_READ_EXCEPTION;
}
}
// Additional validation
if (elastic && !lreSet.validate(threadId, ub)) {
LockTable.setAndReleaseLock(hash, timestamp);
// stats
//stats.reportAbort(AbortType.BETWEEN_READ_AND_WRITE);
throw BETWEEN_READ_AND_WRITE_EXCEPTION;
}
if (elastic && !lreSet.isEmpty()) {
readSet.copy(lreSet);
lreSet.clear();
}
// Add to write set
writeSet.add(hash, obj, field, value, type, timestamp);
}
public Object onReadAccess(Object obj, Object value, long field) {
return (onReadAccess(obj, field, Type.OBJECT) ? readValue : value);
}
public boolean onReadAccess(Object obj, boolean value, long field) {
return (onReadAccess(obj, field, Type.BOOLEAN) ? (Boolean) readValue : value);
}
public byte onReadAccess(Object obj, byte value, long field) {
return (onReadAccess(obj, field, Type.BYTE) ? ((Number) readValue).byteValue() : value);
}
public char onReadAccess(Object obj, char value, long field) {
return (onReadAccess(obj, field, Type.CHAR) ? (Character) readValue : value);
}
public short onReadAccess(Object obj, short value, long field) {
return (onReadAccess(obj, field, Type.SHORT) ? ((Number) readValue).shortValue() : value);
}
public int onReadAccess(Object obj, int value, long field) {
return (onReadAccess(obj, field, Type.INT) ? ((Number) readValue).intValue() : value);
}
public long onReadAccess(Object obj, long value, long field) {
return (onReadAccess(obj, field, Type.LONG) ? ((Number) readValue).longValue() : value);
}
public float onReadAccess(Object obj, float value, long field) {
return (onReadAccess(obj, field, Type.FLOAT) ? ((Number) readValue).floatValue() : value);
}
public double onReadAccess(Object obj, double value, long field) {
return (onReadAccess(obj, field, Type.DOUBLE) ? ((Number) readValue).doubleValue() : value);
}
public void onWriteAccess(Object obj, Object value, long field) {
onWriteAccess(obj, field, value, Type.OBJECT);
}
public void onWriteAccess(Object obj, boolean value, long field) {
onWriteAccess(obj, field, (Object) value, Type.BOOLEAN);
}
public void onWriteAccess(Object obj, byte value, long field) {
onWriteAccess(obj, field, (Object) value, Type.BYTE);
}
public void onWriteAccess(Object obj, char value, long field) {
onWriteAccess(obj, field, (Object) value, Type.CHAR);
}
public void onWriteAccess(Object obj, short value, long field) {
onWriteAccess(obj, field, (Object) value, Type.SHORT);
}
public void onWriteAccess(Object obj, int value, long field) {
onWriteAccess(obj, field, (Object) value, Type.INT);
}
public void onWriteAccess(Object obj, long value, long field) {
onWriteAccess(obj, field, (Object) value, Type.LONG);
}
public void onWriteAccess(Object obj, float value, long field) {
onWriteAccess(obj, field, (Object) value, Type.FLOAT);
}
public void onWriteAccess(Object obj, double value, long field) {
onWriteAccess(obj, field, (Object) value, Type.DOUBLE);
}
@Exclude
public static class LockTable {
final private static int ARRAYSIZE = 1 << 20; // 2^20
final private static int MASK = ARRAYSIZE - 1;
final private static int LOCK = 1 << 31;
final private static int IDMASK = LOCK - 1;
// Array of 32-bit lock words
final private static AtomicIntegerArray locks = new AtomicIntegerArray(ARRAYSIZE);
public static int lock(int hash, int id) {
assert hash <= MASK;
while (true) {
int lock = locks.get(hash);
if ((lock & LOCK) != 0) {
if ((lock & IDMASK) != id) {
// Already locked by other thread
return -2;
} else {
// We already own this lock
return -1;
}
}
if (locks.compareAndSet(hash, lock, id | LOCK)) {
// Return old timestamp (lock bit is not set)
return lock;
}
}
}
public static int checkLock(int hash, int id) {
assert hash <= MASK;
int lock = locks.get(hash);
if ((lock & LOCK) != 0) {
if ((lock & IDMASK) != id) {
// Already locked by other thread
return -2;
} else {
// We already own this lock
return -1;
}
}
// Return old timestamp (lock bit is not set)
return lock;
}
public static void setAndReleaseLock(int hash, int lock) {
assert hash <= MASK;
locks.set(hash, lock);
}
public static int hash(Object obj, long field) {
int hash = System.identityHashCode(obj) + (int) field;
return hash & MASK;
}
}
@Override
public void beforeReadAccessStrongIso(Object obj, long field, Object obj2,
long fieldObj) {
// TODO Auto-generated method stub
}
@Override
public void onWriteAccessStrongIso(Object obj, Object value, long field,
long fieldObj) {
// TODO Auto-generated method stub
}
@Override
public void onWriteAccessStrongIso(Object obj, boolean value, long field,
long fieldObj) {
// TODO Auto-generated method stub
}
@Override
public void onWriteAccessStrongIso(Object obj, byte value, long field,
long fieldObj) {
// TODO Auto-generated method stub
}
@Override
public void onWriteAccessStrongIso(Object obj, char value, long field,
long fieldObj) {
// TODO Auto-generated method stub
}
@Override
public void onWriteAccessStrongIso(Object obj, short value, long field,
long fieldObj) {
// TODO Auto-generated method stub
}
@Override
public void onWriteAccessStrongIso(Object obj, int value, long field,
long fieldObj) {
// TODO Auto-generated method stub
}
@Override
public void onWriteAccessStrongIso(Object obj, long value, long field,
long fieldObj) {
// TODO Auto-generated method stub
}
@Override
public void onWriteAccessStrongIso(Object obj, float value, long field,
long fieldObj) {
// TODO Auto-generated method stub
}
@Override
public void onWriteAccessStrongIso(Object obj, double value, long field,
long fieldObj) {
// TODO Auto-generated method stub
}
}