package org.deuce.transaction.estm; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerArray; import org.deuce.transaction.TransactionException; import org.deuce.transaction.estm.field.ReadFieldAccess.Field; import org.deuce.transaction.estm.field.ReadFieldAccess.Field.Type; import org.deuce.transaction.estm.ReadSet; import org.deuce.transaction.estm.WriteSet; import org.deuce.transform.Exclude; /** * E-STM * An STM implementing regular and elastic transactions. * * See the companion paper, Elastic Transactions [DISC '09] * * * @author Vincent Gramoli */ @Exclude final public class Context implements org.deuce.transaction.Context { /** Type of the tx, (!elastic) means regular type */ private boolean elastic; /** 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; 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 WRITE_FAILURE_EXCEPTION = new TransactionException("Fail on write (read previous version)."); final private static TransactionException EXTEND_FAILURE_EXCEPTION = new TransactionException("Fail on extend (regular mode)."); final private static AtomicInteger clock = new AtomicInteger(0); final private static AtomicInteger threadID = new AtomicInteger(0); /** * The last-read-entries 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 readHash; private int readLock; private Object readValue; private int id; public Context() { // Unique identifier among active threads id = threadID.incrementAndGet(); } /** * The begin delimiter of the transaction */ public void init(int blockId, String metainf) { elastic = (metainf.indexOf("elastic") != -1); writeSet.clear(); readSet.clear(); lreSet.clear(); lb = ub = clock.get(); } /** * The end delimiter of the transaction */ public boolean commit() { if (!writeSet.isEmpty()) { int newClock = clock.incrementAndGet(); if (newClock != lb + 1 && !readSet.validate(id)) { rollback(); return false; } // Write values and release locks writeSet.commit(newClock); } 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(id)) { 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, id); } /** * 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, id); 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; } // Partial validation if (elastic && writeSet.isEmpty()) { // check if last read entries have been updated if (!lreSet.validate(id, ub)) throw BETWEEN_SUCCESSIVE_READS_EXCEPTION; ub = readLock; return b; } else { // Try to extend snapshot if (!extend()) { throw EXTEND_FAILURE_EXCEPTION; } } } } /** * Upon writing * * @param obj the object of the field * @param field the field to access * @param type the type * @return */ private void onWriteAccess(Object obj, long field, Object value, Type type) { int hash = LockTable.hash(obj, field); // Lock entry (might throw an exception) int timestamp = LockTable.lock(hash, id); 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); throw WRITE_FAILURE_EXCEPTION; } // We delay validation until later (although we could already validate once here) } // Additional validation if (elastic && !lreSet.validate(id, ub)) { LockTable.setAndReleaseLock(hash, timestamp); 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); } @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 } @Exclude static public class LockTable { // Failure transaction final private static TransactionException FAILURE_EXCEPTION = new TransactionException("Fail on lock (already locked)."); 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) throws TransactionException { assert hash <= MASK; while (true) { int lock = locks.get(hash); if ((lock & LOCK) != 0) { if ((lock & IDMASK) != id) { // Already locked by other thread throw FAILURE_EXCEPTION; } 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) throws TransactionException { assert hash <= MASK; int lock = locks.get(hash); if ((lock & LOCK) != 0) { if ((lock & IDMASK) != id) { // Already locked by other thread throw FAILURE_EXCEPTION; } 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; } } }