Package org.voltdb.jdbc

Source Code of org.voltdb.jdbc.JDBC4ClientConnection$TrackingCallback

/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.voltdb.jdbc;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import org.voltdb.client.Client;
import org.voltdb.client.ClientConfig;
import org.voltdb.client.ClientFactory;
import org.voltdb.client.ClientImpl;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.ClientStats;
import org.voltdb.client.ClientStatsContext;
import org.voltdb.client.NoConnectionsException;
import org.voltdb.client.ProcCallException;
import org.voltdb.client.ProcedureCallback;

/**
* Provides a high-level wrapper around the core {@link Client} class to provide performance
* tracking, connection pooling and Future-based asynchronous execution support. ClientConnections
* should be obtained through the {@link JDBC4ClientConnectionPool} get methods and cannot be
* instantiated directly.
*
* Extending ClientStatusListenerExt allows us to detect dropped connections, etc..
*
* @author Seb Coursol (copied and renamed from exampleutils)
* @since 2.0
*/
public class JDBC4ClientConnection implements Closeable {
    private final ArrayList<String> servers;
    private final ClientConfig config;
    private AtomicReference<Client> client = new AtomicReference<Client>();

    /**
     * The base hash/key for this connection, that uniquely identifies its parameters, as defined by
     * the pool.
     */
    protected final String keyBase;

    /**
     * The actual hash/key for this connection, that uniquely identifies this specific native
     * {@link Client} wrapper.
     */
    protected final String key;

    /**
     * The number of active users on the connection. Used and managed by the pool to determine when
     * a specific {@link Client} wrapper has reached capacity (and a new one should be created).
     */
    protected short users = 0;

    /**
     * The default asynchronous operation timeout for Future-based executions (while the operation
     * may so time out on the client side, note that, technically, once submitted to the database
     * cluster, the call cannot be cancelled!).
     */
    protected long defaultAsyncTimeout = 60000;

    /**
     * Creates a new native client wrapper from the given parameters (internal use only).
     *
     * @param clientConnectionKeyBase
     *            the base hash/key for this connection, as defined by the pool.
     * @param clientConnectionKey
     *            the actual hash/key for this connection, as defined by the pool (may contain a
     *            trailing index when the pool decides a new client needs to be created based on the
     *            number of clients).
     * @param servers
     *            the list of VoltDB servers to connect to in hostname[:port] format.
     * @param user
     *            the user name to use when connecting to the server(s).
     * @param password
     *            the password to use when connecting to the server(s).
     * @param isHeavyWeight
     *            the flag indicating callback processes on this connection will be heavy (long
     *            running callbacks). By default the connection only allocates one background
     *            processing thread to process callbacks. If those callbacks run for a long time,
     *            the network stack can get clogged with pending responses that have yet to be
     *            processed, at which point the server will disconnect the application, thinking it
     *            died and is not reading responses as fast as it is pushing requests. When the flag
     *            is set to 'true', an additional 2 processing thread will deal with processing
     *            callbacks, thus mitigating the issue.
     * @param maxOutstandingTxns
     *            the number of transactions the client application may push against a specific
     *            connection before getting blocked on back-pressure. By default the connection
     *            allows 3,000 open transactions before preventing the client from posting more
     *            work, thus preventing server fire-hosing. In some cases however, with very fast,
     *            small transactions, this limit can be raised.
     * @throws IOException
     * @throws UnknownHostException
     */
    protected JDBC4ClientConnection(
            String clientConnectionKeyBase, String clientConnectionKey,
            String[] servers, String user, String password, boolean isHeavyWeight,
            int maxOutstandingTxns)
                    throws UnknownHostException, IOException
    {
        // Save the list of trimmed non-empty server names.
        this.servers = new ArrayList<String>(servers.length);
        for (String server : servers) {
            server = server.trim();
            if (!server.isEmpty()) {
                this.servers.add(server);
            }
        }
        if (this.servers.isEmpty()) {
            throw new UnknownHostException("JDBC4ClientConnection: no servers provided");
        }

        this.keyBase = clientConnectionKeyBase;
        this.key = clientConnectionKey;

        // Create configuration
        this.config = new ClientConfig(user, password);
        config.setHeavyweight(isHeavyWeight);
        if (maxOutstandingTxns > 0)
            config.setMaxOutstandingTxns(maxOutstandingTxns);

        // Create client and connect.
        createClientAndConnect();
    }

    /**
     * Private method to (re)initialize a client connection.
     * @return new ClientImpl
     * @throws UnknownHostException
     * @throws IOException
     */
    private ClientImpl createClientAndConnect() throws UnknownHostException, IOException
    {
        // Make client connections.
        ClientImpl clientTmp = (ClientImpl) ClientFactory.createClient(this.config);
        // ENG-6231: Only fail if we can't connect to any of the provided servers.
        boolean connectedAnything = false;
        for (String server : this.servers) {
            try {
                clientTmp.createConnection(server);
                connectedAnything = true;
            }
            catch (UnknownHostException e) {
            }
            catch (IOException e) {
            }
        }

        if (!connectedAnything) {
            try {
                clientTmp.close();
            } catch (InterruptedException ie) {}
            throw new IOException("Unable to connect to VoltDB cluster with servers: " + this.servers);
        }

        this.client.set(clientTmp);
        this.users++;
        return clientTmp;
    }

    /**
     * Get current client or reconnect one as needed.
     * Concurrency strategy: If the connection is lost while providing one the
     * caller will get a non-null, but ultimately bad connection that will fail.
     * But it won't cause an NPE. This method is synchronized so that a
     * reconnection won't happen simultaneously. The client won't get dropped
     * if a parallel thread comes in later trying to drop the original client
     * because dropClient() only does it if the request matches the current client.
     * @return  client
     * @throws UnknownHostException
     * @throws IOException
     */
    protected synchronized ClientImpl getClient() throws UnknownHostException, IOException
    {
        ClientImpl retClient = (ClientImpl) this.client.get() ;
        if (retClient != null) {
            return retClient;
        }
        return this.createClientAndConnect();
    }

    /**
     * Used by the pool to indicate a new thread/user is using a specific connection, helping the
     * pool determine when new connections need to be created.
     *
     * @return the reference to this connection to be returned to the calling user.
     */
    protected synchronized JDBC4ClientConnection use() {
        this.users++;
        return this;
    }

    /**
     * Used by the pool to indicate a thread/user has stopped using the connection (and optionally
     * close the underlying client if there are no more users against it).
     */
    protected synchronized void dispose() {
        this.users--;
        if (this.users == 0) {
            try {
                Client currentClient = this.client.get();
                if (currentClient != null) {
                    currentClient.close();
                }
            } catch (Exception x) {
                // ignore
            }
        }
    }

    /**
     * Drop the client connection, e.g. when a NoConnectionsException is caught.
     * It will try to reconnect as needed and appropriate.
     * @param clientToDrop caller-provided client to avoid re-nulling from another thread that comes in later
     */
    protected synchronized void dropClient(ClientImpl clientToDrop) {
        Client currentClient = this.client.get();
        if (currentClient != null && currentClient == clientToDrop) {
            try {
                currentClient.close();
                this.client.set(null);
            }
            catch (Exception x) {
                // ignore
            }
        }
        this.users = 0;
    }

    /**
     * Closes the connection, releasing it to the pool so another thread/client may pick it up. This
     * method must be closed by a user when the connection is no longer needed to avoid pool
     * pressure and leaks where the pool would keep creating new connections all the time,
     * wrongfully believing all existing connections to be actively used.
     */
    @Override
    public void close() {
        JDBC4ClientConnectionPool.dispose(this);
    }

    /**
     * Executes a procedure synchronously and returns the result to the caller. The method
     * internally tracks execution performance.
     *
     * @param procedure
     *            the name of the procedure to call.
     * @param parameters
     *            the list of parameters to pass to the procedure.
     * @return the response sent back by the VoltDB cluster for the procedure execution.
     * @throws IOException
     * @throws NoConnectionsException
     * @throws ProcCallException
     */
    public ClientResponse execute(String procedure, long timeout, Object... parameters)
            throws NoConnectionsException, IOException, ProcCallException {
        long start = System.currentTimeMillis();
        ClientImpl currentClient = this.getClient();
        try {
            // If connections are lost try reconnecting.
            ClientResponse response = currentClient.callProcedureWithTimeout(procedure, timeout, TimeUnit.SECONDS, parameters);
            return response;
        }
        catch (ProcCallException pce) {
            throw pce;
        }
        catch (NoConnectionsException e) {
            this.dropClient(currentClient);
            throw e;
        }
    }

    /**
     * Internal asynchronous callback used to track the execution performance of asynchronous calls.
     */
    private static class TrackingCallback implements ProcedureCallback {
        private final JDBC4ClientConnection Owner;
        private final String Procedure;
        private final ProcedureCallback UserCallback;

        /**
         * Creates a new callback.
         *
         * @param owner
         *            the connection to which the request was sent (and that will be receiving the
         *            response).
         * @param procedure
         *            the procedure being executed and for which we're awaiting a response.
         * @param userCallback
         *            the user-specified callback that will be called once we have tracked
         *            statistics, making this internal callback transparent to the calling
         *            application.
         */
        public TrackingCallback(JDBC4ClientConnection owner, String procedure,
                ProcedureCallback userCallback) {
            this.Owner = owner;
            this.Procedure = procedure;
            this.UserCallback = userCallback;
        }

