/*
* 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.gc;
// import java.util.concurrent.Executors;
// import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import jvstm.ActiveTransactionsRecord;
import jvstm.Transaction;
public class GCTask implements Runnable {
private ActiveTransactionsRecord lastCleanedRecord;
private ThreadPoolExecutor cleanersPool = makeCleanersPool();
private static ThreadPoolExecutor makeCleanersPool() {
ThreadFactory fact = new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}};
ThreadPoolExecutor executor = null;
int poolSize = Runtime.getRuntime().availableProcessors() / 10 + 1;
// if (poolSize > 40) {
// poolSize = 40;
// }
// poolSize = 5;
executor = new ThreadPoolExecutor(poolSize, poolSize, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), fact);
executor.allowCoreThreadTimeOut(true);
return executor;
}
public GCTask(ActiveTransactionsRecord lastCleanedRecord) {
this.lastCleanedRecord = lastCleanedRecord;
}
public void run() {
while(true) {
try {Thread.sleep(500);} catch (Exception e) {}
ActiveTransactionsRecord rec = findOldestRecordInUse();
/* take your pick: either launch a Task to clean each record or launch a task to clean a sequence of records... */
//cleanUnusedRecords(rec);
if (rec.transactionNumber > this.lastCleanedRecord.transactionNumber) {
cleanersPool.execute(new MultipleCleanTask(this.lastCleanedRecord, rec));
this.lastCleanedRecord = rec;
}
}
}
/**
* This method is used for unit tests purpose to force GC running and
* convert objects to the CompactLayout, when using the AOM approach.
* In this case we should also have disabled the previous asynchronous
* task through the VM property: -Djvstm.gc.disabled=true
*/
public void runGc(){
ActiveTransactionsRecord rec = findOldestRecordInUse();
if (rec.transactionNumber > this.lastCleanedRecord.transactionNumber) {
new MultipleCleanTask(this.lastCleanedRecord, rec).run();
this.lastCleanedRecord = rec;
}
}
// used to pass state between two calls of findOldestRecordUpTo()
private TxContext oldestContext = null;
private ActiveTransactionsRecord findOldestRecordInUse() {
// We use this in case there are no thread running, to know until where to clean. If we
// only read this after doing the search we might clean more than we should, because a new
// transaction can begin and commit a new record at any time. By reading first, we ensure
// that if all threads have gone, we can clean at least until here.
ActiveTransactionsRecord mostRecentCommittedAtBegin = Transaction.mostRecentCommittedRecord;
for (ActiveTransactionsRecord next = mostRecentCommittedAtBegin.getNext();
(next != null) && next.isCommitted(); next = next.getNext()) {
mostRecentCommittedAtBegin = next;
} // we could use this opportunity to advance Transaction.mostRecentCommittedRecord
// First pass. Here we check all contexts to identify the oldest record in use.
ActiveTransactionsRecord minRequiredRecord1 = findOldestRecordUpTo(null, Integer.MAX_VALUE);
// If there was no record identified as a minimum we can safely clean up to the record that
// was committed at the beginning, because all other threads will see it and use it (or use
// another more recent record which is ok)
if (minRequiredRecord1 == null) {
return mostRecentCommittedAtBegin;
}
// Otherwise we do a second pass. In the second pass we re-check all the records that were
// checked before the identified oldest context, as they may have changed concurrently to a
// lower minimum.
ActiveTransactionsRecord minRequiredRecord2 = findOldestRecordUpTo(this.oldestContext,
minRequiredRecord1.transactionNumber);
// If we find another record in the second pass then that is the minimum. If not then the
// first found is it.
return (minRequiredRecord2 != null) ? minRequiredRecord2 : minRequiredRecord1;
}
private ActiveTransactionsRecord findOldestRecordUpTo(TxContext limitContext, int minRequiredVersion) {
ActiveTransactionsRecord minRequiredRecord = null;
TxContext previousCtx = null;
TxContext currentCtx = Transaction.allTxContexts;
while (currentCtx != limitContext) {
// remove a dead context if possible
if ((currentCtx.owner.get() == null) && (currentCtx.next != null)) {
removeCtx(previousCtx, currentCtx);
currentCtx = currentCtx.next; // previous does not advance
continue;
}
// we REALLY need this local variable, because of concurrent updates
ActiveTransactionsRecord record = currentCtx.oldestRequiredVersion;
if ((record != null) && (record.transactionNumber < minRequiredVersion)) {
minRequiredVersion = record.transactionNumber;
minRequiredRecord = record;
this.oldestContext = currentCtx;
}
previousCtx = currentCtx;
currentCtx = currentCtx.next;
}
return minRequiredRecord;
}
// this method does not check whether there is another context next of 'toRemove', so the caller
// should be careful not to accidentally remove the last existing record. Simply put: do no
// invoke this method when 'toRemove.next' can return null.
private void removeCtx(TxContext previous, TxContext toRemove) {
if (previous == null) { // 'toRemove' is the first record
Transaction.allTxContexts = toRemove.next;
} else {
previous.next = toRemove.next;
}
}
// public static int total = 0;
// public static int count = 0;
// public static int max = 0;
private void cleanUnusedRecords(ActiveTransactionsRecord upToThisRecord) {
// int diff = (upToThisRecord.transactionNumber - this.lastCleanedRecord.transactionNumber);
// total += diff;
// if (diff != 0) count++;
// if (diff > max) max = diff;
while (this.lastCleanedRecord.transactionNumber < upToThisRecord.transactionNumber) {
ActiveTransactionsRecord toClean = this.lastCleanedRecord.getNext();
this.lastCleanedRecord = toClean;
cleanersPool.execute(new CleanTask(toClean));
}
}
private static class CleanTask implements Runnable {
ActiveTransactionsRecord rec;
public CleanTask(ActiveTransactionsRecord rec) {
this.rec = rec;
}
public void run() {
this.rec.clean();
}
}
private static class MultipleCleanTask implements Runnable {
ActiveTransactionsRecord lastCleaned, upToThis;
public MultipleCleanTask(ActiveTransactionsRecord lastCleaned, ActiveTransactionsRecord upToThis) {
this.lastCleaned = lastCleaned;
this.upToThis = upToThis;
}
public void run() {
while (this.lastCleaned.transactionNumber < upToThis.transactionNumber) {
this.lastCleaned = this.lastCleaned.getNext();
this.lastCleaned.clean();
}
}
}
// // This executor does not create another thread to do the cleaning. It is used in scenarios
// // where the processor count is low enough, so that it does not justify to start a new thread
// private static class SingleThreadedCleaner extends ThreadPoolExecutor {
// public SingleThreadedCleaner() {
// super(1, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
// }
// public void execute(Runnable r) {
// r.run();
// }
// }
}