package org.apache.blur.thrift; /** * 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. */ import static org.apache.blur.utils.BlurConstants.BLUR_CLIENTPOOL_CLIENT_CLEAN_FREQUENCY; import static org.apache.blur.utils.BlurConstants.BLUR_CLIENTPOOL_CLIENT_MAX_CONNECTIONS_PER_HOST; import static org.apache.blur.utils.BlurConstants.BLUR_CLIENTPOOL_CLIENT_STALE_THRESHOLD; import static org.apache.blur.utils.BlurConstants.BLUR_THRIFT_MAX_FRAME_SIZE; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.Proxy.Type; import java.net.Socket; import java.net.SocketAddress; import java.util.Map; import java.util.Map.Entry; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.blur.BlurConfiguration; import org.apache.blur.log.Log; import org.apache.blur.log.LogFactory; import org.apache.blur.thirdparty.thrift_0_9_0.TException; import org.apache.blur.thirdparty.thrift_0_9_0.protocol.TBinaryProtocol; import org.apache.blur.thirdparty.thrift_0_9_0.protocol.TCompactProtocol; import org.apache.blur.thirdparty.thrift_0_9_0.protocol.TProtocol; import org.apache.blur.thirdparty.thrift_0_9_0.transport.TFramedTransport; import org.apache.blur.thirdparty.thrift_0_9_0.transport.TSocket; import org.apache.blur.thirdparty.thrift_0_9_0.transport.TTransport; import org.apache.blur.thirdparty.thrift_0_9_0.transport.TTransportException; import org.apache.blur.thrift.generated.Blur.Client; import org.apache.blur.thrift.generated.SafeClientGen; import org.apache.blur.thrift.sasl.SaslHelper; public class ClientPool { private static final Log LOG = LogFactory.getLog(ClientPool.class); private static final Map<Connection, BlockingQueue<Client>> _connMap = new ConcurrentHashMap<Connection, BlockingQueue<Client>>(); private static final int _maxFrameSize; private static final int _maxConnectionsPerHost; private static final long _idleTimeBeforeClosingClient; private static final long _clientPoolCleanFrequency; private static final Timer _master; private static final BlurConfiguration _configurationFromClassPath; private BlurConfiguration _configuration = _configurationFromClassPath; static { try { _configurationFromClassPath = new BlurConfiguration(); int maxConnectionsPerHost = _configurationFromClassPath.getInt(BLUR_CLIENTPOOL_CLIENT_MAX_CONNECTIONS_PER_HOST, 64); if (maxConnectionsPerHost < 1) { LOG.fatal("Max connections per host cannot be less than 1 current value [{0}] using 1.", maxConnectionsPerHost); maxConnectionsPerHost = 1; } _maxConnectionsPerHost = maxConnectionsPerHost; _idleTimeBeforeClosingClient = TimeUnit.SECONDS.toNanos(_configurationFromClassPath.getLong( BLUR_CLIENTPOOL_CLIENT_STALE_THRESHOLD, 30)); _clientPoolCleanFrequency = TimeUnit.SECONDS.toMillis(_configurationFromClassPath.getLong( BLUR_CLIENTPOOL_CLIENT_CLEAN_FREQUENCY, 10)); _maxFrameSize = _configurationFromClassPath.getInt(BLUR_THRIFT_MAX_FRAME_SIZE, 16384000); } catch (Exception e) { throw new RuntimeException(e); } _master = checkAndRemoveStaleClients(); } public void setBlurConfiguration(BlurConfiguration configuration) { _configuration = configuration; } public static void close() { _master.cancel(); _master.purge(); } private static Timer checkAndRemoveStaleClients() { Timer master = new Timer("Blur-Client-Connection-Cleaner", true); master.schedule(new TimerTask() { @Override public void run() { try { for (Entry<Connection, BlockingQueue<Client>> e : _connMap.entrySet()) { testConnections(e.getKey(), e.getValue()); } } catch (Throwable t) { LOG.error("Unknown error while trying to clean up connections.", t); } } }, getClientPoolCleanFrequency(), getClientPoolCleanFrequency()); return master; } private static void testConnections(Connection connection, BlockingQueue<Client> clients) { int size = clients.size(); LOG.debug("Testing clients for connection [{0}] has size of [{1}]", connection, size); for (int i = 0; i < size; i++) { WeightedClient weightedClient = (WeightedClient) clients.poll(); if (weightedClient == null) { return; } if (weightedClient.isStale()) { if (testClient(connection, weightedClient)) { tryToReturnToQueue(clients, weightedClient); } else { LOG.error("Closing potentially bad client [{0}]", weightedClient); close(weightedClient); } } else { tryToReturnToQueue(clients, weightedClient); } } } private static void tryToReturnToQueue(BlockingQueue<Client> clients, WeightedClient weightedClient) { LOG.debug("Offering client [{0}] to queue.", weightedClient); if (!clients.offer(weightedClient)) { // Close client LOG.info("Too many clients in pool, closing client [{0}]", weightedClient); close(weightedClient); } } private class WeightedClient extends SafeClientGen { private long _lastUse = System.nanoTime(); private final String _id; public WeightedClient(TProtocol prot, String id) { super(prot); _id = id; } public void touch() { _lastUse = System.nanoTime(); } public boolean isStale() { long diff = System.nanoTime() - _lastUse; return diff >= getClientIdleTimeThreshold(); } @Override public String toString() { return _id; } } private static long getClientIdleTimeThreshold() { return _idleTimeBeforeClosingClient; } private static long getClientPoolCleanFrequency() { return _clientPoolCleanFrequency; } public void returnClient(Connection connection, Client client) { BlockingQueue<Client> queue = getQueue(connection); WeightedClient weightedClient = (WeightedClient) client; weightedClient.touch(); tryToReturnToQueue(queue, weightedClient); } private BlockingQueue<Client> getQueue(Connection connection) { BlockingQueue<Client> blockingQueue = _connMap.get(connection); if (blockingQueue != null) { return blockingQueue; } synchronized (_connMap) { blockingQueue = _connMap.get(connection); if (blockingQueue == null) { blockingQueue = getNewQueue(); _connMap.put(connection, blockingQueue); } } return _connMap.get(connection); } public void trashConnections(Connection connection, Client client) { BlockingQueue<Client> blockingQueue; synchronized (_connMap) { blockingQueue = _connMap.put(connection, getNewQueue()); try { blockingQueue.put(client); } catch (InterruptedException e) { throw new RuntimeException(e); } } LOG.debug("Trashing client for connections [{0}]", connection); for (Client c : blockingQueue) { close(c); } } private BlockingQueue<Client> getNewQueue() { return new LinkedBlockingQueue<Client>(_maxConnectionsPerHost); } /** * Get a client from the pool or creates a new one if the pool is empty. Also * the clients are tested before being returned. * * @param connection * @return * @throws TTransportException * @throws IOException */ public Client getClient(Connection connection) throws TTransportException, IOException { BlockingQueue<Client> blockingQueue = getQueue(connection); if (blockingQueue.isEmpty()) { LOG.debug("New client for connection [{0}]", connection); return newClient(connection); } while (true) { WeightedClient client = (WeightedClient) blockingQueue.poll(); if (client == null) { LOG.debug("New client for connection [{0}]", connection); return newClient(connection); } if (client.isStale()) { // Test client if (testClient(connection, client)) { return refresh(client); } } else { return refresh(client); } } } private Client refresh(WeightedClient client) throws IOException { try { client.refresh(); } catch (TException e) { throw new IOException(e); } return client; } public Client newClient(Connection connection) throws TTransportException, IOException { String host = connection.getHost(); int port = connection.getPort(); TProtocol proto; Socket socket; int timeout = connection.getTimeout(); if (SaslHelper.isSaslEnabled(_configuration)) { if (connection.isProxy()) { throw new IOException("Proxy connections are not allowed when SASL is enabled."); } TSocket transport = new TSocket(host, port, timeout); TTransport tSaslClientTransport = SaslHelper.getTSaslClientTransport(_configuration, transport); tSaslClientTransport.open(); socket = transport.getSocket(); proto = new TCompactProtocol(tSaslClientTransport); } else { if (connection.isProxy()) { Proxy proxy = new Proxy(Type.SOCKS, new InetSocketAddress(connection.getProxyHost(), connection.getProxyPort())); socket = new Socket(proxy); } else { socket = new Socket(); } socket.setTcpNoDelay(true); socket.setSoTimeout(timeout); socket.connect(new InetSocketAddress(host, port), timeout); TSocket trans = new TSocket(socket); proto = new TBinaryProtocol(new TFramedTransport(trans, _maxFrameSize)); } return new WeightedClient(proto, getIdentifer(socket)); } private String getIdentifer(Socket socket) { SocketAddress localSocketAddress = socket.getLocalSocketAddress(); SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress(); return localSocketAddress.toString() + " -> " + remoteSocketAddress.toString(); } private static boolean testClient(Connection connection, WeightedClient weightedClient) { LOG.debug("Testing client, could be stale. Client [{0}] for connection [{1}]", weightedClient, connection); try { weightedClient.refresh(); weightedClient.ping(); weightedClient.touch(); return true; } catch (TException e) { LOG.error("Client test failed. Destroying client. Client [{0}] for connection [{1}]", weightedClient, connection); return false; } } public static void close(Client client) { try { client.getInputProtocol().getTransport().close(); client.getOutputProtocol().getTransport().close(); } catch (Exception e) { LOG.error("Error during closing of client [{0}].", client); } } }