package org.jgroups.blocks; import org.jgroups.logging.Log; import org.jgroups.logging.LogFactory; import org.jgroups.Address; import org.jgroups.Global; import org.jgroups.Version; import org.jgroups.stack.IpAddress; import org.jgroups.util.*; import java.io.*; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Shared class for TCP connection tables. * @author Scott Marlow */ public abstract class BasicConnectionTable { private ThreadFactory factory; final Map<Address,Connection> conns=new HashMap<Address,Connection>(); // keys: Addresses (peer address), values: Connection Receiver receiver=null; boolean use_send_queues=false; // max number of messages in a send queue int send_queue_size=10000; InetAddress bind_addr=null; Address local_addr=null; // bind_addr + port of srv_sock int srv_port=7800; int recv_buf_size=120000; int send_buf_size=60000; final List<ConnectionListener> conn_listeners=new ArrayList<ConnectionListener>(); // listeners to be notified when a conn is established/torn down Reaper reaper=null; // closes conns that have been idle for more than n secs long reaper_interval=60000; // reap unused conns once a minute long conn_expire_time=300000; // connections can be idle for 5 minutes before they are reaped int sock_conn_timeout=1000; // max time in millis to wait for Socket.connect() to return int peer_addr_read_timeout=2000; // max time in milliseconds to block on reading peer address protected final Log log= LogFactory.getLog(getClass()); final byte[] cookie={'b', 'e', 'l', 'a'}; boolean use_reaper=false; // by default we don't reap idle conns static final int backlog=20; // 20 conn requests are queued by ServerSocket (addtl will be discarded) volatile ServerSocket srv_sock=null; boolean tcp_nodelay=false; int linger=-1; protected SocketFactory socket_factory=new DefaultSocketFactory(); /** * The address which will be broadcast to the group (the externally visible address which this host should * be contacted on). If external_addr is null, it will default to the same address that the server socket is bound to. */ InetAddress external_addr=null; int external_port=0; int max_port=0; // maximum port to bind to (if < srv_port, no limit) Thread acceptor=null; // continuously calls srv_sock.accept() boolean running=false; /** Total number of Connections created for this connection table */ static AtomicInteger conn_creations=new AtomicInteger(0); final static long MAX_JOIN_TIMEOUT=Global.THREAD_SHUTDOWN_WAIT_TIME; protected BasicConnectionTable() { factory = new DefaultThreadFactory("Connection Table", false); } public final void setReceiver(Receiver r) { receiver=r; } public void addConnectionListener(ConnectionListener l) { if(l != null && !conn_listeners.contains(l)) conn_listeners.add(l); } public void removeConnectionListener(ConnectionListener l) { if(l != null) conn_listeners.remove(l); } public Address getLocalAddress() { if(local_addr == null) local_addr=bind_addr != null ? new IpAddress(bind_addr, srv_port) : null; return local_addr; } public int getSendBufferSize() { return send_buf_size; } public void setSendBufferSize(int send_buf_size) { this.send_buf_size=send_buf_size; } public int getReceiveBufferSize() { return recv_buf_size; } public void setReceiveBufferSize(int recv_buf_size) { this.recv_buf_size=recv_buf_size; } public int getSocketConnectionTimeout() { return sock_conn_timeout; } public void setSocketConnectionTimeout(int sock_conn_timeout) { this.sock_conn_timeout=sock_conn_timeout; } public int getPeerAddressReadTimeout() { return peer_addr_read_timeout; } public void setPeerAddressReadTimeout(int peer_addr_read_timeout) { this.peer_addr_read_timeout=peer_addr_read_timeout; } public int getNumConnections() { return conns.size(); } public static int getNumberOfConnectionCreations() { return conn_creations.intValue(); } public boolean getTcpNodelay() { return tcp_nodelay; } public void setTcpNodelay(boolean tcp_nodelay) { this.tcp_nodelay=tcp_nodelay; } public int getLinger() { return linger; } public void setLinger(int linger) { this.linger=linger; } public void setThreadFactory(ThreadFactory factory){ this.factory = factory; } public ThreadFactory getThreadFactory(){ return factory; } public SocketFactory getSocketFactory() { return socket_factory; } public void setSocketFactory(SocketFactory socket_factory) { this.socket_factory=socket_factory; } public boolean getUseSendQueues() {return use_send_queues;} public void setUseSendQueues(boolean flag) {this.use_send_queues=flag;} public int getSendQueueSize() { return send_queue_size; } public void setSendQueueSize(int send_queue_size) { this.send_queue_size=send_queue_size; } public void start() throws Exception { running=true; } public void stop() { running=false; // 1. Stop the reaper if(reaper != null) reaper.stop(); // 2. close the server socket (this also stops the acceptor thread) if(srv_sock != null) { try { ServerSocket tmp=srv_sock; srv_sock=null; socket_factory.close(tmp); if(acceptor != null) Util.interruptAndWaitToDie(acceptor); } catch(Exception e) { } } // 3. then close the connections Collection<Connection> connsCopy=null; synchronized(conns) { connsCopy=new LinkedList<Connection>(conns.values()); conns.clear(); } for(Connection conn:connsCopy) { conn.destroy(); } connsCopy.clear(); local_addr=null; } /** Remove <code>addr</code>from connection table. This is typically triggered when a member is suspected. */ public void removeConnection(Address addr) { Connection conn; synchronized(conns) { conn=conns.remove(addr); } if(conn != null) { try { conn.destroy(); // won't do anything if already destroyed } catch(Exception e) { } } if(log.isTraceEnabled()) log.trace("removed " + addr + ", connections are " + toString()); } /** * Calls the receiver callback. We do not serialize access to this method, and it may be called concurrently * by several Connection handler threads. Therefore the receiver needs to be reentrant. */ public void receive(Address sender, byte[] data, int offset, int length) { if(receiver != null) receiver.receive(sender, data, offset, length); } public String toString() { StringBuilder ret=new StringBuilder(); Address key; Connection val; Entry<Address,Connection> entry; HashMap<Address,Connection> copy; synchronized(conns) { copy=new HashMap<Address,Connection>(conns); } ret.append("local_addr=" + local_addr).append("\n"); ret.append("connections (" + copy.size() + "):\n"); for(Iterator<Entry<Address,Connection>> it=copy.entrySet().iterator(); it.hasNext();) { entry=it.next(); key=entry.getKey(); val=entry.getValue(); ret.append(key + ": " + val + '\n'); } ret.append('\n'); return ret.toString(); } void notifyConnectionOpened(Address peer) { if(peer == null) return; for(int i=0; i < conn_listeners.size(); i++) conn_listeners.get(i).connectionOpened(peer); } void notifyConnectionClosed(Address peer) { if(peer == null) return; for(int i=0; i < conn_listeners.size(); i++) conn_listeners.get(i).connectionClosed(peer); } void addConnection(Address peer, Connection c) { synchronized (conns) { conns.put(peer, c); } if(reaper != null && !reaper.isRunning()) reaper.start(); } public void send(Address dest, byte[] data, int offset, int length) throws Exception { Connection conn; if(dest == null) { if(log.isErrorEnabled()) log.error("destination is null"); return; } if(data == null) { log.warn("data is null; discarding packet"); return; } if(!running) { if(log.isWarnEnabled()) log.warn("connection table is not running, discarding message to " + dest); return; } if(dest.equals(local_addr)) { receive(local_addr, data, offset, length); return; } // 1. Try to obtain correct Connection (or create one if not yet existent) try { conn=getConnection(dest); if(conn == null) return; } catch(Throwable ex) { throw new Exception("connection to " + dest + " could not be established", ex); } // 2. Send the message using that connection try { conn.send(data, offset, length); } catch(Throwable ex) { if(log.isTraceEnabled()) log.trace("sending msg to " + dest + " failed (" + ex.getClass().getName() + "); removing from connection table", ex); removeConnection(dest); } } abstract Connection getConnection(Address dest) throws Exception; /** * Removes all connections from ConnectionTable which are not in current_mbrs * @param current_mbrs */ public void retainAll(Collection<Address> current_mbrs) { if(current_mbrs == null) return; HashMap<Address,Connection> copy; synchronized(conns) { copy=new HashMap<Address,Connection>(conns); conns.keySet().retainAll(current_mbrs); } copy.keySet().removeAll(current_mbrs); //destroy orphaned connection i.e. connections //to members that are not in current view for(Connection orphanConnection:copy.values()){ if (log.isTraceEnabled()) log.trace("At " + local_addr + " destroying orphan to " + orphanConnection.getPeerAddress()); orphanConnection.destroy(); } copy.clear(); } /** Used for message reception. */ public interface Receiver { void receive(Address sender, byte[] data, int offset, int length); } /** Used to be notified about connection establishment and teardown. */ public interface ConnectionListener { void connectionOpened(Address peer_addr); void connectionClosed(Address peer_addr); } class Connection implements Runnable { Socket sock=null; // socket to/from peer (result of srv_sock.accept() or new Socket()) String sock_addr=null; // used for Thread.getName() DataOutputStream out=null; // for sending messages DataInputStream in=null; // for receiving messages Thread receiverThread=null; // thread for receiving messages Address peer_addr=null; // address of the 'other end' of the connection final Lock send_lock=new ReentrantLock(); // serialize send() long last_access=System.currentTimeMillis(); // last time a message was sent or received /** Bounded queue of data to be sent to the peer of this connection */ BlockingQueue<byte[]> send_queue=null; Sender sender=null; boolean is_running=false; private String getSockAddress() { if(sock_addr != null) return sock_addr; if(sock != null) { StringBuilder sb; sb=new StringBuilder(); sb.append(sock.getLocalAddress().getHostAddress()).append(':').append(sock.getLocalPort()); sb.append(" - ").append(sock.getInetAddress().getHostAddress()).append(':').append(sock.getPort()); sock_addr=sb.toString(); } return sock_addr; } Connection(Socket s, Address peer_addr) { sock=s; this.peer_addr=peer_addr; if(use_send_queues) { send_queue=new LinkedBlockingQueue<byte[]>(send_queue_size); sender=new Sender(); } try { // out=new DataOutputStream(sock.getOutputStream()); // in=new DataInputStream(sock.getInputStream()); // The change to buffered input and output stream yielded a 400% performance gain ! // bela Sept 7 2006 out=new DataOutputStream(new BufferedOutputStream(sock.getOutputStream())); in=new DataInputStream(new BufferedInputStream(sock.getInputStream())); if(sender != null) sender.start(); conn_creations.incrementAndGet(); } catch(Exception ex) { if(log.isErrorEnabled()) log.error("exception is " + ex); } } boolean established() { return receiverThread != null; } void setPeerAddress(Address peer_addr) { this.peer_addr=peer_addr; } Address getPeerAddress() {return peer_addr;} void updateLastAccessed() { last_access=System.currentTimeMillis(); } void init() { is_running=true; if(receiverThread == null || !receiverThread.isAlive()) { // Roland Kurmann 4/7/2003, put in thread_group - removed bela Nov 2012 receiverThread=getThreadFactory().newThread(this, "ConnectionTable.Connection.Receiver [" + getSockAddress() + "]"); receiverThread.start(); if(log.isTraceEnabled()) log.trace("receiver started: " + receiverThread); } } /** * Returns true if underlying socket to peer is closed * * @return */ boolean isSocketClosed() { return !(sock != null && sock.isConnected()); } void destroy() { if(log.isTraceEnabled()) log.trace("destroyed " + this); is_running=false; closeSocket(); // should terminate handler as well if(sender != null) sender.stop(); Thread tmp=receiverThread; receiverThread=null; if(tmp != null) { Util.interruptAndWaitToDie(tmp); } conn_creations.decrementAndGet(); } /** * * @param data Guaranteed to be non null * @param offset * @param length */ void send(byte[] data, int offset, int length) { if(!is_running) { if(log.isWarnEnabled()) log.warn("Connection is not running, discarding message"); return; } if(use_send_queues) { try { // we need to copy the byte[] buffer here because the original buffer might get changed meanwhile byte[] tmp=new byte[length]; System.arraycopy(data, offset, tmp, 0, length); send_queue.put(tmp); } catch(InterruptedException e) { Thread.currentThread().interrupt(); } } else _send(data, offset, length, true); } /** * Sends data using the 'out' output stream of the socket * @param data * @param offset * @param length * @param acquire_lock */ private void _send(byte[] data, int offset, int length, boolean acquire_lock) { if(acquire_lock) send_lock.lock(); try { doSend(data, offset, length); updateLastAccessed(); } catch(InterruptedException iex) { Thread.currentThread().interrupt(); // set interrupt flag again } catch(Throwable ex) { if(log.isErrorEnabled()) log.error("failed sending data to " + peer_addr + ": " + ex); } finally { if(acquire_lock) send_lock.unlock(); } } void doSend(byte[] data, int offset, int length) throws Exception { try { if(out != null) { out.writeInt(length); // write the length of the data buffer first out.write(data, offset, length); out.flush(); // may not be very efficient (but safe) } } catch(Exception ex) { removeConnection(peer_addr); throw ex; } } /** * Reads the peer's address. First a cookie has to be sent which has to match my own cookie, otherwise * the connection will be refused */ Address readPeerAddress(Socket client_sock) throws Exception { Address client_peer_addr=null; byte[] input_cookie=new byte[cookie.length]; int client_port=client_sock != null? client_sock.getPort() : 0; short version; InetAddress client_addr=client_sock != null? client_sock.getInetAddress() : null; int timeout=client_sock.getSoTimeout(); client_sock.setSoTimeout(peer_addr_read_timeout); try { if(in != null) { initCookie(input_cookie); // read the cookie first in.readFully(input_cookie, 0, input_cookie.length); if(!matchCookie(input_cookie)) throw new SocketException("ConnectionTable.Connection.readPeerAddress(): cookie sent by " + client_peer_addr + " does not match own cookie; terminating connection"); // then read the version version=in.readShort(); if(Version.isBinaryCompatible(version) == false) { if(log.isWarnEnabled()) log.warn(new StringBuilder("packet from ").append(client_addr).append(':').append(client_port). append(" has different version (").append(Version.print(version)).append(") from ours ("). append(Version.printVersion()).append("). This may cause problems").toString()); } client_peer_addr=new IpAddress(); client_peer_addr.readFrom(in); updateLastAccessed(); } return client_peer_addr; } finally { client_sock.setSoTimeout(timeout); } } /** * Send the cookie first, then the our port number. If the cookie doesn't match the receiver's cookie, * the receiver will reject the connection and close it. */ void sendLocalAddress(Address local_addr) { if(local_addr == null) { if(log.isWarnEnabled()) log.warn("local_addr is null"); return; } if(out != null) { try { // write the cookie out.write(cookie, 0, cookie.length); // write the version out.writeShort(Version.version); local_addr.writeTo(out); out.flush(); // needed ? updateLastAccessed(); } catch(Throwable t) { if(log.isErrorEnabled()) log.error("exception is " + t); } } } void initCookie(byte[] c) { if(c != null) for(int i=0; i < c.length; i++) c[i]=0; } boolean matchCookie(byte[] input) { if(input == null || input.length < cookie.length) return false; for(int i=0; i < cookie.length; i++) if(cookie[i] != input[i]) return false; return true; } String printCookie(byte[] c) { if(c == null) return ""; return new String(c); } public void run() { while(receiverThread != null && receiverThread.equals(Thread.currentThread()) && is_running) { try { if(in == null) { if(log.isErrorEnabled()) log.error("input stream is null !"); break; } int len=in.readInt(); byte[] buf=new byte[len]; in.readFully(buf, 0, len); updateLastAccessed(); receive(peer_addr, buf, 0, len); // calls receiver.receive(msg) } catch(OutOfMemoryError mem_ex) { if(log.isWarnEnabled()) log.warn("dropped invalid message, closing connection"); break; // continue; } catch(IOException io_ex) { //this is very common occurrence, hence log under trace level if(log.isTraceEnabled()) log.trace("Exception while read blocked for data from peer ", io_ex); notifyConnectionClosed(peer_addr); break; } catch(Throwable e) { if(log.isWarnEnabled()) log.warn("Problem encountered while receiving message from peer " + peer_addr, e); } } if(log.isTraceEnabled()) log.trace("ConnectionTable.Connection.Receiver terminated"); receiverThread=null; closeSocket(); // remove(peer_addr); } public String toString() { StringBuilder ret=new StringBuilder(); InetAddress local=null, remote=null; String local_str, remote_str; Socket tmp_sock=sock; if(tmp_sock == null) ret.append("<null socket>"); else { //since the sock variable gets set to null we want to make //make sure we make it through here without a nullpointer exception local=tmp_sock.getLocalAddress(); remote=tmp_sock.getInetAddress(); local_str=local != null ? Util.shortName(local) : "<null>"; remote_str=remote != null ? Util.shortName(remote) : "<null>"; ret.append('<' + local_str + ':' + tmp_sock.getLocalPort() + " --> " + remote_str + ':' + tmp_sock.getPort() + "> (" + ((System.currentTimeMillis() - last_access) / 1000) + " secs old)"); } tmp_sock=null; return ret.toString(); } void closeSocket() { Util.close(sock); // should actually close in/out (so we don't need to close them explicitly) sock=null; Util.close(out); // flushes data // removed 4/22/2003 (request by Roland Kurmann) // out=null; Util.close(in); } class Sender implements Runnable { AtomicReference<Thread> senderThread = new AtomicReference<Thread>(); private final AtomicBoolean is_it_running=new AtomicBoolean(); void start() { Thread localThread=senderThread.getAndSet(getThreadFactory() .newThread(this, "ConnectionTable.Connection.Sender local_addr=" + local_addr + " [" + getSockAddress() + "]")); if(localThread == null ) { is_it_running.set(true); senderThread.get().setDaemon(true); senderThread.get().start(); if(log.isTraceEnabled()) log.trace("sender thread started: " + senderThread); } } void stop() { is_it_running.set(false); if(send_queue != null) send_queue.clear(); Thread localThread = senderThread.getAndSet(null); if(localThread != null) { Util.interruptAndWaitToDie(localThread); } } boolean isRunning() { return is_it_running.get() && senderThread.get() != null; } public void run() { byte[] data; while(senderThread.get() != null && senderThread.get().equals(Thread.currentThread()) && is_it_running.get()) { try { data=send_queue.take(); if(data == null) continue; // we don't need to serialize access to 'out' as we're the only thread sending messages _send(data, 0, data.length, false); } catch(InterruptedException e) { ; } } is_it_running.set(false); if(log.isTraceEnabled()) log.trace("ConnectionTable.Connection.Sender thread terminated"); } } } class Reaper implements Runnable { Thread t=null; Reaper() { ; } // return true if we have zero connections private boolean haveZeroConnections() { synchronized(conns) { return conns.isEmpty(); } } public void start() { if(haveZeroConnections()) return; if(t != null && !t.isAlive()) t=null; if(t == null) { // RKU 7.4.2003, put in threadgroup -- removed bela Nov 2012 t=getThreadFactory().newThread(this, "ConnectionTable.ReaperThread"); t.setDaemon(true); // will allow us to terminate if all remaining threads are daemons t.start(); } } public void stop() { Thread tmp=t; if(t != null) t=null; if(tmp != null) { Util.interruptAndWaitToDie(tmp); } } public boolean isRunning() { return t != null; } public void run() { Connection connection; Entry<Address,Connection> entry; long curr_time; if(log.isDebugEnabled()) log.debug("connection reaper thread was started. Number of connections=" + conns.size() + ", reaper_interval=" + reaper_interval + ", conn_expire_time=" + conn_expire_time); while(!haveZeroConnections() && t != null && t.equals(Thread.currentThread())) { Util.sleep(reaper_interval); if(t == null || !Thread.currentThread().equals(t)) break; synchronized(conns) { curr_time=System.currentTimeMillis(); for(Iterator<Entry<Address,Connection>> it=conns.entrySet().iterator(); it.hasNext();) { entry=it.next(); connection=entry.getValue(); if(log.isTraceEnabled()) log.trace("connection is " + ((curr_time - connection.last_access) / 1000) + " seconds old (curr-time=" + curr_time + ", last_access=" + connection.last_access + ')'); if(connection.last_access + conn_expire_time < curr_time) { if(log.isTraceEnabled()) log.trace("connection " + connection + " has been idle for too long (conn_expire_time=" + conn_expire_time + "), will be removed"); connection.destroy(); it.remove(); } } } } if(log.isDebugEnabled()) log.debug("reaper terminated"); t=null; } } }