co.cask.tephra.distributed.TransactionServiceClient.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.tephra.distributed.TransactionServiceClient.java

Source

/*
 * Copyright  2012-2014 Cask Data, Inc.
 *
 * 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 co.cask.tephra.distributed;

import co.cask.tephra.InvalidTruncateTimeException;
import co.cask.tephra.Transaction;
import co.cask.tephra.TransactionCouldNotTakeSnapshotException;
import co.cask.tephra.TransactionNotInProgressException;
import co.cask.tephra.TransactionSystemClient;
import co.cask.tephra.TxConstants;
import co.cask.tephra.distributed.thrift.TInvalidTruncateTimeException;
import co.cask.tephra.distributed.thrift.TTransactionCouldNotTakeSnapshotException;
import co.cask.tephra.distributed.thrift.TTransactionNotInProgressException;
import co.cask.tephra.runtime.ConfigModule;
import co.cask.tephra.runtime.DiscoveryModules;
import co.cask.tephra.runtime.TransactionClientModule;
import co.cask.tephra.runtime.TransactionModules;
import co.cask.tephra.runtime.ZKModule;
import co.cask.tephra.util.ConfigurationFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import org.apache.hadoop.conf.Configuration;
import org.apache.thrift.TException;
import org.apache.twill.zookeeper.ZKClientService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;

/**
 * A tx service client
 */
public class TransactionServiceClient implements TransactionSystemClient {

    private static final Logger LOG = LoggerFactory.getLogger(TransactionServiceClient.class);

    // we will use this to provide every call with an tx client
    private ThriftClientProvider clientProvider;

    // the retry strategy we will use
    private final RetryStrategyProvider retryStrategyProvider;

    /**
     * Utility to be used for basic verification of transaction system availability and functioning
     * @param args arguments list, accepts single option "-v" that makes it to print out more details about started tx
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        if (args.length > 1 || (args.length == 1 && !"-v".equals(args[0]))) {
            System.out.println("USAGE: TransactionServiceClient [-v]");
        }

        boolean verbose = false;
        if (args.length == 1 && "-v".equals(args[0])) {
            verbose = true;
        }
        doMain(verbose, new ConfigurationFactory().get());
    }

    @VisibleForTesting
    public static void doMain(boolean verbose, Configuration conf) throws Exception {
        LOG.info("Starting tx server client test.");
        Injector injector = Guice.createInjector(new ConfigModule(conf), new ZKModule(),
                new DiscoveryModules().getDistributedModules(), new TransactionModules().getDistributedModules(),
                new TransactionClientModule());

        ZKClientService zkClient = injector.getInstance(ZKClientService.class);
        zkClient.startAndWait();

        try {
            TransactionServiceClient client = injector.getInstance(TransactionServiceClient.class);
            LOG.info("Starting tx...");
            Transaction tx = client.startShort();
            if (verbose) {
                LOG.info("Started tx details: " + tx.toString());
            } else {
                LOG.info("Started tx: " + tx.getTransactionId() + ", readPointer: " + tx.getReadPointer()
                        + ", invalids: " + tx.getInvalids().length + ", inProgress: " + tx.getInProgress().length);
            }
            LOG.info("Checking if canCommit tx...");
            boolean canCommit = client.canCommit(tx, Collections.<byte[]>emptyList());
            LOG.info("canCommit: " + canCommit);
            if (canCommit) {
                LOG.info("Committing tx...");
                boolean committed = client.commit(tx);
                LOG.info("Committed tx: " + committed);
                if (!committed) {
                    LOG.info("Aborting tx...");
                    client.abort(tx);
                    LOG.info("Aborted tx...");
                }
            } else {
                LOG.info("Aborting tx...");
                client.abort(tx);
                LOG.info("Aborted tx...");
            }
        } finally {
            zkClient.stopAndWait();
        }
    }

    /**
     * Create from a configuration. This will first attempt to find a zookeeper
     * for service discovery. Otherwise it will look for the port in the
     * config and use localhost.
     * @param config a configuration containing the zookeeper properties
     */
    @Inject
    public TransactionServiceClient(Configuration config, ThriftClientProvider clientProvider) {

        // initialize the retry logic
        String retryStrat = config.get(TxConstants.Service.CFG_DATA_TX_CLIENT_RETRY_STRATEGY,
                TxConstants.Service.DEFAULT_DATA_TX_CLIENT_RETRY_STRATEGY);
        if ("backoff".equals(retryStrat)) {
            this.retryStrategyProvider = new RetryWithBackoff.Provider();
        } else if ("n-times".equals(retryStrat)) {
            this.retryStrategyProvider = new RetryNTimes.Provider();
        } else {
            String message = "Unknown Retry Strategy '" + retryStrat + "'.";
            LOG.error(message);
            throw new IllegalArgumentException(message);
        }
        this.retryStrategyProvider.configure(config);
        LOG.debug("Retry strategy is " + this.retryStrategyProvider);

        this.clientProvider = clientProvider;
    }