        /**
         * Processes the server response, tracking performance statistics internally, then calling
         * the user-specified callback (if any).
         */
        @Override
        public void clientCallback(ClientResponse response) throws Exception {
            if (this.UserCallback != null)
                this.UserCallback.clientCallback(response);
        }
    }

    /**
     * Executes a procedure asynchronously, then calls the provided user callback with the server
     * response upon completion.
     *
     * @param callback
     *            the user-specified callback to call with the server response upon execution
     *            completion.
     * @param procedure
     *            the name of the procedure to call.
     * @param parameters
     *            the list of parameters to pass to the procedure.
     * @return the result of the submission false if the client connection was terminated and unable
     *         to post the request to the server, true otherwise.
     */
    public boolean executeAsync(ProcedureCallback callback, String procedure, Object... parameters)
            throws NoConnectionsException, IOException
    {
        ClientImpl currentClient = this.getClient();
        try {
            return currentClient.callProcedure(new TrackingCallback(this, procedure, callback),
                    procedure, parameters);
        }
        catch (NoConnectionsException e) {
            this.dropClient(currentClient);
            throw e;
        }
    }

    /**
     * Executes a procedure asynchronously, returning a Future that can be used by the caller to
     * wait upon completion before processing the server response.
     *
     * @param procedure
     *            the name of the procedure to call.
     * @param parameters
     *            the list of parameters to pass to the procedure.
     * @return the Future created to wrap around the asynchronous process.
     */
    public Future<ClientResponse> executeAsync(String procedure, Object... parameters)
            throws NoConnectionsException, IOException
    {
        ClientImpl currentClient = this.getClient();
        final JDBC4ExecutionFuture future = new JDBC4ExecutionFuture(this.defaultAsyncTimeout);
        try {
            currentClient.callProcedure(new TrackingCallback(this, procedure, new ProcedureCallback() {
                @SuppressWarnings("unused")
                final JDBC4ExecutionFuture result;
                {
                    this.result = future;
                }

                @Override
                public void clientCallback(ClientResponse response) throws Exception {
                    future.set(response);
                }
            }), procedure, parameters);
        }
        catch (NoConnectionsException e) {
            this.dropClient(currentClient);
            throw e;
        }
        return future;
    }


    /**
     * Gets the new version of the performance statistics for this connection only.
     * @return A {@link ClientStatsContext} that correctly represents the client statistics.
     */
    public ClientStatsContext getClientStatsContext() {
        if (this.client.get() == null) {
            return null;
        }
        return this.client.get().createStatsContext();
    }

    /**
     * Save statistics to a CSV file.
     *
     * @param file
     *            File path
     * @throws IOException
     */
    public void saveStatistics(ClientStats stats, String file) throws IOException {
        this.client.get().writeSummaryCSV(stats, file);
    }

    void writeSummaryCSV(ClientStats stats, String path) throws IOException {
        if (this.client.get() == null) {
            throw new IOException("Client is unavailable for writing summary CSV.");
        }
        this.client.get().writeSummaryCSV(stats, path);
    }

    /**
     * Block the current thread until all queued stored procedure invocations have received
     * responses or there are no more connections to the cluster
     *
     * @throws InterruptedException
     * @throws IOException
     * @see Client#drain()
     */
    public void drain() throws InterruptedException, IOException {
        ClientImpl currentClient = this.getClient();
        if (currentClient == null) {
            throw new IOException("Client is unavailable for drain().");
        }
        currentClient.drain();
    }

    /**
     * Blocks the current thread until there is no more backpressure or there are no more
     * connections to the database
     *
     * @throws InterruptedException
     * @throws IOException
     */
    public void backpressureBarrier() throws InterruptedException, IOException {
        ClientImpl currentClient = this.getClient();
        if (currentClient == null) {
            throw new IOException("Client is unavailable for backpressureBarrier().");
        }
        currentClient.backpressureBarrier();
    }

    /**
     * Synchronously invokes UpdateApplicationCatalog procedure. Blocks until a result is available.
     * A {@link ProcCallException} is thrown if the response is anything other then success.
     *
     * @param catalogPath
     *            Path to the catalog jar file.
     * @param deploymentPath
     *            Path to the deployment file
     * @return array of VoltTable results
     * @throws IOException
     *             If the files cannot be serialized
     * @throws NoConnectionException
     * @throws ProcCallException
     */
    public ClientResponse updateApplicationCatalog(File catalogPath, File deploymentPath)
            throws IOException, NoConnectionsException, ProcCallException
    {
        ClientImpl currentClient = this.getClient();
        try {
            return currentClient.updateApplicationCatalog(catalogPath, deploymentPath);
        }
        catch (NoConnectionsException e) {
            this.dropClient(currentClient);
            throw e;
        }
    }
}
TOP

Related Classes of org.voltdb.jdbc.JDBC4ClientConnection$TrackingCallback

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.