package edu.brown.hstore;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import org.apache.log4j.Logger;
import edu.brown.hstore.Hstoreservice.Status;
import edu.brown.hstore.conf.HStoreConf;
import edu.brown.hstore.txns.AbstractTransaction;
import edu.brown.hstore.txns.LocalTransaction;
import edu.brown.hstore.txns.RemoteTransaction;
import edu.brown.interfaces.Shutdownable;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.utils.ExceptionHandlingRunnable;
import edu.brown.utils.ThreadUtil;
/**
* Simple thread that will rip through the deletable threads and remove txn handles
* @author pavlo
*/
public class TransactionCleaner extends ExceptionHandlingRunnable implements Shutdownable {
private static final Logger LOG = Logger.getLogger(TransactionCleaner.class);
private static final LoggerBoolean debug = new LoggerBoolean();
private static final LoggerBoolean trace = new LoggerBoolean();
static {
LoggerUtil.attachObserver(LOG, debug, trace);
}
private static final int LIMIT_PER_ROUND = 10000;
private static final int NUM_REQUEUE_LISTS = 3;
private final HStoreSite hstore_site;
@SuppressWarnings("unused")
private final HStoreConf hstore_conf;
private boolean shutdown = false;
private final Map<Long, AbstractTransaction> inflight_txns;
/**
* Queues for transactions that are ready to be cleaned up and deleted
* There is one queue for each Status type
*/
private final Queue<Long> deletables[];
private final Status statuses[];
/**
* We'll maintain multiple sets of txns that need to get requeued for deletion.
* We'll cycle through them to add in a natural delay for waiting until a txn
* is fully ready to be deleted. This is probably only really necessary for distributed txns.
*/
private final Collection<Long> requeues[][];
/**
* Constructor
* @param hstore_site
*/
@SuppressWarnings("unchecked")
public TransactionCleaner(HStoreSite hstore_site) {
this.hstore_site = hstore_site;
this.hstore_conf = hstore_site.getHStoreConf();
this.inflight_txns = hstore_site.getInflightTxns();
this.statuses = new Status[Status.values().length];
this.deletables = new Queue[this.statuses.length];
this.requeues = new Collection[NUM_REQUEUE_LISTS][this.statuses.length];
int i = 0;
for (Entry<Status, Queue<Long>> e : hstore_site.getDeletableQueues().entrySet()) {
this.statuses[i] = e.getKey();
this.deletables[i] = e.getValue();
for (int j = 0; j < this.requeues.length; j++) {
this.requeues[j][i] = new ArrayList<Long>();
} // FOR
i += 1;
} // FOR
}
@Override
public void runImpl() {
this.hstore_site.getThreadManager().registerProcessingThread();
// Delete txn handles
Long txn_id = null;
int cur_index = 0;
while (this.shutdown == false) {
int swap_index = (cur_index + 1) % NUM_REQUEUE_LISTS;
// if (hstore_conf.site.profiling) this.profiler.cleanup.start();
boolean needsSleep = true;
for (int i = 0; i < this.statuses.length; i++) {
Status status = this.statuses[i];
Queue<Long> queue = this.deletables[i];
Collection<Long> swap_queue = this.requeues[swap_index][i];
if (swap_queue.isEmpty() == false) {
queue.addAll(swap_queue);
swap_queue.clear();
}
Collection<Long> requeue = this.requeues[cur_index][i];
int limit = LIMIT_PER_ROUND;
while ((txn_id = queue.poll()) != null) {
// It's ok for us to not have a transaction handle, because it could be
// for a remote transaction that told us that they were going to need one
// of our partitions but then they never actually sent work to us
AbstractTransaction ts = this.inflight_txns.get(txn_id);
if (ts != null) {
assert(txn_id.equals(ts.getTransactionId())) :
String.format("Mismatched %s - Expected[%d] != Actual[%s]",
ts, txn_id, ts.getTransactionId());
// We need to check whether a txn is ready to be deleted
if (ts.isDeletable()) {
if (ts instanceof RemoteTransaction) {
this.hstore_site.deleteRemoteTransaction((RemoteTransaction)ts, status);
}
else {
this.hstore_site.deleteLocalTransaction((LocalTransaction)ts, status);
}
needsSleep = false;
limit--;
}
// We can't delete this yet, so we'll just stop checking
else {
if (trace.val)
LOG.trace(String.format("%s - Cannot delete %s at this point [status=%s]\n%s",
ts, ts.getClass().getSimpleName(), status, ts.debug()));
requeue.add(txn_id);
}
} else if (debug.val) {
LOG.warn(String.format("Ignoring clean-up request for txn #%d because we do not have a handle " +
"[status=%s]", txn_id, status));
}
if (limit <= 0) break;
} // WHILE
} // FOR
if (needsSleep) ThreadUtil.sleep(10);
cur_index = swap_index;
// if (hstore_conf.site.profiling) this.profiler.cleanup.stop();
} // WHILE
}
@Override
public boolean isShuttingDown() {
return (this.shutdown == true);
}
@Override
public void shutdown() {
this.shutdown = true;
}
@Override
public void prepareShutdown(boolean error) {
// Nothing to do...
}
}