package io.eguan.dtx;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import static io.eguan.dtx.DtxResourceManagerState.UNDETERMINED;
import java.io.Serializable;
import java.util.Set;
import java.util.UUID;
import javax.transaction.xa.XAException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hazelcast.core.AtomicNumber;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.LifecycleEvent;
import com.hazelcast.core.LifecycleListener;
/**
* Transaction monitor to be sent to all nodes participating in a transaction.
*
* @author oodrive
* @author pwehrle
*
*/
final class TransactionMonitorHandler extends AbstractDistOpHandler implements Runnable {
private class ShutdownListener implements LifecycleListener, Serializable {
private static final long serialVersionUID = 3075326218179487411L;
@Override
public void stateChanged(final LifecycleEvent event) {
switch (event.getState()) {
case SHUTTING_DOWN:
case SHUTDOWN:
shutdown = true;
break;
default:
// nothing
}
}
}
private static final long serialVersionUID = 3261167430220912186L;
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionMonitorHandler.class);
private static final int TX_CHECK_OCCURRENCES = 10;
private final long txId;
private final long timeout;
private final UUID resId;
private final LifecycleListener shutdownListener = new ShutdownListener();
private boolean shutdown;
/**
* Constructs an instance for a given transaction.
*
* @param txId
* the transaction's ID
* @param resId
* the target resource manager's {@link UUID}
* @param timeout
* the timeout in milliseconds beyond which the transaction is to be rolled back locally
* @param participants
* the {@link Set} of participants to record with the transaction rollback
*/
TransactionMonitorHandler(final long txId, final UUID resId, final long timeout, final Set<DtxNode> participants) {
super(participants);
this.txId = txId;
this.resId = resId;
this.timeout = timeout;
}
@Override
public final void run() {
final HazelcastInstance hzInstance = getHazelcastInstance();
final AtomicNumber currCounter = hzInstance.getAtomicNumber(TransactionInitiator.TX_CURRENT_ID);
final long limit = System.currentTimeMillis() + timeout;
hzInstance.getLifecycleService().addLifecycleListener(shutdownListener);
try {
final TransactionManager txMgr = getTransactionManager();
final long txCheckInterval = timeout / TX_CHECK_OCCURRENCES;
long currTxId;
do {
try {
currTxId = currCounter.get();
Thread.sleep(txCheckInterval);
}
catch (IllegalStateException | InterruptedException e) {
// gracefully exit if monitoring conditions are degraded
return;
}
} while (currTxId <= txId && System.currentTimeMillis() < limit);
/*
* TODO: before calling any "starveable" methods (getting resource managers or last tx IDs), seek out and
* destroy any previous deadlocked or starving thread still holding those locks
*/
final DtxResourceManager targetResMgr = txMgr.getRegisteredResourceManager(resId);
if (targetResMgr == null) {
// resource manager vanished, abort
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Resource manager gone, aborting; txId=" + txId + ", resId=" + resId + ", node="
+ txMgr.getLocalNode());
}
return;
}
final long lastTxId = txMgr.getLastCompleteTxIdForResMgr(resId);
// transaction passed
if (currTxId > txId) {
if (lastTxId < txId && targetResMgr != null) {
// transaction end phase didn't end up here -> resync
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Transaction not recorded locally; txId=" + txId + ", last local=" + lastTxId);
}
txMgr.setResManagerSyncState(resId, UNDETERMINED);
}
return;
}
// transaction monitor timed out
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Monitor on transaction timed out; txId=" + txId + ", nodeId=" + hzInstance.getName());
}
try {
try {
if (lastTxId < txId && targetResMgr != null) {
// roll back locally
txMgr.rollback(txId, getParticipants());
}
}
catch (final XAException e) {
LOGGER.error("Rollback after timeout failed; txId=" + txId + ", nodeId=" + hzInstance.getName()
+ ", errorCode=" + e.errorCode);
txMgr.setResManagerSyncState(resId, UNDETERMINED);
}
finally {
if (!shutdown) {
DtxUtils.updateAtomicNumberToAtLeast(currCounter, txId);
}
}
}
catch (final IllegalStateException e) {
LOGGER.error("Illegal state; txId=" + txId + ", nodeId=" + hzInstance.getName(), e);
}
}
finally {
hzInstance.getLifecycleService().removeLifecycleListener(shutdownListener);
}
}
}