/** * 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 java.util.Iterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HRegionLocation; import org.apache.hadoop.hbase.NotServingRegionException; import org.apache.hadoop.hbase.client.HConnection; import org.apache.hadoop.hbase.client.HConnectionManager; import org.apache.hadoop.hbase.ipc.TransactionalRegionInterface; /** * Transaction Manager. Responsible for committing transactions. * */ public class TransactionManager { static { TransactionalRPC.initialize(); } static final Log LOG = LogFactory.getLog(TransactionManager.class); private final HConnection connection; private final TransactionLogger transactionLogger; private JtaXAResource xAResource; /** * @param conf */ public TransactionManager(final HBaseConfiguration conf) { this(LocalTransactionLogger.getInstance(), conf); } /** * @param transactionLogger * @param conf */ public TransactionManager(final TransactionLogger transactionLogger, final HBaseConfiguration conf) { 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 { Iterator<HRegionLocation> locationIterator = transactionState.getParticipatingRegions().iterator(); while (locationIterator.hasNext()) { HRegionLocation location = locationIterator.next(); 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: locationIterator.remove(); // No need to doCommit for read-onlys break; case TransactionalRegionInterface.COMMIT_UNSUCESSFUL: canCommit = false; 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, location); throw new CommitUnsuccessfulException(); } } } 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 = System.currentTimeMillis(); LOG.debug("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()); } LOG.debug("Committed transaction ["+transactionState.getTransactionId()+"] in ["+((System.currentTimeMillis()-startTime))+"]ms"); } /** Do the commit. This is the 2nd phase of the 2-phase protocol. * * @param transactionState * @throws CommitUnsuccessfulException */ public void doCommit(final TransactionState transactionState) throws CommitUnsuccessfulException{ try { LOG.debug("Commiting [" + transactionState.getTransactionId() + "]"); transactionLogger.setStatusForTransaction(transactionState .getTransactionId(), TransactionLogger.TransactionStatus.COMMITTED); for (HRegionLocation location : transactionState .getParticipatingRegions()) { TransactionalRegionInterface transactionalRegionServer = (TransactionalRegionInterface) connection .getHRegionConnection(location.getServerAddress()); transactionalRegionServer.commit(location.getRegionInfo() .getRegionName(), transactionState.getTransactionId()); } } 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); } transactionLogger.forgetTransaction(transactionState.getTransactionId()); } /** * Abort a s transaction. * * @param transactionState * @throws IOException */ public void abort(final TransactionState transactionState) throws IOException { abort(transactionState, null); } private void abort(final TransactionState transactionState, final HRegionLocation locationToIgnore) throws IOException { transactionLogger.setStatusForTransaction(transactionState .getTransactionId(), TransactionLogger.TransactionStatus.ABORTED); for (HRegionLocation location : transactionState.getParticipatingRegions()) { if (locationToIgnore != null && location.equals(locationToIgnore)) { continue; } try { TransactionalRegionInterface transactionalRegionServer = (TransactionalRegionInterface) connection .getHRegionConnection(location.getServerAddress()); transactionalRegionServer.abort(location.getRegionInfo() .getRegionName(), transactionState.getTransactionId()); } catch (UnknownTransactionException e) { LOG .debug("Got unknown transaciton exception durring abort. Transaction: [" + transactionState.getTransactionId() + "], region: [" + location.getRegionInfo().getRegionNameAsString() + "]. Ignoring."); } catch (NotServingRegionException e) { LOG .debug("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; } }