    /**
     * This is an abstract class that encapsulates an operation. It provides a
     * method to attempt the actual operation, and it can throw an operation
     * exception.
     * @param <T> The return type of the operation
     */
    abstract static class Operation<T> {

        /** the name of the operation. */
        String name;

        /** constructor with name of operation. */
        Operation(String name) {
            this.name = name;
        }

        /** return the name of the operation. */
        String getName() {
            return name;
        }

        /** execute the operation, given an tx client. */
        abstract T execute(TransactionServiceThriftClient client) throws Exception;
    }

    /** see execute(operation, client). */
    private <T> T execute(Operation<T> operation) throws Exception {
        return execute(operation, null);
    }

    /**
     * This is a generic method implementing the somewhat complex execution
     * and retry logic for operations, to avoid repetitive code.
     *
     * Attempts to execute one operation, by obtaining an tx client from
     * the client provider and passing the operation to the client. If the
     * call fails with a Thrift exception, apply the retry strategy. If no
     * more retries are to be made according to the strategy, call the
     * operation's error method to obtain a value to return. Note that error()
     * may also throw an exception. Note also that the retry logic is only
     * applied for thrift exceptions.
     *
     * @param operation The operation to be executed
     * @param provider An tx client provider. If null, then a client will be
     *                 obtained using the client provider
     * @param <T> The return type of the operation
     * @return the result of the operation, or a value returned by error()
     */
    private <T> T execute(Operation<T> operation, ThriftClientProvider provider) throws Exception {
        RetryStrategy retryStrategy = retryStrategyProvider.newRetryStrategy();
        while (true) {
            // did we get a custom client provider or do we use the default?
            if (provider == null) {
                provider = this.clientProvider;
            }
            // this will throw a TException if it cannot get a client
            try (CloseableThriftClient closeable = provider.getCloseableClient()) {
                // note that this can throw exceptions other than TException
                return operation.execute(closeable.getThriftClient());

            } catch (TException te) {
                // determine whether we should retry
                boolean retry = retryStrategy.failOnce();
                if (!retry) {
                    // retry strategy is exceeded, throw an operation exception
                    String message = "Thrift error for " + operation + ": " + te.getMessage();
                    LOG.error(message);
                    LOG.debug(message, te);
                    throw new Exception(message, te);
                } else {
                    // call retry strategy before retrying
                    retryStrategy.beforeRetry();
                    String msg = "Retrying " + operation.getName() + " after Thrift error: " + te.getMessage();
                    LOG.info(msg);
                    LOG.debug(msg, te);
                }

            }
        }
    }

