/**
* Copyright 2009 The Apache Software Foundation Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to you 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.
*/
package org.apache.hadoop.hbase.client.transactional;
import java.io.IOException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.NotServingRegionException;
import org.apache.hadoop.hbase.ZooKeeperConnectionException;
import org.apache.hadoop.hbase.client.HConnection;
import org.apache.hadoop.hbase.client.HConnectionManager;
import org.apache.hadoop.hbase.ipc.TransactionalRegionInterface;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
/**
* Transaction Manager. Responsible for committing transactions.
*/
public class TransactionManager {
static final Log LOG = LogFactory.getLog(TransactionManager.class);
private final HConnection connection;
private final TransactionLogger transactionLogger;
private JtaXAResource xAResource;
/**
* @param conf
* @throws ZooKeeperConnectionException
*/
public TransactionManager(final Configuration conf) throws ZooKeeperConnectionException {
this(LocalTransactionLogger.getInstance(), conf);
}
/**
* @param transactionLogger
* @param conf
* @throws ZooKeeperConnectionException
*/
public TransactionManager(final TransactionLogger transactionLogger, final Configuration conf)
throws ZooKeeperConnectionException {
this.transactionLogger = transactionLogger;
connection = HConnectionManager.getConnection(conf);
}
/**
* Called to start a transaction.
*
* @return new transaction state
*/
public TransactionState beginTransaction() {
long transactionId = transactionLogger.createNewTransactionLog();
LOG.debug("Begining transaction " + transactionId);
return new TransactionState(transactionId);
}
/**
* Prepare to commit a transaction.
*
* @param transactionState
* @return commitStatusCode (see {@link TransactionalRegionInterface})
* @throws IOException
* @throws CommitUnsuccessfulException
*/
public int prepareCommit(final TransactionState transactionState) throws CommitUnsuccessfulException, IOException {
boolean allReadOnly = true;
try {
for (HRegionLocation location : transactionState.getParticipatingRegions()) {
@SuppressWarnings("deprecation")
TransactionalRegionInterface transactionalRegionServer = (TransactionalRegionInterface) connection
.getHRegionConnection(location.getServerAddress());
int commitStatus = transactionalRegionServer.commitRequest(location.getRegionInfo().getRegionName(),
transactionState.getTransactionId());
boolean canCommit = true;
switch (commitStatus) {
case TransactionalRegionInterface.COMMIT_OK:
allReadOnly = false;
break;
case TransactionalRegionInterface.COMMIT_OK_READ_ONLY:
transactionState.addRegionToIgnore(location); // No need to doCommit for read-onlys
break;
case TransactionalRegionInterface.COMMIT_UNSUCESSFUL:
canCommit = false;
transactionState.addRegionToIgnore(location); // No need to re-abort.
break;
default:
throw new CommitUnsuccessfulException("Unexpected return code from prepareCommit: "
+ commitStatus);
}
if (LOG.isTraceEnabled()) {
LOG.trace("Region [" + location.getRegionInfo().getRegionNameAsString() + "] votes "
+ (canCommit ? "to commit" : "to abort") + " transaction "
+ transactionState.getTransactionId());
}
if (!canCommit) {
LOG.debug("Aborting [" + transactionState.getTransactionId() + "]");
abort(transactionState);
return TransactionalRegionInterface.COMMIT_UNSUCESSFUL;
}
}
} catch (Exception e) {
LOG.debug("Commit of transaction [" + transactionState.getTransactionId() + "] was unsucsessful", e);
// This happens on a NSRE that is triggered by a split
try {
abort(transactionState);
} catch (Exception abortException) {
LOG.warn("Exeption durring abort", abortException);
}
throw new CommitUnsuccessfulException(e);
}
return allReadOnly ? TransactionalRegionInterface.COMMIT_OK_READ_ONLY : TransactionalRegionInterface.COMMIT_OK;
}
/**
* Try and commit a transaction. This does both phases of the 2-phase protocol: prepare and commit.
*
* @param transactionState
* @throws IOException
* @throws CommitUnsuccessfulException
*/
public void tryCommit(final TransactionState transactionState) throws CommitUnsuccessfulException, IOException {
long startTime = EnvironmentEdgeManager.currentTimeMillis();
LOG.trace("atempting to commit trasaction: " + transactionState.toString());
int status = prepareCommit(transactionState);
if (status == TransactionalRegionInterface.COMMIT_OK) {
doCommit(transactionState);
} else if (status == TransactionalRegionInterface.COMMIT_OK_READ_ONLY) {
transactionLogger.forgetTransaction(transactionState.getTransactionId());
} else if (status == TransactionalRegionInterface.COMMIT_UNSUCESSFUL) {
// We have already aborted at this point
throw new CommitUnsuccessfulException();
}
LOG.trace("Committed transaction [" + transactionState.getTransactionId() + "] in ["
+ ((EnvironmentEdgeManager.currentTimeMillis() - startTime)) + "]ms");
}
/**
* Do the commit. This is the 2nd phase of the 2-phase protocol.
*
* @param transactionState
* @throws CommitUnsuccessfulException
*/
void doCommit(final TransactionState transactionState) throws CommitUnsuccessfulException {
try {
LOG.trace("Commiting [" + transactionState.getTransactionId() + "]");
transactionLogger.setStatusForTransaction(transactionState.getTransactionId(),
TransactionLogger.TransactionStatus.COMMITTED);
for (HRegionLocation location : transactionState.getParticipatingRegions()) {
if (transactionState.getRegionsToIngore().contains(location)) {
continue;
}
@SuppressWarnings("deprecation")
TransactionalRegionInterface transactionalRegionServer = (TransactionalRegionInterface) connection
.getHRegionConnection(location.getServerAddress());
transactionalRegionServer.commit(location.getRegionInfo().getRegionName(),
transactionState.getTransactionId());
}
} catch (Exception e) {
LOG.info("Commit of transaction [" + transactionState.getTransactionId() + "] was unsucsessful", e);
// This happens on a NSRE that is triggered by a split
try {
abort(transactionState);
} catch (Exception abortException) {
LOG.warn("Exeption durring abort", abortException);
}
throw new CommitUnsuccessfulException(e);
}
transactionLogger.forgetTransaction(transactionState.getTransactionId());
}
/**
* Abort a s transaction.
*
* @param transactionState
* @throws IOException
*/
public void abort(final TransactionState transactionState) throws IOException {
transactionLogger.setStatusForTransaction(transactionState.getTransactionId(),
TransactionLogger.TransactionStatus.ABORTED);
for (HRegionLocation location : transactionState.getParticipatingRegions()) {
if (transactionState.getRegionsToIngore().contains(location)) {
continue;
}
try {
@SuppressWarnings("deprecation")
TransactionalRegionInterface transactionalRegionServer = (TransactionalRegionInterface) connection
.getHRegionConnection(location.getServerAddress());
transactionalRegionServer.abortTransaction(location.getRegionInfo().getRegionName(),
transactionState.getTransactionId());
} catch (UnknownTransactionException e) {
LOG.info("Got unknown transaciton exception durring abort. Transaction: ["
+ transactionState.getTransactionId() + "], region: ["
+ location.getRegionInfo().getRegionNameAsString() + "]. Ignoring.");
} catch (NotServingRegionException e) {
LOG.info("Got NSRE durring abort. Transaction: [" + transactionState.getTransactionId() + "], region: ["
+ location.getRegionInfo().getRegionNameAsString() + "]. Ignoring.");
}
}
transactionLogger.forgetTransaction(transactionState.getTransactionId());
}
public synchronized JtaXAResource getXAResource() {
if (xAResource == null) {
xAResource = new JtaXAResource(this);
}
return xAResource;
}
}