/*
* Bitronix Transaction Manager
*
* Copyright (c) 2011, Juergen Kellerer.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program 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 distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package bitronix.tm.journal.nio;
import bitronix.tm.utils.Uid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static javax.transaction.Status.*;
/**
* A tracker for dangling transactions (= transactions that need to be considered in rollovers and during Recoverer runs).
*
* @author juergen kellerer, 2011-04-30
*/
class NioTrackedTransactions implements NioJournalConstants {
private static final Logger log = LoggerFactory.getLogger(NioTrackedTransactions.class);
private static boolean isTrackedRecordInValidStatus(int trackedStatus, int status) {
switch (status) {
case STATUS_COMMITTED:
return trackedStatus == STATUS_COMMITTING;
case STATUS_ROLLEDBACK:
return trackedStatus == STATUS_ROLLING_BACK;
case STATUS_PREPARED:
return trackedStatus == STATUS_PREPARING;
}
return true;
}
private final ConcurrentMap<Uid, NioJournalRecord> tracked = new ConcurrentHashMap<Uid, NioJournalRecord>();
/**
* Track the given transaction log record entry.
*
* @param record the record to track.
*/
public void track(final NioJournalRecord record) {
track(record.getStatus(), record.getGtrid(), record);
}
/**
* Track the given transaction information.
*
* @param status the TX status.
* @param gtrid the global transaction id.
* @param record the record to track.
*/
public void track(final int status, final Uid gtrid, final NioJournalRecord record) {
if (status == STATUS_UNKNOWN)
return;
if (FINAL_STATUS.contains(status)) {
NioJournalRecord existing = tracked.get(gtrid);
if (existing != null) {
if (!isTrackedRecordInValidStatus(existing.getStatus(), status)) {
log.warn("Found a violation in the logged tx journal record " + record + ". The transaction " + gtrid + " was set to status " +
TRANSACTION_LONG_STATUS_STRINGS.get(status) + " on resources " + record.getUniqueNames() + " though the tracked status is " +
TRANSACTION_LONG_STATUS_STRINGS.get(existing.getStatus()) + " on resources " + existing.getUniqueNames());
}
NioJournalRecord replacement;
while ((existing = tracked.get(gtrid)) != null && (replacement = existing.createNameReducedCopy(record)) != existing) {
if (replacement.getUniqueNamesCount() == 0) {
if (tracked.remove(gtrid, existing)) {
if (log.isDebugEnabled()) { log.debug("No longer tracking transaction '" + record + "', was '" + existing + "' before"); }
}
} else {
if (tracked.replace(gtrid, existing, replacement)) {
if (log.isDebugEnabled()) {
log.debug("Changed status on transaction bits '" + record.getUniqueNames() + "' in '" + existing + "', from '" + record + "'");
}
}
}
}
}
} else if (TRACKED_STATUS.contains(status)) {
final NioJournalRecord removed = tracked.put(gtrid, record);
// Note: Merging names between removed and record is not required as the external implementation
// should not provide a subset of names unless isFinalStatus returns true. Logging an error if the case still happens.
if (removed != null && !removed.isUniqueNamesContainedInRecord(record)) {
if (removed.getStatus() <= record.getStatus() && record.isRolledOverFlag()) {
if (log.isDebugEnabled()) {
log.debug("Found a rolled over reduced transaction in state " + TRANSACTION_LONG_STATUS_STRINGS.get(status) + " with " +
"unique names " + record.getUniqueNames() + " used to be " + removed.getUniqueNames());
}
} else {
log.error("The unique names describing the TX members changed at an invalid TX status of " + TRANSACTION_LONG_STATUS_STRINGS.get(status) +
", when tracking updates to the transaction " + gtrid + " inside the journal. (names had been " + removed.getUniqueNames() +
" at status " + TRANSACTION_LONG_STATUS_STRINGS.get(removed.getStatus()) + " and were now set to " + record.getUniqueNames() + ")");
}
}
if (log.isDebugEnabled()) { log.debug("Tracking pending transaction '" + record + "', was '" + removed + "' before"); }
} else {
NioJournalRecord removed = tracked.remove(gtrid);
if (removed != null)
if (log.isDebugEnabled()) { log.debug("No longer tracking transaction " + removed + " when receiving a log record of " + record); }
}
}
/**
* Purges entries that exceeded the maximum lifetime that transactions are tracked.
*/
public void purgeTransactionsExceedingLifetime() {
final long now = System.currentTimeMillis();
for (Iterator<NioJournalRecord> i = tracked.values().iterator(); i.hasNext(); ) {
NioJournalRecord journalRecord = i.next();
final long age = now - journalRecord.getTime();
if (age > TRANSACTION_MAX_LIFETIME) {
log.warn("The maximum lifetime of " + (TRANSACTION_MAX_LIFETIME / MS_PER_HOUR) + " hours was exceeded " +
"(TX age is " + (age / MS_PER_HOUR) + " hours). Discarding dangling transaction " + journalRecord);
i.remove();
}
}
}
public void clear() {
tracked.clear();
}
public int size() {
return tracked.size();
}
/**
* Returns an unmodifiable map of tracked transactions.
*
* @return an unmodifiable map of tracked transactions.
*/
public Map<Uid, NioJournalRecord> getTracked() {
return Collections.unmodifiableMap(tracked);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "NioTrackedTransactions{" +
"tracked=" + tracked.size() +
'}';
}
}