    @Override
    public Transaction startLong() {
        try {
            return execute(new Operation<Transaction>("startLong") {
                @Override
                public Transaction execute(TransactionServiceThriftClient client) throws TException {
                    return client.startLong();
                }
            });
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public Transaction startShort() {
        try {
            return execute(new Operation<Transaction>("startShort") {
                @Override
                public Transaction execute(TransactionServiceThriftClient client) throws TException {
                    return client.startShort();
                }
            });
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public Transaction startShort(final int timeout) {
        try {
            return execute(new Operation<Transaction>("startShort") {
                @Override
                public Transaction execute(TransactionServiceThriftClient client) throws TException {
                    return client.startShort(timeout);
                }
            });
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public boolean canCommit(final Transaction tx, final Collection<byte[]> changeIds)
            throws TransactionNotInProgressException {

        try {
            return execute(new Operation<Boolean>("canCommit") {
                @Override
                public Boolean execute(TransactionServiceThriftClient client) throws Exception {
                    return client.canCommit(tx, changeIds);
                }
            });
        } catch (TransactionNotInProgressException e) {
            throw e;
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public boolean commit(final Transaction tx) throws TransactionNotInProgressException {
        try {
            return this.execute(new Operation<Boolean>("commit") {
                @Override
                public Boolean execute(TransactionServiceThriftClient client) throws Exception {
                    return client.commit(tx);
                }
            });
        } catch (TransactionNotInProgressException e) {
            throw e;
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public void abort(final Transaction tx) {
        try {
            this.execute(new Operation<Boolean>("abort") {
                @Override
                public Boolean execute(TransactionServiceThriftClient client) throws TException {
                    client.abort(tx);
                    return true;
                }
            });
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public boolean invalidate(final long tx) {
        try {
            return this.execute(new Operation<Boolean>("invalidate") {
                @Override
                public Boolean execute(TransactionServiceThriftClient client) throws TException {
                    return client.invalidate(tx);
                }
            });
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public Transaction checkpoint(final Transaction tx) throws TransactionNotInProgressException {
        try {
            return this.execute(new Operation<Transaction>("checkpoint") {
                @Override
                Transaction execute(TransactionServiceThriftClient client) throws Exception {
                    return client.checkpoint(tx);
                }
            });
        } catch (TransactionNotInProgressException e) {
            throw e;
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public InputStream getSnapshotInputStream() throws TransactionCouldNotTakeSnapshotException {
        try {
            return this.execute(new Operation<InputStream>("takeSnapshot") {
                @Override
                public InputStream execute(TransactionServiceThriftClient client) throws Exception {
                    return client.getSnapshotStream();
                }
            });
        } catch (TransactionCouldNotTakeSnapshotException e) {
            throw e;
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public String status() {
        try {
            return this.execute(new Operation<String>("status") {
                @Override
                public String execute(TransactionServiceThriftClient client) throws Exception {
                    return client.status();
                }
            });
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public void resetState() {
        try {
            this.execute(new Operation<Boolean>("resetState") {
                @Override
                public Boolean execute(TransactionServiceThriftClient client) throws TException {
                    client.resetState();
                    return true;
                }
            });
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public boolean truncateInvalidTx(final Set<Long> invalidTxIds) {
        try {
            return this.execute(new Operation<Boolean>("truncateInvalidTx") {
                @Override
                public Boolean execute(TransactionServiceThriftClient client) throws TException {
                    return client.truncateInvalidTx(invalidTxIds);
                }
            });
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public boolean truncateInvalidTxBefore(final long time) throws InvalidTruncateTimeException {
        try {
            return this.execute(new Operation<Boolean>("truncateInvalidTxBefore") {
                @Override
                public Boolean execute(TransactionServiceThriftClient client) throws Exception {
                    return client.truncateInvalidTxBefore(time);
                }
            });
        } catch (InvalidTruncateTimeException e) {
            throw e;
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public int getInvalidSize() {
        try {
            return this.execute(new Operation<Integer>("getInvalidSize") {
                @Override
                public Integer execute(TransactionServiceThriftClient client) throws TException {
                    return client.getInvalidSize();
                }
            });
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }
}