com.whalin.MemCached.SockIOPool Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Memcached-Java-Client Show documentation
Show all versions of Memcached-Java-Client Show documentation
high performance memcached java client
The newest version!
/**
* Copyright (c) 2008 Greg Whalin
* All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the BSD license
*
* This library 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.
*
* You should have received a copy of the BSD License along with this
* library.
*
* @author greg whalin
*/
/*******************************************************************************
* Copyright (c) 2009 Schooner Information Technology, Inc.
* All rights reserved.
*
* http://www.schoonerinfotech.com/
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package com.whalin.MemCached;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.schooner.MemCached.SchoonerSockIOPool;
/**
* This class is a connection pool for maintaning a pool of persistent
* connections
* to memcached servers.
*
* The pool must be initialized prior to use. This should typically be early on
* in the lifecycle of the JVM instance.
*
* An example of initializing using defaults:
*
*
*
* static {
* String[] serverlist = { "cache0.server.com:12345", "cache1.server.com:12345" };
* SockIOPool pool = SockIOPool.getInstance();
* pool.setServers(serverlist);
* pool.initialize();
* }
*
*
* An example of initializing using defaults and providing weights for
* servers:
*
*
* static {
* String[] serverlist = { "cache0.server.com:12345", "cache1.server.com:12345" };
* Integer[] weights = { new Integer(5), new Integer(2) };
*
* SockIOPool pool = SockIOPool.getInstance();
* pool.setServers(serverlist);
* pool.setWeights(weights);
* pool.initialize();
* }
*
*
* An example of initializing overriding defaults:
*
*
* static {
* String[] serverlist = { "cache0.server.com:12345", "cache1.server.com:12345" };
* Integer[] weights = { new Integer(5), new Integer(2) };
* int initialConnections = 10;
* int minSpareConnections = 5;
* int maxSpareConnections = 50;
* long maxIdleTime = 1000 * 60 * 30; // 30 minutes
* long maxBusyTime = 1000 * 60 * 5; // 5 minutes
* long maintThreadSleep = 1000 * 5; // 5 seconds
* int socketTimeOut = 1000 * 3; // 3 seconds to block on reads
* int socketConnectTO = 1000 * 3; // 3 seconds to block on initial
* // connections. If 0, then will use blocking
* // connect (default)
* boolean failover = false; // turn off auto-failover in event of server down
* boolean nagleAlg = false; // turn off Nagle's algorithm on all sockets in
* // pool
* boolean aliveCheck = false; // disable health check of socket on checkout
* SockIOPool pool = SockIOPool.getInstance();
* pool.setServers(serverlist);
* pool.setWeights(weights);
* pool.setInitConn(initialConnections);
* pool.setMinConn(minSpareConnections);
* pool.setMaxConn(maxSpareConnections);
* pool.setMaxIdle(maxIdleTime);
* pool.setMaxBusyTime(maxBusyTime);
* pool.setMaintSleep(maintThreadSleep);
* pool.setSocketTO(socketTimeOut);
* pool.setNagle(nagleAlg);
* pool.setHashingAlg(SockIOPool.NEW_COMPAT_HASH);
* pool.setAliveCheck(true);
* pool.initialize();
* }
*
*
* The easiest manner in which to initialize the pool is to set the servers and
* rely on defaults as in the first example.
* After pool is initialized, a client will request a SockIO object by calling
* getSock with the cache key
* The client must always close the SockIO object when finished, which will
* return the connection back to the pool.
* An example of retrieving a SockIO object:
*
*
* SockIOPool.SockIO sock = SockIOPool.getInstance().getSock( key );
* try {
* sock.write( "version\r\n" );
* sock.flush();
* System.out.println( "Version: " + sock.readLine() );
* }
* catch (IOException ioe) { System.out.println( "io exception thrown" ) };
* sock.close();
*
*
* @author greg whalin
* @version 1.5
*/
public class SockIOPool {
// logger
private static Logger log = LoggerFactory.getLogger(SockIOPool.class);
// Constants
private static final Integer ZERO = new Integer(0);
// Pool data
// is initialized
// initial, min and max pool sizes
private int poolMultiplier = 3;
private int minConn = 5;
private int maxConn = 100;
private int maxIdle = 1000 * 60 * 5; // max idle time for avail sockets
private long maxBusyTime = 1000 * 30; // max idle time for avail sockets
private int socketTO = 1000 * 3; // default timeout of socket reads
private int socketConnectTO = 1000 * 3; // default timeout of socket
// connections
// for being alive
private boolean failover = true; // default to failover in event of cache
// server dead
private boolean failback = true; // only used if failover is also set ...
// controls putting a dead server back
// into rotation
private boolean nagle = false; // enable/disable Nagle's algorithm
// it is the fastest
// locks
private final ReentrantLock hostDeadLock = new ReentrantLock();
// dead server map
private Map hostDead;
private Map hostDeadDur;
// map to hold all available sockets
// map to hold busy sockets
// set to hold sockets to close
private Map> availPool;
private Map> busyPool;
private Map deadPool;;
private SchoonerSockIOPool schoonerSockIOPool;
// Constants
// native String.hashCode();
public static final int NATIVE_HASH = SchoonerSockIOPool.NATIVE_HASH;
// original compatibility hashing algorithm (works with other clients)
public static final int OLD_COMPAT_HASH = SchoonerSockIOPool.OLD_COMPAT_HASH;
// new CRC32 based compatibility hashing algorithm (works with other
// clients)
public static final int NEW_COMPAT_HASH = SchoonerSockIOPool.NEW_COMPAT_HASH;
// MD5 Based -- Stops thrashing when a server added or removed
public static final int CONSISTENT_HASH = SchoonerSockIOPool.CONSISTENT_HASH;
// max of 10 minute delay for fall off
public static final long MAX_RETRY_DELAY = SchoonerSockIOPool.MAX_RETRY_DELAY;
// empty constructor
protected SockIOPool() {
}
/**
* Factory to create/retrieve new pools given a unique poolName.
*
* @param poolName
* unique name of the pool
* @return instance of SockIOPool
*/
public static synchronized SockIOPool getInstance(String poolName) {
SockIOPool whalinSockIOPool = new SockIOPool();
whalinSockIOPool.schoonerSockIOPool = SchoonerSockIOPool.getInstance(poolName);
return whalinSockIOPool;
}
public static SockIOPool getInstance(boolean isTcp) {
SockIOPool whalinSockIOPool = new SockIOPool();
whalinSockIOPool.schoonerSockIOPool = SchoonerSockIOPool.getInstance(isTcp);
return whalinSockIOPool;
}
public static SockIOPool getInstance(String poolName, boolean isTcp) {
SockIOPool whalinSockIOPool = new SockIOPool();
whalinSockIOPool.schoonerSockIOPool = SchoonerSockIOPool.getInstance(poolName, isTcp);
return whalinSockIOPool;
}
/**
* Single argument version of factory used for back compat. Simply creates a
* pool named "default".
*
* @return instance of SockIOPool
*/
public static SockIOPool getInstance() {
SockIOPool whalinSockIOPool = new SockIOPool();
whalinSockIOPool.schoonerSockIOPool = SchoonerSockIOPool.getInstance("default");
return whalinSockIOPool;
}
/**
* Sets the list of all cache servers.
*
* @param servers
* String array of servers [host:port]
*/
public void setServers(String[] servers) {
schoonerSockIOPool.setServers(servers);
}
/**
* Returns the current list of all cache servers.
*
* @return String array of servers [host:port]
*/
public String[] getServers() {
return schoonerSockIOPool.getServers();
}
/**
* Sets the list of weights to apply to the server list.
*
* This is an int array with each element corresponding to an element
* in the same position in the server String array.
*
* @param weights
* Integer array of weights
*/
public void setWeights(Integer[] weights) {
schoonerSockIOPool.setWeights(weights);
}
/**
* Returns the current list of weights.
*
* @return int array of weights
*/
public Integer[] getWeights() {
return schoonerSockIOPool.getWeights();
}
/**
* Sets the initial number of connections per server in the available pool.
*
* @param initConn
* int number of connections
*/
public void setInitConn(int initConn) {
schoonerSockIOPool.setInitConn(initConn);
}
/**
* Returns the current setting for the initial number of connections per
* server in the available pool.
*
* @return number of connections
*/
public int getInitConn() {
return schoonerSockIOPool.getInitConn();
}
/**
* Sets the minimum number of spare connections to maintain in our available
* pool.
*
* @param minConn
* number of connections
*/
public void setMinConn(int minConn) {
schoonerSockIOPool.setMinConn(minConn);
}
/**
* Returns the minimum number of spare connections in available pool.
*
* @return number of connections
*/
public int getMinConn() {
return schoonerSockIOPool.getMinConn();
}
/**
* Sets the maximum number of spare connections allowed in our available
* pool.
*
* @param maxConn
* number of connections
*/
public void setMaxConn(int maxConn) {
schoonerSockIOPool.setMaxConn(maxConn);
}
/**
* Returns the maximum number of spare connections allowed in available
* pool.
*
* @return number of connections
*/
public int getMaxConn() {
return schoonerSockIOPool.getMaxConn();
}
/**
* Sets the max busy time for threads in the busy pool.
*
* @param maxBusyTime
* idle time in ms
*/
public void setMaxBusyTime(long maxBusyTime) {
schoonerSockIOPool.setMaxBusyTime(maxBusyTime);
}
/**
* Returns the current max busy setting.
*
* @return max busy setting in ms
*/
public long getMaxBusy() {
return schoonerSockIOPool.getMaxBusy();
}
/**
* Sets the socket timeout for reads.
*
* @param socketTO
* timeout in ms
*/
public void setSocketTO(int socketTO) {
schoonerSockIOPool.setSocketTO(socketTO);
}
/**
* Returns the socket timeout for reads.
*
* @return timeout in ms
*/
public int getSocketTO() {
return schoonerSockIOPool.getSocketTO();
}
/**
* Sets the socket timeout for connect.
*
* @param socketConnectTO
* timeout in ms
*/
public void setSocketConnectTO(int socketConnectTO) {
schoonerSockIOPool.setSocketConnectTO(socketConnectTO);
}
/**
* Returns the socket timeout for connect.
*
* @return timeout in ms
*/
public int getSocketConnectTO() {
return schoonerSockIOPool.getSocketTO();
}
/**
* Sets the max idle time for threads in the available pool.
*
* @param maxIdle
* idle time in ms
*/
public void setMaxIdle(int maxIdle) {
schoonerSockIOPool.setMaxIdle(maxIdle);
}
/**
* Returns the current max idle setting.
*
* @return max idle setting in ms
*/
public int getMaxIdle() {
return schoonerSockIOPool.getMaxIdle();
}
/**
* Set the sleep time between runs of the pool maintenance thread. If set to
* 0, then the maint thread will not be started.
*
* @param maintSleep
* sleep time in ms
*/
public void setMaintSleep(long maintSleep) {
schoonerSockIOPool.setMaintSleep(maintSleep);
}
/**
* Returns the current maint thread sleep time.
*
* @return sleep time in ms
*/
public long getMaintSleep() {
return schoonerSockIOPool.getMaintSleep();
}
/**
* Sets the failover flag for the pool.
*
* If this flag is set to true, and a socket fails to connect,
* the pool will attempt to return a socket from another server
* if one exists. If set to false, then getting a socket
* will return null if it fails to connect to the requested server.
*
* @param failover
* true/false
*/
public void setFailover(boolean failover) {
schoonerSockIOPool.setFailover(failover);
}
/**
* Returns current state of failover flag.
*
* @return true/false
*/
public boolean getFailover() {
return schoonerSockIOPool.getFailover();
}
/**
* Sets the failback flag for the pool.
*
* If this is true and we have marked a host as dead, will try to bring it
* back. If it is false, we will never try to resurrect a dead host.
*
* @param failback
* true/false
*/
public void setFailback(boolean failback) {
schoonerSockIOPool.setFailback(failback);
}
/**
* Returns current state of failover flag.
*
* @return true/false
*/
public boolean getFailback() {
return schoonerSockIOPool.getFailback();
}
/**
* Sets the aliveCheck flag for the pool.
*
* When true, this will attempt to talk to the server on every connection
* checkout to make sure the connection is still valid. This adds extra
* network chatter and thus is defaulted off. May be useful if you want to
* ensure you do not have any problems talking to the server on a dead
* connection.
*
* @param aliveCheck
* true/false
*/
public void setAliveCheck(boolean aliveCheck) {
schoonerSockIOPool.setAliveCheck(aliveCheck);
}
/**
* Returns the current status of the aliveCheck flag.
*
* @return true / false
*/
public boolean getAliveCheck() {
return schoonerSockIOPool.getAliveCheck();
}
/**
* Sets the Nagle alg flag for the pool.
*
* If false, will turn off Nagle's algorithm on all sockets created.
*
* @param nagle
* true/false
*/
public void setNagle(boolean nagle) {
schoonerSockIOPool.setNagle(nagle);
}
/**
* Returns current status of nagle flag
*
* @return true/false
*/
public boolean getNagle() {
return schoonerSockIOPool.getNagle();
}
/**
* Sets the hashing algorithm we will use.
*
* The types are as follows.
*
* SockIOPool.NATIVE_HASH (0) - native String.hashCode() - fast (cached) but
* not compatible with other clients SockIOPool.OLD_COMPAT_HASH (1) -
* original compatibility hashing alg (works with other clients)
* SockIOPool.NEW_COMPAT_HASH (2) - new CRC32 based compatibility hashing
* algorithm (fast and works with other clients)
*
* @param alg
* int value representing hashing algorithm
*/
public void setHashingAlg(int alg) {
schoonerSockIOPool.setHashingAlg(alg);
}
/**
* Returns current status of customHash flag
*
* @return true/false
*/
public int getHashingAlg() {
return schoonerSockIOPool.getHashingAlg();
}
/**
* Initializes the pool.
*/
public void initialize() {
schoonerSockIOPool.initialize();
}
/**
* Returns state of pool.
*
* @return true
if initialized.
*/
public boolean isInitialized() {
return schoonerSockIOPool.isInitialized();
}
/**
* @param key
* @return
*/
public String getHost(String key) {
return schoonerSockIOPool.getHost(key);
}
/**
* Gets the host that a particular key / hashcode resides on.
*
* @param key
* @param hashcode
* @return
*/
public String getHost(String key, Integer hashcode) {
return schoonerSockIOPool.getHost(key, hashcode);
}
/**
* Shuts down the pool.
*
* Cleanly closes all sockets.
* Stops the maint thread.
* Nulls out all internal maps
*/
public void shutDown() {
schoonerSockIOPool.shutDown();
}
/**
* In memcached 1.4+, user can specify memory size for each memcached item.
* You can specify it with parameter "-I" in the server side.
* While in our client side, we make the max memcached item size with the
* default value 1Mb.
* In this scenario, you can extend the size as what you want, please be
* sure you have enough memory in the client side, since we are using direct
* buffer to hold the memory, they will not be free until shutdown.
*
* @param bufferSize
* specified buffer size.
*/
public void setBufferSize(int bufferSize) {
schoonerSockIOPool.setBufferSize(bufferSize);
}
public int getBufferSize() {
return schoonerSockIOPool.getBufferSize();
}
/**
* MemCached client for Java, utility class for Socket IO.
*
* This class is a wrapper around a Socket and its streams.
*
* @author greg whalin
* @author Richard 'toast' Russo
* @version 1.5
*/
public static class SockIO implements LineInputStream {
// logger
private static Logger log = LoggerFactory.getLogger(SockIO.class);
// pool
private SockIOPool pool;
// data
private String host;
private Socket sock;
private DataInputStream in;
private BufferedOutputStream out;
/**
* creates a new SockIO object wrapping a socket connection to
* host:port, and its input and output streams
*
* @param pool
* Pool this object is tied to
* @param host
* host to connect to
* @param port
* port to connect to
* @param timeout
* int ms to block on data for read
* @param connectTimeout
* timeout (in ms) for initial connection
* @param noDelay
* TCP NODELAY option?
* @throws IOException
* if an io error occurrs when creating socket
* @throws UnknownHostException
* if hostname is invalid
*/
public SockIO(SockIOPool pool, String host, int port, int timeout, int connectTimeout, boolean noDelay)
throws IOException, UnknownHostException {
this.pool = pool;
// get a socket channel
sock = getSocket(host, port, connectTimeout);
if (timeout >= 0)
sock.setSoTimeout(timeout);
// testing only
sock.setTcpNoDelay(noDelay);
// wrap streams
in = new DataInputStream(new BufferedInputStream(sock.getInputStream()));
out = new BufferedOutputStream(sock.getOutputStream());
this.host = host + ":" + port;
}
/**
* creates a new SockIO object wrapping a socket connection to
* host:port, and its input and output streams
*
* @param host
* hostname:port
* @param timeout
* read timeout value for connected socket
* @param connectTimeout
* timeout for initial connections
* @param noDelay
* TCP NODELAY option?
* @throws IOException
* if an io error occurrs when creating socket
* @throws UnknownHostException
* if hostname is invalid
*/
public SockIO(SockIOPool pool, String host, int timeout, int connectTimeout, boolean noDelay)
throws IOException, UnknownHostException {
if (pool == null)
return;
this.pool = pool;
String[] ip = host.split(":");
// get socket: default is to use non-blocking connect
sock = getSocket(ip[0], Integer.parseInt(ip[1]), connectTimeout);
if (timeout >= 0)
this.sock.setSoTimeout(timeout);
// testing only
sock.setTcpNoDelay(noDelay);
// wrap streams
in = new DataInputStream(new BufferedInputStream(sock.getInputStream()));
out = new BufferedOutputStream(sock.getOutputStream());
this.host = host;
}
/**
* Method which gets a connection from SocketChannel.
*
* @param host
* host to establish connection to
* @param port
* port on that host
* @param timeout
* connection timeout in ms
*
* @return connected socket
* @throws IOException
* if errors connecting or if connection times out
*/
protected static Socket getSocket(String host, int port, int timeout) throws IOException {
SocketChannel sock = SocketChannel.open();
sock.socket().connect(new InetSocketAddress(host, port), timeout);
return sock.socket();
}
/**
* Lets caller get access to underlying channel.
*
* @return the backing SocketChannel
*/
public SocketChannel getChannel() {
return sock.getChannel();
}
/**
* returns the host this socket is connected to
*
* @return String representation of host (hostname:port)
*/
public String getHost() {
return this.host;
}
/**
* closes socket and all streams connected to it
*
* @throws IOException
* if fails to close streams or socket
*/
public void trueClose() throws IOException {
trueClose(true);
}
/**
* closes socket and all streams connected to it
*
* @throws IOException
* if fails to close streams or socket
*/
public void trueClose(boolean addToDeadPool) throws IOException {
if (log.isDebugEnabled())
log.debug("++++ Closing socket for real: " + toString());
boolean err = false;
StringBuilder errMsg = new StringBuilder();
if (in != null) {
try {
in.close();
} catch (IOException ioe) {
if (log.isErrorEnabled()) {
log.error("++++ error closing input stream for socket: " + toString() + " for host: "
+ getHost());
log.error(ioe.getMessage(), ioe);
}
errMsg.append("++++ error closing input stream for socket: " + toString() + " for host: "
+ getHost() + "\n");
errMsg.append(ioe.getMessage());
err = true;
}
}
if (out != null) {
try {
out.close();
} catch (IOException ioe) {
if (log.isErrorEnabled()) {
log.error("++++ error closing output stream for socket: " + toString() + " for host: "
+ getHost());
log.error(ioe.getMessage(), ioe);
}
errMsg.append("++++ error closing output stream for socket: " + toString() + " for host: "
+ getHost() + "\n");
errMsg.append(ioe.getMessage());
err = true;
}
}
if (sock != null) {
try {
sock.close();
} catch (IOException ioe) {
if (log.isErrorEnabled()) {
log.error("++++ error closing socket: " + toString() + " for host: " + getHost());
log.error(ioe.getMessage(), ioe);
}
errMsg.append("++++ error closing socket: " + toString() + " for host: " + getHost() + "\n");
errMsg.append(ioe.getMessage());
err = true;
}
}
// check in to pool
if (addToDeadPool && sock != null)
pool.checkIn(this, false);
in = null;
out = null;
sock = null;
if (err)
throw new IOException(errMsg.toString());
}
/**
* sets closed flag and checks in to connection pool but does not close
* connections
*/
public void close() {
// check in to pool
if (log.isDebugEnabled())
log.debug("++++ marking socket (" + this.toString()
+ ") as closed and available to return to avail pool");
pool.checkIn(this);
}
/**
* checks if the connection is open
*
* @return true if connected
*/
protected boolean isConnected() {
return (sock != null && sock.isConnected());
}
/*
* checks to see that the connection is still working
*
* @return true if still alive
*/
public boolean isAlive() {
if (!isConnected())
return false;
// try to talk to the server w/ a dumb query to ask its version
try {
this.write("version\r\n".getBytes());
this.flush();
this.readLine();
} catch (IOException ex) {
return false;
}
return true;
}
/**
* reads a line intentionally not using the deprecated readLine method
* from DataInputStream
*
* @return String that was read in
* @throws IOException
* if io problems during read
*/
public String readLine() throws IOException {
if (sock == null || !sock.isConnected()) {
if (log.isErrorEnabled())
log.error("++++ attempting to read from closed socket");
throw new IOException("++++ attempting to read from closed socket");
}
byte[] b = new byte[1];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
boolean eol = false;
while (in.read(b, 0, 1) != -1) {
if (b[0] == 13) {
eol = true;
} else {
if (eol) {
if (b[0] == 10)
break;
eol = false;
}
}
// cast byte into char array
bos.write(b, 0, 1);
}
if (bos == null || bos.size() <= 0) {
throw new IOException("++++ Stream appears to be dead, so closing it down");
}
// else return the string
return bos.toString().trim();
}
/**
* reads up to end of line and returns nothing
*
* @throws IOException
* if io problems during read
*/
public void clearEOL() throws IOException {
if (sock == null || !sock.isConnected()) {
if (log.isErrorEnabled())
log.error("++++ attempting to read from closed socket");
throw new IOException("++++ attempting to read from closed socket");
}
byte[] b = new byte[1];
boolean eol = false;
while (in.read(b, 0, 1) != -1) {
// only stop when we see
// \r (13) followed by \n (10)
if (b[0] == 13) {
eol = true;
continue;
}
if (eol) {
if (b[0] == 10)
break;
eol = false;
}
}
}
/**
* reads length bytes into the passed in byte array from dtream
*
* @param b
* byte array
* @throws IOException
* if io problems during read
*/
public int read(byte[] b) throws IOException {
if (sock == null || !sock.isConnected()) {
if (log.isErrorEnabled())
log.error("++++ attempting to read from closed socket");
throw new IOException("++++ attempting to read from closed socket");
}
int count = 0;
while (count < b.length) {
int cnt = in.read(b, count, (b.length - count));
count += cnt;
}
return count;
}
/**
* flushes output stream
*
* @throws IOException
* if io problems during read
*/
public void flush() throws IOException {
if (sock == null || !sock.isConnected()) {
if (log.isErrorEnabled())
log.error("++++ attempting to write to closed socket");
throw new IOException("++++ attempting to write to closed socket");
}
out.flush();
}
/**
* writes a byte array to the output stream
*
* @param b
* byte array to write
* @throws IOException
* if an io error happens
*/
public void write(byte[] b) throws IOException {
if (sock == null || !sock.isConnected()) {
if (log.isErrorEnabled())
log.error("++++ attempting to write to closed socket");
throw new IOException("++++ attempting to write to closed socket");
}
out.write(b);
}
/**
* use the sockets hashcode for this object so we can key off of SockIOs
*
* @return int hashcode
*/
public int hashCode() {
return (sock == null) ? 0 : sock.hashCode();
}
/**
* returns the string representation of this socket
*
* @return string
*/
public String toString() {
return (sock == null) ? "" : sock.toString();
}
/**
* Hack to reap any leaking children.
*/
protected void finalize() throws Throwable {
try {
if (sock != null) {
if (log.isErrorEnabled())
log.error("++++ closing potentially leaked socket in finalize");
sock.close();
sock = null;
}
} catch (Throwable t) {
if (log.isErrorEnabled())
log.error(t.getMessage(), t);
} finally {
super.finalize();
}
}
}
/**
* Returns appropriate SockIO object given string cache key.
*
* @param key
* hashcode for cache key
* @return SockIO obj connected to server
*/
public SockIO getSock(String key) {
return schoonerSockIOPool.getSock(key);
}
/**
* Returns appropriate SockIO object given string cache key and optional
* hashcode.
*
* Trys to get SockIO from pool. Fails over to additional pools in event of
* server failure.
*
* @param key
* hashcode for cache key
* @param hashCode
* if not null, then the int hashcode to use
* @return SockIO obj connected to server
*/
public SockIO getSock(String key, Integer hashCode) {
return schoonerSockIOPool.getSock(key, hashCode);
}
/**
* Returns a SockIO object from the pool for the passed in host.
*
* Meant to be called from a more intelligent method
* which handles choosing appropriate server
* and failover.
*
* @param host
* host from which to retrieve object
* @return SockIO object or null if fail to retrieve one
*/
public SockIO getConnection(String host) {
return schoonerSockIOPool.getConnection(host);
}
/**
* Returns a socket to the avail pool.
*
* This is called from SockIO.close(). Calling this method
* directly without closing the SockIO object first
* will cause an IOException to be thrown.
*
* @param socket
* socket to return
*/
private void checkIn(SockIO socket) {
checkIn(socket, true);
}
/**
* Checks a SockIO object in with the pool.
*
* This will remove SocketIO from busy pool, and optionally
* add to avail pool.
*
* @param socket
* socket to return
* @param addToAvail
* add to avail pool if true
*/
private void checkIn(SockIO socket, boolean addToAvail) {
String host = socket.getHost();
if (log.isDebugEnabled())
log.debug("++++ calling check-in on socket: " + socket.toString() + " for host: " + host);
synchronized (this) {
// remove from the busy pool
if (log.isDebugEnabled())
log.debug("++++ removing socket (" + socket.toString() + ") from busy pool for host: " + host);
removeSocketFromPool(busyPool, host, socket);
if (socket.isConnected() && addToAvail) {
// add to avail pool
if (log.isDebugEnabled())
log.debug("++++ returning socket (" + socket.toString() + " to avail pool for host: " + host);
addSocketToPool(availPool, host, socket);
} else {
deadPool.put(socket, ZERO);
socket = null;
}
}
}
/**
* Class which extends thread and handles maintenance of the pool.
*
* @author greg whalin
* @version 1.5
*/
protected static class MaintThread extends Thread {
private SockIOPool pool;
private long interval = 1000 * 3; // every 3 seconds
private boolean stopThread = false;
private boolean running;
protected MaintThread(SockIOPool pool) {
this.pool = pool;
this.setDaemon(true);
this.setName("MaintThread");
}
public void setInterval(long interval) {
this.interval = interval;
}
public boolean isRunning() {
return this.running;
}
/**
* sets stop variable and interupts any wait
*/
public void stopThread() {
this.stopThread = true;
this.interrupt();
}
/**
* Start the thread.
*/
public void run() {
this.running = true;
while (!this.stopThread) {
try {
Thread.sleep(interval);
// if pool is initialized, then
// run the maintenance method on itself
if (pool.isInitialized())
pool.selfMaint();
} catch (Exception e) {
break;
}
}
this.running = false;
}
}
/**
* Creates a new SockIO obj for the given server.
*
* If server fails to connect, then return null and do not try
* again until a duration has passed. This duration will grow
* by doubling after each failed attempt to connect.
*
* @param host
* host:port to connect to
* @return SockIO obj or null if failed to create
*/
protected SockIO createSocket(String host) {
SockIO socket = null;
// if host is dead, then we don't need to try again
// until the dead status has expired
// we do not try to put back in if failback is off
hostDeadLock.lock();
try {
if (failover && failback && hostDead.containsKey(host) && hostDeadDur.containsKey(host)) {
Date store = hostDead.get(host);
long expire = hostDeadDur.get(host).longValue();
if ((store.getTime() + expire) > System.currentTimeMillis())
return null;
}
} finally {
hostDeadLock.unlock();
}
try {
socket = new SockIO(this, host, this.socketTO, this.socketConnectTO, this.nagle);
if (!socket.isConnected()) {
if (log.isErrorEnabled())
log.error("++++ failed to get SockIO obj for: " + host + " -- new socket is not connected");
deadPool.put(socket, ZERO);
socket = null;
}
} catch (Exception ex) {
if (log.isErrorEnabled()) {
log.error("++++ failed to get SockIO obj for: " + host);
log.error(ex.getMessage(), ex);
}
socket = null;
}
// if we failed to get socket, then mark
// host dead for a duration which falls off
hostDeadLock.lock();
try {
if (socket == null) {
Date now = new Date();
hostDead.put(host, now);
long expire = (hostDeadDur.containsKey(host)) ? (((Long) hostDeadDur.get(host)).longValue() * 2) : 1000;
if (expire > MAX_RETRY_DELAY)
expire = MAX_RETRY_DELAY;
hostDeadDur.put(host, new Long(expire));
if (log.isDebugEnabled())
log.debug("++++ ignoring dead host: " + host + " for " + expire + " ms");
// also clear all entries for this host from availPool
clearHostFromPool(availPool, host);
} else {
if (log.isDebugEnabled())
log.debug("++++ created socket (" + socket.toString() + ") for host: " + host);
if (hostDead.containsKey(host) || hostDeadDur.containsKey(host)) {
hostDead.remove(host);
hostDeadDur.remove(host);
}
}
} finally {
hostDeadLock.unlock();
}
return socket;
}
/**
* Runs self maintenance on all internal pools.
*
* This is typically called by the maintenance thread to manage pool size.
*/
protected void selfMaint() {
if (log.isDebugEnabled())
log.debug("++++ Starting self maintenance....");
// go through avail sockets and create sockets
// as needed to maintain pool settings
Map needSockets = new HashMap();
synchronized (this) {
// find out how many to create
for (Iterator i = availPool.keySet().iterator(); i.hasNext();) {
String host = i.next();
Map sockets = availPool.get(host);
if (log.isDebugEnabled())
log.debug("++++ Size of avail pool for host (" + host + ") = " + sockets.size());
// if pool is too small (n < minSpare)
if (sockets.size() < minConn) {
// need to create new sockets
int need = minConn - sockets.size();
needSockets.put(host, need);
}
}
}
// now create
Map> newSockets = new HashMap>();
for (String host : needSockets.keySet()) {
Integer need = needSockets.get(host);
if (log.isDebugEnabled())
log.debug("++++ Need to create " + need + " new sockets for pool for host: " + host);
Set newSock = new HashSet(need);
for (int j = 0; j < need; j++) {
SockIO socket = createSocket(host);
if (socket == null)
break;
newSock.add(socket);
}
newSockets.put(host, newSock);
}
// synchronize to add and remove to/from avail pool
// as well as clean up the busy pool (no point in releasing
// lock here as should be quick to pool adjust and no
// blocking ops here)
synchronized (this) {
for (String host : newSockets.keySet()) {
Set sockets = newSockets.get(host);
for (SockIO socket : sockets)
addSocketToPool(availPool, host, socket);
}
for (Iterator i = availPool.keySet().iterator(); i.hasNext();) {
String host = i.next();
Map sockets = availPool.get(host);
if (log.isDebugEnabled())
log.debug("++++ Size of avail pool for host (" + host + ") = " + sockets.size());
if (sockets.size() > maxConn) {
// need to close down some sockets
int diff = sockets.size() - maxConn;
int needToClose = (diff <= poolMultiplier) ? diff : (diff) / poolMultiplier;
if (log.isDebugEnabled())
log.debug("++++ need to remove " + needToClose + " spare sockets for pool for host: " + host);
for (Iterator j = sockets.keySet().iterator(); j.hasNext();) {
if (needToClose <= 0)
break;
// remove stale entries
SockIO socket = j.next();
long expire = sockets.get(socket).longValue();
// if past idle time
// then close socket
// and remove from pool
if ((expire + maxIdle) < System.currentTimeMillis()) {
if (log.isDebugEnabled())
log.debug("+++ removing stale entry from pool as it is past its idle timeout and pool is over max spare");
// remove from the availPool
deadPool.put(socket, ZERO);
j.remove();
needToClose--;
}
}
}
}
// go through busy sockets and destroy sockets
// as needed to maintain pool settings
for (Iterator i = busyPool.keySet().iterator(); i.hasNext();) {
String host = i.next();
Map sockets = busyPool.get(host);
if (log.isDebugEnabled())
log.debug("++++ Size of busy pool for host (" + host + ") = " + sockets.size());
// loop through all connections and check to see if we have any
// hung connections
for (Iterator j = sockets.keySet().iterator(); j.hasNext();) {
// remove stale entries
SockIO socket = j.next();
long hungTime = sockets.get(socket).longValue();
// if past max busy time
// then close socket
// and remove from pool
if ((hungTime + maxBusyTime) < System.currentTimeMillis()) {
if (log.isErrorEnabled())
log.error("+++ removing potentially hung connection from busy pool ... socket in pool for "
+ (System.currentTimeMillis() - hungTime) + "ms");
// remove from the busy pool
deadPool.put(socket, ZERO);
j.remove();
}
}
}
}
// finally clean out the deadPool
Set toClose;
synchronized (deadPool) {
toClose = deadPool.keySet();
deadPool = new IdentityHashMap();
}
for (SockIO socket : toClose) {
try {
socket.trueClose(false);
} catch (Exception ex) {
if (log.isErrorEnabled()) {
log.error("++++ failed to close SockIO obj from deadPool");
log.error(ex.getMessage(), ex);
}
}
socket = null;
}
if (log.isDebugEnabled())
log.debug("+++ ending self maintenance.");
}
/**
* Adds a socket to a given pool for the given host. THIS METHOD IS NOT
* THREADSAFE, SO BE CAREFUL WHEN USING!
*
* Internal utility method.
*
* @param pool
* pool to add to
* @param host
* host this socket is connected to
* @param socket
* socket to add
*/
protected void addSocketToPool(Map> pool, String host, SockIO socket) {
if (pool.containsKey(host)) {
Map sockets = pool.get(host);
if (sockets != null) {
sockets.put(socket, new Long(System.currentTimeMillis()));
return;
}
}
Map sockets = new IdentityHashMap();
sockets.put(socket, new Long(System.currentTimeMillis()));
pool.put(host, sockets);
}
/**
* Removes a socket from specified pool for host. THIS METHOD IS NOT
* THREADSAFE, SO BE CAREFUL WHEN USING!
*
* Internal utility method.
*
* @param pool
* pool to remove from
* @param host
* host pool
* @param socket
* socket to remove
*/
protected void removeSocketFromPool(Map> pool, String host, SockIO socket) {
if (pool.containsKey(host)) {
Map sockets = pool.get(host);
if (sockets != null)
sockets.remove(socket);
}
}
/**
* Closes and removes all sockets from specified pool for host. THIS METHOD
* IS NOT THREADSAFE, SO BE CAREFUL WHEN USING!
*
* Internal utility method.
*
* @param pool
* pool to clear
* @param host
* host to clear
*/
protected void clearHostFromPool(Map> pool, String host) {
if (pool.containsKey(host)) {
Map sockets = pool.get(host);
if (sockets != null && sockets.size() > 0) {
for (Iterator i = sockets.keySet().iterator(); i.hasNext();) {
SockIO socket = i.next();
try {
socket.trueClose();
} catch (IOException ioe) {
if (log.isErrorEnabled())
log.error("++++ failed to close socket: " + ioe.getMessage());
}
i.remove();
socket = null;
}
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy