com.atomikos.icatch.imp.RecoveryDomainService Maven / Gradle / Ivy
/**
* Copyright (C) 2000-2023 Atomikos
*
* LICENSE CONDITIONS
*
* See http://www.atomikos.com/Main/WhichLicenseApplies for details.
*/
package com.atomikos.icatch.imp;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import com.atomikos.datasource.RecoverableResource;
import com.atomikos.icatch.config.Configuration;
import com.atomikos.icatch.event.transaction.TransactionHeuristicEvent;
import com.atomikos.logging.Logger;
import com.atomikos.logging.LoggerFactory;
import com.atomikos.publish.EventPublisher;
import com.atomikos.recovery.LogReadException;
import com.atomikos.recovery.PendingTransactionRecord;
import com.atomikos.recovery.RecoveryLog;
import com.atomikos.recovery.TxState;
import com.atomikos.thread.TaskManager;
import com.atomikos.timing.AlarmTimer;
import com.atomikos.timing.AlarmTimerListener;
import com.atomikos.timing.PooledAlarmTimer;
public class RecoveryDomainService {
private static final Logger LOGGER = LoggerFactory.createLogger(RecoveryDomainService.class);
private RecoveryLog recoveryLog;
private boolean stopped; // for case 189603: avoid API-based recovery attempts after shutdown
public RecoveryDomainService(RecoveryLog recoveryLog) {
this.recoveryLog = recoveryLog;
}
private long maxTimeout;
private PooledAlarmTimer recoveryTimer;
private String recoveryDomainName;
public void init() {
long recoveryDelay = Configuration.getConfigProperties().getRecoveryDelay();
setMaxTimeout(Configuration.getConfigProperties().getMaxTimeout());
recoveryDomainName = Configuration.getConfigProperties().getTmUniqueName();
recoveryTimer = new PooledAlarmTimer(recoveryDelay);
recoveryTimer.addAlarmTimerListener(new AlarmTimerListener() {
@Override
public void alarm(AlarmTimer timer) {
performRecovery();
}
});
TaskManager.SINGLETON.executeTask(recoveryTimer);
}
public void setMaxTimeout(long maxTimeout) {
this.maxTimeout = maxTimeout;
}
protected synchronized boolean performRecovery() {
boolean perform = !stopped && recoveryLog.isActive();
if (perform) {
try {
boolean allOk = true;
long startOfRecovery = System.currentTimeMillis();
Set resourcesToRecover = getResourcesForRecovery();
Collection indoubtCoordinators = recoveryLog.getIndoubtTransactionRecords();
Collection foreignIndoubtCoordinators = extractForeignRecords(indoubtCoordinators);
Collection foreignCoordinatorsForHeuristicAbort = extractForeignIndoubtCoordinatorsForHeuristicAbort(foreignIndoubtCoordinators, startOfRecovery);
Collection expiredCommittingCoordinators = recoveryLog.getExpiredPendingCommittingTransactionRecordsAt(startOfRecovery);
for (RecoverableResource recoverableResource : resourcesToRecover) {
try {
allOk = allOk && recoverableResource.recover(startOfRecovery, expiredCommittingCoordinators, foreignIndoubtCoordinators);
} catch (Throwable e) {
allOk = false;
LOGGER.logError(e.getMessage(), e);
}
}
Collection recordsToDelete = new HashSet<>();
if (allOk) {
recordsToDelete.addAll(expiredCommittingCoordinators);
Collection expiredNativeIndoubtCoordinators = extractNativeIndoubtCoordinatorsExpiredSince(startOfRecovery - maxTimeout, indoubtCoordinators);
recordsToDelete.addAll(expiredNativeIndoubtCoordinators);
}
recordsToDelete.addAll(foreignCoordinatorsForHeuristicAbort);
recoveryLog.forgetTransactionRecords(recordsToDelete);
} catch (Throwable e) {
LOGGER.logError(e.getMessage(), e);
}
}
return perform;
}
private Collection extractNativeIndoubtCoordinatorsExpiredSince(long momentInThePast,
Collection collection) {
return PendingTransactionRecord.collectLineages(
(PendingTransactionRecord r) -> r.isLocalRoot(recoveryDomainName) && !r.isForeignInDomain(recoveryDomainName) && r.expires < momentInThePast && r.state == TxState.IN_DOUBT ,
collection);
}
private Collection extractForeignRecords(
Collection collection) {
return PendingTransactionRecord.collectLineages(
(PendingTransactionRecord r) -> r.isForeignInDomain(recoveryDomainName),
collection);
}
private Collection extractForeignIndoubtCoordinatorsForHeuristicAbort(
Collection foreignIndoubtCoordinators, long startOfRecovery) {
HashSet ret = new HashSet<>();
Iterator it = foreignIndoubtCoordinators.iterator();
while (it.hasNext()) {
PendingTransactionRecord record = it.next();
if (record.expires + maxTimeout < startOfRecovery) {
if (record.allowsHeuristicTermination(recoveryDomainName)) {
ret.add(record);
} else {
//pending expired in-doubt => generate warning
TransactionHeuristicEvent event = new TransactionHeuristicEvent(record.id, record.superiorId, TxState.IN_DOUBT);
EventPublisher.INSTANCE.publish(event);
}
}
for (PendingTransactionRecord entry : ret) {
foreignIndoubtCoordinators.remove(entry); //remove - so presumed abort will terminate this one
PendingTransactionRecord.removeAllDescendants(entry, foreignIndoubtCoordinators); //make sure that local descendants also abort
TransactionHeuristicEvent event = new TransactionHeuristicEvent(record.id, record.superiorId, TxState.HEUR_ABORTED);
EventPublisher.INSTANCE.publish(event);
}
}
return ret;
}
private Set getResourcesForRecovery() {
Collection resources = null;
resources = Configuration.getResources();
return filterDuplicates(resources); //cf case 170618
}
private Set filterDuplicates(Collection resources) {
return new HashSet(resources);
}
public synchronized void stop() {
if (recoveryTimer != null) {
recoveryTimer.stopTimer();
recoveryTimer = null;
}
stopped = true;
}
/**
*
* @return False if nothing more to do.
*/
public synchronized boolean hasMoreToRecover() {
if (!recoveryLog.isActive()) {
// another instance has taken over => we're off the hook
return false;
}
try {
Collection remainingCommitRecords =
recoveryLog.getExpiredPendingCommittingTransactionRecordsAt(Long.MAX_VALUE);
Collection remainingIndoubtRecords =
recoveryLog.getIndoubtTransactionRecords();
if (remainingCommitRecords.isEmpty() && remainingIndoubtRecords.isEmpty()) {
// no significant records left => no recovery work pending
return false;
}
} catch (LogReadException e) {
LOGGER.logDebug("Unexpected error", e);
}
return true;
}
}