/*
* JVSTM: a Java library for Software Transactional Memory
* Copyright (C) 2005 INESC-ID Software Engineering Group
* http://www.esw.inesc-id.pt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Author's contact:
* INESC-ID Software Engineering Group
* Rua Alves Redol 9
* 1000 - 029 Lisboa
* Portugal
*/
package jvstm;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import jvstm.util.Cons;
public class ActiveTransactionsRecord {
/*
* The TxQueueListeners are notified whenever we detect that some
* old values became unreachable. Yet, there is no guarantee
* regarding the order of notification. The same listener may be
* notified simultaneously by several threads; so, the listeners
* must be thread-safe.
*
* As there is no synchronization on the notifications, it may
* happen that a listener is notified of a new oldestTx and only
* later receives the notification of a prior number. Yet,
* whenever it receives a notification, it is guaranteed that
* there is no active transaction older than the number notified.
*/
private final static AtomicReference<Cons<TxQueueListener>> listeners =
new AtomicReference<Cons<TxQueueListener>>(Cons.<TxQueueListener>empty());
public static void addListener(TxQueueListener listener) {
while (true) {
Cons<TxQueueListener> old = listeners.get();
if (listeners.compareAndSet(old, old.cons(listener))) {
return;
}
}
}
public static void removeListener(TxQueueListener listener) {
while (true) {
Cons<TxQueueListener> old = listeners.get();
if (listeners.compareAndSet(old, old.removeFirst(listener))) {
return;
}
}
}
protected static void notifyListeners(int newOldest) {
for (TxQueueListener l : listeners.get()) {
try {
l.noteOldestTransaction(newOldest);
} catch (Throwable t) {
t.printStackTrace();
// don't throw error, or else it would kill the
// current thread because of an error in the client
// listener
}
}
}
public Transaction tx;
/*
* The ActiveTransactionsRecord class was designed as a lock-free data structure. Its goal is
* twofold: 1) to maintain a record of active transactions for the purpose of garbage collecting
* old values; 2) to hold transaction's write-sets to enable future transaction's to validate
* their read state.
*
* It is composed of the following fields:
*/
// the transactionNumber is assigned when the record is created and never changes thereafter its
// value corresponds to the number of a write-transaction that commits this record (an instance
// of this class) is used to record all the running transactions that have this transaction
// number, and that, thus, may need to access the values committed by the transaction that
// created this record
public final int transactionNumber;
/* This is the write-set of the transaction that created this record.
*
* This slot is not final, because it is set to null, in clean(). This is safe, because then,
* no other transaction will ever need to read it. Setting this slot to null helps (a lot!) the
* Java GC.
*/
protected WriteSet writeSet;
// the next field indicates a more recent record (the one that was created immediately after
// this one). This field's AtomicReference starts as null and is assigned only once through a
// compare-and-set. The transaction that succeeds in the CAS operation will be the next one to
// win the race for the commit position.
private final AtomicReference<ActiveTransactionsRecord> next =
new AtomicReference<ActiveTransactionsRecord>(null);
/* This variable needs to be volatile because the beginning of a transaction and the GC
* algorithm triggered by the finish of another transaction, both test this variable.
*/
private volatile boolean recordCommitted = false;
/*
* The next field creates, in effect, a linked list of records,
* with older records pointing to newer records. This linked-list
* of records will allow us to move forward in the cleaning
* process necessary to allow the garbage collection of
* unreachable old-values.
*
* Cheking the commit state of a record is important, because records are enqueued as soon as a
* transaction obtains its commit position. However, that record should only be "seen" by new
* transactions after it has been committed. Given that records are committed "in order", when
* we see an uncommitted record, we know that we should behave as if the remainder of the queue
* didn't exist yet.
*
/* ADDITIONAL EXPLANATION FOR THIS CLASS
*
* Initially, this class was only used in the process of maintaining references to committed
* versions of box's bodies and to clean them when they were no longer necessary. Records were
* created only after a successful commit operation. We realized that, in effect, this class
* implemented a queue of committed transactions. We extended this behaviour to include
* transactions that are in the middle of the commit process. Instances of this class are
* connected as the following example shows:
*
*
* mostRecentCommittedRecord
* |
* v
* +---------+ +---------+ +---------+ +---------+ +---------+
* | #9 | | #10 | | #11 | | #12 | | #13 |
* | | | | | | | | | |
* |COMMITTED| |COMMITTED| | ACTIVE | | ACTIVE | | ACTIVE |
* | | | | | | | | | |
* | next |--> | next |--> | next |--> | next |--> | next |-.
* +---------+ +---------+ +---------+ +---------+ +---------+
* ^ ^
* | |
* activeTxRecord commitTxRecord
*
* Starting from the beginning of the queue and up to a certain point, this list will contain
* only committed transaction records. From that point on, any record will be in an uncommitted
* state. Records are enqueued (through a compare-and-set operation) after "their" transaction
* is validated, thus ensuring a position in the list of successful commits to come. However,
* until that transaction actually commits, this record cannot be seen by newly starting
* transactions.
*
* The static slot mostRecentCommittedRecord of the Transaction class will always point to a
* record that represents a valid committed state.
*
* For each running transaction:
*
* - the activeTxRecord must point to some valid committed tx record. This is the version of
* the world that a transaction will see during its execution.
*
* - the commitTxRecord points to an ActiveTransactionsRecord that is created in the beginning
* of the commit process. This record will eventually be set to committed after the
* transaction's commit operation completes.
*
* This structure enables the validation process to occur for any given transaction that is
* trying to commit, because such transaction can access the write-sets of all the transactions
* that occur(ed) since its beginning until its commit.
*
* Another advantage is that when a transaction tries to commit, if its activeTxRecord is the
* last in line (next is null), then the transaction knows that nothing as changed and can
* immediately commit without the need to validate.
*/
// private method to be used only when instantiating the sentinel record
private ActiveTransactionsRecord() {
this.transactionNumber = 1;
this.writeSet = WriteSet.empty();
this.recordCommitted = true;
this.tx = new TopLevelReadTransaction(this);
}
private static boolean sentinelRecordCreated = false;
protected static synchronized ActiveTransactionsRecord makeSentinelRecord() {
if (sentinelRecordCreated) {
throw new Error("ActiveTransactionsRecord::makeSentinelRecord() invoked more than once!");
}
sentinelRecordCreated = true;
return new ActiveTransactionsRecord();
}
public ActiveTransactionsRecord(int txNumber, WriteSet writeSet) {
this.transactionNumber = txNumber;
this.writeSet = writeSet;
this.tx = new TopLevelReadTransaction(this);
}
public ActiveTransactionsRecord getNext() {
return next.get();
}
/**
* Sets the next record iff there was no next record already set
*
* @return <code>true</code> if the next was successfully set. <code>false</code> if there was
* another record already set as next
*/
public boolean trySetNext(ActiveTransactionsRecord next) {
return this.next.compareAndSet(null, next);
}
protected void setCommitted() {
this.recordCommitted = true;
}
public boolean isCommitted() {
return this.recordCommitted;
}
public WriteSet getWriteSet() {
return this.writeSet;
}
public void clean() {
int nBlocks = this.writeSet.normalWriteSet.nBlocks;
int blockIdx = 0, idx = 0;
for (Cons<GarbageCollectable> bodiesPerBlock : this.writeSet.normalWriteSet.bodiesPerBlock) {
for (GarbageCollectable body : bodiesPerBlock) {
body.clearPrevious();
if(REVERSION && blockIdx < nBlocks){
VBox vbox = this.writeSet.normalWriteSet.allWrittenVBoxes[idx];
tryRevert(vbox, body);
idx++;
}
}
blockIdx++;
}
nBlocks = this.writeSet.perTxBoxesWriteSet.nBlocks;
blockIdx = 0;
idx = 0;
for (Cons<GarbageCollectable> bodiesPerBlock : this.writeSet.perTxBoxesWriteSet.bodiesPerBlock) {
for (GarbageCollectable body : bodiesPerBlock) {
body.clearPrevious();
if(REVERSION && blockIdx < nBlocks){
VBox vbox = this.writeSet.perTxBoxesWriteSet.allWrittenVBoxes[idx];
tryRevert(vbox, body);
idx++;
}
}
blockIdx++;
}
writeSet = null; // this is helpful for the GC. verified by experimentation
notifyListeners(transactionNumber);
}
/*===========================================================================*
*~~~~~~~~~~~~~ REVERSION part of the AOM approach ~~~~~~~~~~~~~~~~~~~~~*
*===========================================================================*/
static final String REVERSION_PROP = "jvstm.aom.reversion";
static final boolean REVERSION;
// public static int nrOfCleans = 0;
public static int nrOfReversions = 0;
public static int nrOfTries = 0;
static{
Logger logger = Logger.getLogger("jvstm");
REVERSION = Boolean.getBoolean(REVERSION_PROP);
logger.info(String.format(
"********** AOM reversion = %b (disable/enable it in property %s)",
REVERSION,
REVERSION_PROP));
}
/**
* If vbodies' history only have one VBoxBody then we will try to revert it.
*
* The reversion process first acquires the object’s monitor and then executes the following
* 3 steps (that are marked in the code bellow):
*
* - (1) read the head of the object’s history and check that it is the VBoxBody that was
* just trimmed (the body argument), executing the following two steps only if it is;
*
* - (2) copy the values of the last committed body to the corresponding fields in the object.
* This copy is performed by the toCompactLayout method.
*
* - (3) do an atomic compare and swap to change the value of the object’s header from its
* previously read value to null.
*
* To understand why our current approach is correct, note that when we revert an object,
* we do not change the instances of VBoxBody that represent the object’s history; we
* simply set the body field of the reverted object to null.
* Moreover, the old object’s history is still valid, in the sense that it represents
* correct information about the committed values for the object; it just happens
* that it is not needed anymore.
*
* Correctness of the Reversion:
*
* Imagine that one thread Tw is at commit phase and writing back to an object concurrently
* with another thread Tr that is trying to revert it.
* In this scenario consider the following interleaves:
*
* - Tw -> Tr(1):
*
* If Tw finishes all its steps before Tr reads the head of the object’s history, then Tr
* will refuse to revert the object because it will see in the head of the history a different body.
*
* - Tr(3) -> Tw:
*
* If Tr is able to run to completion before Tw reads the history’s head, then Tw will see
* the object in the compact layout and will extend it as usual (See VBoxAom::commit method).
*
* - Tw -> Tr(2/3):
*
* If Tr sees the expected value at the history’s head, and Tw sees that same value, because
* it reads it before Tr sets it to null. In this case, Tw decides that it does not
* need to extend the object (because the object is already in the extended layout)
* and simply adds another version at the head of the current history and sets the
* corresponding body field in the vbox object; this will be done independently of what Tr does.
* If Tw finishes first, the CAS of Tr will fail (step 3), leaving the object in the correct state
* (in the extended layout with the new value at the beginning of its history - the value written by Tw).
*
* - Tw(1) -> Tr -> T2(2)
*
* If both Tr and Tw both see the object extended and Tr sees the expected value at the history’s
* head and finishes before Tw, then Tr successfully converts the object to the compact layout and
* after that the Tw overwrites the body field with the new history, effectively turning the object
* into the extended layout again.
*
*/
private <T extends VBox<T>> boolean tryRevert(VBox<T> vbox, GarbageCollectable body){
synchronized (vbox){
if(vbox.body == body /* (1) step one of the reversion */
// && (Transaction.mostRecentRecord.transactionNumber - body.version) >= 8
// && (currentOwner.version != 0 && currentOwner.version <= this.transactionNumber)
){
nrOfTries++;
vbox.toCompactLayout(((VBoxBody<T>)body).value); /* (2) step two of the reversion */
boolean res = UtilUnsafe.UNSAFE.compareAndSwapObject(vbox, VBox.Offsets.bodyOffset, body, null); /* (3) step three of the reversion */
if(res) nrOfReversions++;
return res;
}
}
return false;
}
}