/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package com.github.geophile.erdo.transaction;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
// synchronization is done by Forest.
public class TransactionRegistry
{
// TransactionRegistry interface
public void transactionStarted(Transaction transaction)
{
assert !transaction.hasTerminated();
// Monotonically increasing startTimes is guaranteed by synchronization in Forest.
// Without this property, the min active startTime could go backward, rendering
// incorrect a decision to remove a transaction from committed. I.e., once a transaction
// is removed from committed, there must never be a new transaction with a startTime
// less that the commit timestamp of the removed transaction.
assert transaction.startTime() > minStartTime();
LOG.log(Level.INFO, "{0} is starting", transaction);
Transaction replaced = active.put(transaction.startTime(), transaction);
assert replaced == null : replaced;
}
// transactionCommitted/Aborted mark transactions that have become irrelevant by the end
// of the given transaction, (possibly including the transaction itself) A transaction is
// irrelevant if its commit timestamp is less than the minimum start timestamp of all active
// transactions.
public List<Transaction> transactionCommitted(Transaction transaction)
{
List<Transaction> irrelevantTransactions;
assert transaction.hasCommitted() : transaction;
long previousMinStart = minStartTime();
Transaction removed = active.remove(transaction.startTime());
assert removed == transaction : removed;
committed.put(transaction.commitTime(), transaction);
if (transaction.startTime() == previousMinStart) {
// We're removing the active transaction with the min timestamp. Some transactions
// may be irrelevant as a result.
irrelevantTransactions = markIrrelevantTransactions(transaction);
} else {
if (LOG.isLoggable(Level.INFO)) {
LOG.log(Level.INFO,
"{0} is committing, minStartTime = {1}",
new Object[]{transaction, previousMinStart});
}
irrelevantTransactions = Collections.emptyList();
}
return irrelevantTransactions;
}
public List<Transaction> transactionAborted(Transaction transaction)
{
List<Transaction> irrelevantTransactions;
assert transaction.hasAborted() : transaction;
assert transaction.irrelevant() : transaction;
long previousMinStart = minStartTime();
Transaction removed = active.remove(transaction.startTime());
assert removed == transaction : removed;
if (transaction.startTime() == previousMinStart) {
// We're removing the active transaction with the min timestamp. Some transactions
// may be irrelevant as a result.
irrelevantTransactions = markIrrelevantTransactions(transaction);
} else {
if (LOG.isLoggable(Level.INFO)) {
LOG.log(Level.INFO,
"{0} is aborting, minStartTime = {1}",
new Object[]{transaction, previousMinStart});
}
irrelevantTransactions = new ArrayList<>();
}
irrelevantTransactions.add(transaction);
return irrelevantTransactions;
}
// For testing
public void reset()
{
active.clear();
committed.clear();
}
// For use by this class
private List<Transaction> markIrrelevantTransactions(Transaction endingTransaction)
{
List<Transaction> irrelevantTransactions = new ArrayList<>();
StringBuilder buffer = null;
boolean logging = LOG.isLoggable(Level.INFO);
if (logging) {
buffer = new StringBuilder();
}
if (active.isEmpty()) {
for (Transaction transaction : committed.values()) {
transaction.markIrrelevant();
irrelevantTransactions.add(transaction);
if (logging) {
buffer.append(transaction.toString());
buffer.append(' ');
}
}
committed.clear();
} else {
long minStartTime = minStartTime();
boolean done = false;
for (Iterator<Map.Entry<Long, Transaction>> committedScan = committed.entrySet().iterator();
!done && committedScan.hasNext();) {
Map.Entry<Long, Transaction> entry = committedScan.next();
long commitTime = entry.getKey();
Transaction committedTransaction = entry.getValue();
if (commitTime < minStartTime) {
committedScan.remove();
committedTransaction.markIrrelevant();
irrelevantTransactions.add(committedTransaction);
if (logging) {
buffer.append(committedTransaction.toString());
buffer.append(' ');
}
} else {
done = true;
}
}
}
if (logging) {
LOG.log(Level.INFO,
"Irrelevant transactions following end of {0}: {1}",
new Object[]{endingTransaction, buffer});
}
return irrelevantTransactions;
}
private long minStartTime()
{
return active.isEmpty() ? Long.MIN_VALUE : active.firstKey();
}
// Class state
private static final Logger LOG = Logger.getLogger(TransactionRegistry.class.getName());
// Transactions that have not yet terminated.
private final SortedMap<Long, Transaction> active = new TreeMap<>();
// Transactions that have committed, but whose commitTime > min startTime
// of active transactions.
private final SortedMap<Long, Transaction> committed = new TreeMap<>();
}