All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.transaction.impl.TransactionManagerServiceImpl Maven / Gradle / Ivy

/*
 * Copyright (c) 2008-2016, Hazelcast, Inc. All Rights Reserved.
 *
 * 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.
 */

package com.hazelcast.transaction.impl;

import com.hazelcast.cluster.ClusterService;
import com.hazelcast.cluster.ClusterState;
import com.hazelcast.core.Member;
import com.hazelcast.instance.MemberImpl;
import com.hazelcast.logging.ILogger;
import com.hazelcast.nio.Address;
import com.hazelcast.spi.ClientAwareService;
import com.hazelcast.spi.ExecutionService;
import com.hazelcast.spi.ManagedService;
import com.hazelcast.spi.MemberAttributeServiceEvent;
import com.hazelcast.spi.MembershipAwareService;
import com.hazelcast.spi.MembershipServiceEvent;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.Operation;
import com.hazelcast.spi.OperationService;
import com.hazelcast.spi.impl.NodeEngineImpl;
import com.hazelcast.transaction.TransactionContext;
import com.hazelcast.transaction.TransactionException;
import com.hazelcast.transaction.TransactionManagerService;
import com.hazelcast.transaction.TransactionOptions;
import com.hazelcast.transaction.TransactionalTask;
import com.hazelcast.transaction.impl.operations.BroadcastTxRollbackOperation;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

import static com.hazelcast.transaction.impl.Transaction.State;
import static com.hazelcast.transaction.impl.Transaction.State.ACTIVE;
import static com.hazelcast.transaction.impl.Transaction.State.COMMITTING;
import static com.hazelcast.transaction.impl.Transaction.State.ROLLING_BACK;
import static com.hazelcast.util.FutureUtil.ExceptionHandler;
import static com.hazelcast.util.FutureUtil.logAllExceptions;
import static com.hazelcast.util.FutureUtil.waitWithDeadline;
import static com.hazelcast.util.Preconditions.checkNotNull;
import static java.util.Collections.shuffle;

public class TransactionManagerServiceImpl implements TransactionManagerService, ManagedService,
        MembershipAwareService, ClientAwareService {

    public static final String SERVICE_NAME = "hz:core:txManagerService";

    private static final Address[] EMPTY_ADDRESSES = new Address[0];

    final ConcurrentMap txBackupLogs = new ConcurrentHashMap();

    private final ExceptionHandler finalizeExceptionHandler;

    private final NodeEngineImpl nodeEngine;

    private final ILogger logger;

    public TransactionManagerServiceImpl(NodeEngineImpl nodeEngine) {
        this.nodeEngine = nodeEngine;
        this.logger = nodeEngine.getLogger(TransactionManagerService.class);
        this.finalizeExceptionHandler = logAllExceptions(logger, "Error while rolling-back tx!", Level.WARNING);
    }

    public String getGroupName() {
        return nodeEngine.getConfig().getGroupConfig().getName();
    }

    @Override
    public  T executeTransaction(TransactionOptions options, TransactionalTask task) throws TransactionException {
        checkNotNull(task, "TransactionalTask is required!");


        TransactionContext context = newTransactionContext(options);
        context.beginTransaction();
        try {
            T value = task.execute(context);
            context.commitTransaction();
            return value;
        } catch (Throwable e) {
            context.rollbackTransaction();
            if (e instanceof TransactionException) {
                throw (TransactionException) e;
            }
            if (e.getCause() instanceof TransactionException) {
                throw (TransactionException) e.getCause();
            }
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            }
            throw new TransactionException(e);
        }
    }

    @Override
    public TransactionContext newTransactionContext(TransactionOptions options) {
        return new TransactionContextImpl(this, nodeEngine, options, null, false);
    }

    @Override
    public TransactionContext newClientTransactionContext(TransactionOptions options, String clientUuid) {
        return new TransactionContextImpl(this, nodeEngine, options, clientUuid, true);
    }

    /**
     * Creates a plain transaction object, without wrapping it
     * inside a TransactionContext.
     * 

* A Transaction is a lower level API than TransactionContext. * It's not possible to create/access transactional * data structures without TransactionContext. *

* A Transaction object * only allows starting/committing/rolling back transaction, * accessing state of the transaction * and adding TransactionLogRecord to the transaction. * * @param options transaction options * @return a new transaction */ public Transaction newTransaction(TransactionOptions options) { return new TransactionImpl(this, nodeEngine, options, null); } /** * Creates a plain transaction object which can be used while cluster state is {@link ClusterState#PASSIVE}, * without wrapping it inside a TransactionContext. *

* Also see {@link TransactionManagerServiceImpl#newTransaction(TransactionOptions)} for more details * * @param options transaction options * @return a new transaction which can be used while cluster state is {@link ClusterState#PASSIVE} */ public Transaction newAllowedDuringPassiveStateTransaction(TransactionOptions options) { return new AllowedDuringPassiveStateTransactionImpl(this, nodeEngine, options, null); } @Override public void init(NodeEngine nodeEngine, Properties properties) { } @Override public void reset() { txBackupLogs.clear(); } @Override public void shutdown(boolean terminate) { reset(); } @Override public void memberAdded(MembershipServiceEvent event) { } @Override public void memberRemoved(MembershipServiceEvent event) { MemberImpl member = event.getMember(); final String uuid = member.getUuid(); if (nodeEngine.isRunning()) { logger.info("Committing/rolling-back alive transactions of " + member + ", UUID: " + uuid); nodeEngine.getExecutionService().execute(ExecutionService.SYSTEM_EXECUTOR, new Runnable() { @Override public void run() { finalizeTransactionsOf(uuid); } }); } else if (logger.isFinestEnabled()) { logger.finest("Will not commit/roll-back transactions of " + member + ", UUID: " + uuid + " because this member is not running"); } } @Override public void memberAttributeChanged(MemberAttributeServiceEvent event) { } private void finalizeTransactionsOf(String callerUuid) { final Iterator> it = txBackupLogs.entrySet().iterator(); while (it.hasNext()) { final Map.Entry entry = it.next(); final String txnId = entry.getKey(); final TxBackupLog log = entry.getValue(); if (finalize(callerUuid, txnId, log)) { it.remove(); } } } private boolean finalize(String uuid, String txnId, TxBackupLog log) { OperationService operationService = nodeEngine.getOperationService(); if (!uuid.equals(log.callerUuid)) { return false; } if (log.state == ACTIVE) { if (logger.isFinestEnabled()) { logger.finest("Rolling-back transaction[id:" + txnId + ", state:ACTIVE] of endpoint " + uuid); } Collection memberList = nodeEngine.getClusterService().getMembers(); Collection futures = new ArrayList(memberList.size()); for (Member member : memberList) { Operation op = new BroadcastTxRollbackOperation(txnId); Future f = operationService.invokeOnTarget(SERVICE_NAME, op, member.getAddress()); futures.add(f); } long timeoutMillis = TransactionOptions.getDefault().getTimeoutMillis(); waitWithDeadline(futures, timeoutMillis, TimeUnit.MILLISECONDS, finalizeExceptionHandler); } else { TransactionImpl tx; if (log.allowedDuringPassiveState) { tx = new AllowedDuringPassiveStateTransactionImpl(this, nodeEngine, txnId, log.records, log.timeoutMillis, log.startTime, log.callerUuid); } else { tx = new TransactionImpl(this, nodeEngine, txnId, log.records, log.timeoutMillis, log.startTime, log.callerUuid); } if (log.state == COMMITTING) { if (logger.isFinestEnabled()) { logger.finest("Committing transaction[id:" + txnId + ", state:COMMITTING] of endpoint " + uuid); } try { tx.commit(); } catch (Throwable e) { logger.warning("Error during committing from tx backup!", e); } } else { if (logger.isFinestEnabled()) { logger.finest("Rolling-back transaction[id:" + txnId + ", state:" + log.state + "] of endpoint " + uuid); } try { tx.rollback(); } catch (Throwable e) { logger.warning("Error during rolling-back from tx backup!", e); } } } return true; } @Override public void clientDisconnected(String clientUuid) { logger.info("Committing/rolling-back alive transactions of client, UUID: " + clientUuid); finalizeTransactionsOf(clientUuid); } Address[] pickBackupLogAddresses(int durability) { if (durability == 0) { return EMPTY_ADDRESSES; } // This should be cleaned up because this is quite a complex approach since it depends on // the number of members in the cluster and creates litter. ClusterService clusterService = nodeEngine.getClusterService(); List members = new ArrayList(clusterService.getMemberImpls()); members.remove(nodeEngine.getLocalMember()); int c = Math.min(members.size(), durability); shuffle(members); Address[] addresses = new Address[c]; for (int i = 0; i < c; i++) { addresses[i] = members.get(i).getAddress(); } return addresses; } public void createBackupLog(String callerUuid, String txnId) { createBackupLog(callerUuid, txnId, false); } public void createAllowedDuringPassiveStateBackupLog(String callerUuid, String txnId) { createBackupLog(callerUuid, txnId, true); } private void createBackupLog(String callerUuid, String txnId, boolean allowedDuringPassiveState) { TxBackupLog log = new TxBackupLog(Collections.emptyList(), callerUuid, ACTIVE, -1, -1, allowedDuringPassiveState); if (txBackupLogs.putIfAbsent(txnId, log) != null) { throw new TransactionException("TxLog already exists!"); } } public void replicaBackupLog(List records, String callerUuid, String txnId, long timeoutMillis, long startTime) { TxBackupLog beginLog = txBackupLogs.get(txnId); if (beginLog == null) { throw new TransactionException("Could not find begin tx log!"); } if (beginLog.state != ACTIVE) { // the exception message is very strange throw new TransactionException("TxLog already exists!"); } TxBackupLog newTxBackupLog = new TxBackupLog(records, callerUuid, COMMITTING, timeoutMillis, startTime, beginLog.allowedDuringPassiveState); if (!txBackupLogs.replace(txnId, beginLog, newTxBackupLog)) { throw new TransactionException("TxLog already exists!"); } } public void rollbackBackupLog(String txnId) { TxBackupLog log = txBackupLogs.get(txnId); if (log == null) { logger.warning("No tx backup log is found, tx -> " + txnId); } else { log.state = ROLLING_BACK; } } public void purgeBackupLog(String txnId) { txBackupLogs.remove(txnId); } static final class TxBackupLog { final List records; final String callerUuid; final long timeoutMillis; final long startTime; final boolean allowedDuringPassiveState; volatile State state; private TxBackupLog(List records, String callerUuid, State state, long timeoutMillis, long startTime, boolean allowedDuringPassiveState) { this.records = records; this.callerUuid = callerUuid; this.state = state; this.timeoutMillis = timeoutMillis; this.startTime = startTime; this.allowedDuringPassiveState = allowedDuringPassiveState; } @Override public String toString() { return "TxBackupLog{" + "records=" + records + ", callerUuid='" + callerUuid + '\'' + ", timeoutMillis=" + timeoutMillis + ", startTime=" + startTime + ", state=" + state + ", allowedDuringPassiveState=" + allowedDuringPassiveState + '}'; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy