org.jgroups.blocks.cs.BaseServer Maven / Gradle / Ivy
package org.jgroups.blocks.cs;
import org.jgroups.Address;
import org.jgroups.Global;
import org.jgroups.annotations.GuardedBy;
import org.jgroups.annotations.MBean;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.conf.AttributeType;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.stack.IpAddress;
import org.jgroups.util.*;
import javax.net.ssl.SSLException;
import java.io.Closeable;
import java.io.DataInput;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Abstract class for a server handling sending, receiving and connection management.
* @since 3.6.5
*/
@MBean(description="Server used to accept connections from other servers (or clients) and send data to servers")
public abstract class BaseServer implements Closeable, ConnectionListener {
protected Address local_addr; // typically the address of the server socket or channel
protected final List conn_listeners=new CopyOnWriteArrayList<>();
protected final Map conns=new ConcurrentHashMap<>();
protected final ThreadFactory factory;
protected SocketFactory socket_factory=new DefaultSocketFactory();
protected long reaperInterval;
protected Reaper reaper;
protected Receiver receiver;
protected final AtomicBoolean running=new AtomicBoolean(false);
protected Log log=LogFactory.getLog(getClass());
protected InetAddress client_bind_addr;
protected int client_bind_port;
protected boolean defer_client_binding;
@ManagedAttribute(description="Time (ms) after which an idle connection is closed. 0 disables connection reaping",
writable=true,type=AttributeType.TIME)
protected long conn_expire_time; // ns
@ManagedAttribute(description="Size (bytes) of the receive channel/socket",writable=true,type=AttributeType.BYTES)
protected int recv_buf_size;
@ManagedAttribute(description="Size (bytes) of the send channel/socket",writable=true,type=AttributeType.BYTES)
protected int send_buf_size;
@ManagedAttribute(description="The max number of bytes a message can have. If greater, an exception will be " +
"thrown. 0 disables this",
writable=true,type=AttributeType.BYTES)
protected int max_length;
@ManagedAttribute(description="When A connects to B, B reuses the same TCP connection to send data to A")
protected boolean use_peer_connections;
@ManagedAttribute(description="Wait for an ack from the server when a connection is established, and retry " +
"connection establishment until a valid connection has been established, or the connection to the peer cannot " +
"be established (https://issues.redhat.com/browse/JGRP-2684)",writable=true)
protected boolean use_acks;
@ManagedAttribute(description="Log a stack trace when a connection is closed")
protected boolean log_details=true;
protected int sock_conn_timeout=1000; // max time in millis to wait for Socket.connect() to return
protected boolean tcp_nodelay=false;
protected int linger=-1;
protected TimeService time_service;
public static final byte[] OK={1,2,3,4}; // ack (srv->client) on successful connection establishment
public static final byte[] FAIL={4,3,2,1}; // ack (srv->client) on failed connection establishment
protected BaseServer(ThreadFactory f, SocketFactory sf, int recv_buf_size) {
this.factory=f;
this.recv_buf_size=recv_buf_size;
if(sf != null)
this.socket_factory=sf;
}
public Receiver receiver() {return receiver;}
public BaseServer receiver(Receiver r) {this.receiver=r; return this;}
public long reaperInterval() {return reaperInterval;}
public BaseServer reaperInterval(long interval) {this.reaperInterval=interval; return this;}
public Log log() {return log;}
public BaseServer log(Log the_log) {this.log=the_log; return this;}
public Address localAddress() {return local_addr;}
public InetAddress clientBindAddress() {return client_bind_addr;}
public BaseServer clientBindAddress(InetAddress addr) {this.client_bind_addr=addr; return this;}
public int clientBindPort() {return client_bind_port;}
public BaseServer clientBindPort(int port) {this.client_bind_port=port; return this;}
public boolean deferClientBinding() {return defer_client_binding;}
public BaseServer deferClientBinding(boolean defer) {this.defer_client_binding=defer; return this;}
public SocketFactory socketFactory() {return socket_factory;}
public BaseServer socketFactory(SocketFactory factory) {this.socket_factory=factory; return this;}
public boolean usePeerConnections() {return use_peer_connections;}
public BaseServer usePeerConnections(boolean flag) {this.use_peer_connections=flag; return this;}
public boolean useAcks() {return use_acks;}
public BaseServer useAcks(boolean f) {use_acks=f; return this;}
public boolean logDetails() {return log_details;}
public BaseServer logDetails(boolean l) {log_details=l; return this;}
public int socketConnectionTimeout() {return sock_conn_timeout;}
public BaseServer socketConnectionTimeout(int timeout) {this.sock_conn_timeout = timeout; return this;}
public long connExpireTime() {return conn_expire_time;}
public BaseServer connExpireTimeout(long t) {conn_expire_time=TimeUnit.NANOSECONDS.convert(t, TimeUnit.MILLISECONDS); return this;}
public TimeService timeService() {return time_service;}
public BaseServer timeService(TimeService ts) {this.time_service=ts; return this;}
public int receiveBufferSize() {return recv_buf_size;}
public BaseServer receiveBufferSize(int recv_buf_size) {this.recv_buf_size = recv_buf_size; return this;}
public int sendBufferSize() {return send_buf_size;}
public BaseServer sendBufferSize(int send_buf_size) {this.send_buf_size = send_buf_size; return this;}
public int getMaxLength() {return max_length;}
public BaseServer setMaxLength(int len) {max_length=len; return this;}
public int linger() {return linger;}
public BaseServer linger(int linger) {this.linger=linger; return this;}
public boolean tcpNodelay() {return tcp_nodelay;}
public BaseServer tcpNodelay(boolean tcp_nodelay) {this.tcp_nodelay = tcp_nodelay; return this;}
@ManagedAttribute(description="True if the server is running, else false")
public boolean running() {return running.get();}
@ManagedAttribute(description="Number of connections")
public int getNumConnections() {
return conns.size();
}
@ManagedAttribute(description="Number of currently open connections")
public int getNumOpenConnections() {
int retval=0;
for(Connection conn: conns.values())
if(!conn.isClosed())
retval++;
return retval;
}
/**
* Starts accepting connections. Typically, socket handler or selectors thread are started here.
*/
public void start() throws Exception {
if(reaperInterval > 0 && (reaper == null || !reaper.isAlive())) {
reaper=new Reaper();
reaper.start();
}
}
/**
* Stops listening for connections and handling traffic. Typically, socket handler or selector threads are stopped,
* and server sockets or channels are closed.
*/
public void stop() {
Util.close(reaper);
reaper=null;
synchronized(this) {
for(Connection c: conns.values())
Util.close(c);
conns.clear();
}
conn_listeners.clear();
}
public void close() throws IOException {
stop();
}
public synchronized void flush(Address dest) {
if(dest != null) {
Connection conn=conns.get(dest);
if(conn != null)
conn.flush();
}
}
public synchronized void flushAll() {
for(Connection c: conns.values())
c.flush();
}
/**
* Called by a {@link Connection} implementation when a message has been received. Note that data might be a
* reused buffer, so unless used to de-serialize an object from it, it should be copied (e.g. if we store a ref
* to it beyone the scope of this receive() method)
*/
public void receive(Address sender, byte[] data, int offset, int length) {
if(this.receiver != null)
this.receiver.receive(sender, data, offset, length);
}
/**
* Called by a {@link Connection} implementation when a message has been received
*/
public void receive(Address sender, ByteBuffer buf) {
if(this.receiver != null)
this.receiver.receive(sender, buf);
}
public void receive(Address sender, DataInput in, int len) throws Exception {
// https://issues.redhat.com/browse/JGRP-2523: check if max_length has been exceeded
if(max_length > 0 && len > max_length)
throw new IllegalStateException(String.format("the length of a message (%s) from %s is bigger than the " +
"max accepted length (%s): discarding the message",
Util.printBytes(len), sender, Util.printBytes(max_length)));
if(this.receiver != null)
this.receiver.receive(sender, in);
else {
// discard len bytes (in.skip() is not guaranteed to discard *all* len bytes)
byte[] buf=new byte[len];
in.readFully(buf, 0, len);
}
}
public void send(Address dest, byte[] data, int offset, int length) throws Exception {
if(!validateArgs(dest, data))
return;
if(dest == null) {
sendToAll(data, offset, length);
return;
}
if(dest.equals(local_addr)) {
receive(dest, data, offset, length);
return;
}
// Get a connection (or create one if not yet existent) and send the data
Connection conn=null;
try {
conn=getConnection(dest, use_acks);
conn.send(data, offset, length);
}
catch(Exception ex) {
removeConnectionIfPresent(dest, conn);
throw ex;
}
}
public void send(Address dest, ByteBuffer data) throws Exception {
if(!validateArgs(dest, data))
return;
if(dest == null) {
sendToAll(data);
return;
}
if(dest.equals(local_addr)) {
receive(dest, data);
return;
}
// Get a connection (or create one if not yet existent) and send the data
Connection conn=null;
try {
conn=getConnection(dest, use_acks);
conn.send(data);
}
catch(Exception ex) {
removeConnectionIfPresent(dest, conn);
throw ex;
}
}
@Override
public void connectionClosed(Connection conn) {
removeConnectionIfPresent(conn.peerAddress(), conn);
}
@Override
public void connectionEstablished(Connection conn) {
}
/** Creates a new connection object to target dest, but doesn't yet connect it */
protected abstract Connection createConnection(Address dest) throws Exception;
public boolean hasConnection(Address address) {
return conns.containsKey(address);
}
public boolean connectionEstablishedTo(Address address) {
return connected(conns.get(address));
}
public Connection getConnection(Address dest, boolean retry) throws Exception {
if(!retry)
return getConnection(dest);
Connection conn=null;
do {
try {
conn=getConnection(dest);
}
catch(ConnectException | SSLException ex) {
throw ex;
}
catch(Exception ise) {
Util.sleepRandom(1, 100);
}
}
while(!connected(conn));
return conn;
}
/** Creates a new connection to dest, or returns an existing one */
public Connection getConnection(Address dest) throws Exception {
Connection conn;
// keep FAST path on the most common case
if(connected(conn=conns.get(dest)))
return conn;
synchronized(this) {
if(connected(conn=conns.get(dest)))
return conn;
conn=createConnection(dest);
replaceConnection(dest, conn);
// now connect to dest:
try {
log.trace("%s: connecting to %s", local_addr, dest);
conn.connect(dest);
notifyConnectionEstablished(conn);
conn.start();
}
catch(Exception connect_ex) {
log.trace("%s: failed connecting to %s: %s", local_addr, dest, connect_ex);
removeConnectionIfPresent(dest, conn); // removes and closes the conn
throw connect_ex;
}
}
return conn;
}
@GuardedBy("this")
public void replaceConnection(Address address, Connection conn) {
Connection previous=conns.put(address, conn);
if(previous != null) {
previous.flush();
Util.close(previous);
}
}
public void closeConnection(Connection conn) {
closeConnection(conn, true);
}
public void closeConnection(Connection conn, boolean notify) {
Util.close(conn);
if(notify)
notifyConnectionClosed(conn);
removeConnectionIfPresent(conn != null? conn.peerAddress() : null, conn);
}
public boolean closeConnection(Address addr) {
return closeConnection(addr, true);
}
public boolean closeConnection(Address addr, boolean notify) {
Connection c;
if(addr != null && (c=conns.get(addr)) != null) {
closeConnection(c, notify);
return true;
}
return false;
}
public synchronized void addConnection(Address peer_addr, Connection conn) throws Exception {
boolean conn_exists=hasConnection(peer_addr),
replace=conn_exists && local_addr.compareTo(peer_addr) < 0; // bigger conn wins
if(!conn_exists || replace) {
replaceConnection(peer_addr, conn); // closes old conn
conn.start();
}
else {
log.trace("%s: rejected connection from %s %s", local_addr, peer_addr, explanation(conn_exists, replace));
Util.close(conn); // keep our existing conn, reject accept() and close client_sock
}
}
public BaseServer addConnectionListener(ConnectionListener cl) {
if(cl == null)
return this;
synchronized(conn_listeners) {
if(!conn_listeners.contains(cl))
conn_listeners.add(cl);
}
return this;
}
public BaseServer removeConnectionListener(ConnectionListener cl) {
if(cl == null)
return this;
synchronized(conn_listeners) {
conn_listeners.remove(cl);
}
return this;
}
@ManagedOperation(description="Prints all connections")
public String printConnections() {
StringBuilder sb=new StringBuilder("\n");
for(Map.Entry entry: conns.entrySet())
sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
return sb.toString();
}
/** Only removes the connection if conns.get(address) == conn */
public void removeConnectionIfPresent(Address address, Connection conn) {
if(address == null || conn == null)
return;
Connection tmp=null;
synchronized(this) {
Connection existing=conns.get(address);
if(conn == existing) {
tmp=conns.remove(address);
}
}
if(tmp != null) { // Moved conn close outside of sync block (https://issues.redhat.com/browse/JGRP-2053)
log.trace("%s: removed connection to %s", local_addr, address);
Util.close(tmp);
}
}
/** Used only for testing ! */
public synchronized void clearConnections() {
conns.values().forEach(Util::close);
conns.clear();
}
/** Removes all connections which are not in current_mbrs */
public void retainAll(Collection current_mbrs) {
if(current_mbrs == null)
return;
Map copy=null;
synchronized(this) {
copy=new HashMap<>(conns);
conns.keySet().retainAll(current_mbrs);
}
copy.keySet().removeAll(current_mbrs);
for(Map.Entry entry: copy.entrySet())
Util.close(entry.getValue());
copy.clear();
}
public void notifyConnectionClosed(Connection conn) {
for(ConnectionListener l: conn_listeners) {
try {
l.connectionClosed(conn);
}
catch(Throwable t) {
log.warn("failed notifying listener %s of connection close: %s", l, t);
}
}
}
public void notifyConnectionEstablished(Connection conn) {
for(ConnectionListener l: conn_listeners) {
try {
l.connectionEstablished(conn);
}
catch(Throwable t) {
log.warn("failed notifying listener %s of connection establishment: %s", l, t);
}
}
}
public String toString() {
return new StringBuilder(getClass().getSimpleName()).append(": local_addr=").append(local_addr).append("\n")
.append("connections (" + conns.size() + "):\n").append(super.toString()).append('\n').toString();
}
public void sendToAll(byte[] data, int offset, int length) {
for(Map.Entry entry: conns.entrySet()) {
Connection conn=entry.getValue();
try {
conn.send(data, offset, length);
}
catch(Throwable ex) {
Address dest=entry.getKey();
removeConnectionIfPresent(dest, conn);
log.error("failed sending data to %s: %s", dest, ex);
}
}
}
public void sendToAll(ByteBuffer data) {
for(Map.Entry entry: conns.entrySet()) {
Connection conn=entry.getValue();
try {
conn.send(data.duplicate());
}
catch(Throwable ex) {
Address dest=entry.getKey();
removeConnectionIfPresent(dest, conn);
log.error("failed sending data to %s: %s", dest, ex);
}
}
}
protected static boolean connected(Connection c) {
return c != null && !c.isClosed() && (c.isConnected() || c.isConnectionPending());
}
protected static org.jgroups.Address localAddress(InetAddress bind_addr, int local_port, InetAddress external_addr, int external_port) {
if(external_addr != null)
return new IpAddress(external_addr, external_port > 0? external_port : local_port);
return bind_addr != null? new IpAddress(bind_addr, local_port) : new IpAddress(local_port);
}
protected boolean validateArgs(Address dest, T buffer) {
if(buffer == null) {
log.warn("%s: data is null; discarding message to %s", local_addr, dest);
return false;
}
if(!running.get()) {
log.trace("%s: server is not running, discarding message to %s", local_addr, dest);
return false;
}
return true;
}
protected static String explanation(boolean connection_existed, boolean replace) {
StringBuilder sb=new StringBuilder();
if(connection_existed) {
sb.append(" (connection existed");
if(replace)
sb.append(" but was replaced because my address is lower)");
else
sb.append(" and my address won as it's higher)");
}
else
sb.append(" (connection didn't exist)");
return sb.toString();
}
protected class Reaper implements Runnable, Closeable {
private Thread thread;
public synchronized void start() {
if(thread == null || !thread.isAlive()) {
thread=factory.newThread(new Reaper(), "Reaper");
thread.start();
}
}
public synchronized void stop() {
if(thread != null && thread.isAlive()) {
thread.interrupt();
try {
thread.join(Global.THREAD_SHUTDOWN_WAIT_TIME);
}
catch(InterruptedException ignored) {
}
}
thread=null;
}
public void close() throws IOException {stop();}
public synchronized boolean isAlive() {return thread != null && thread.isDaemon();}
public void run() {
while(!Thread.currentThread().isInterrupted()) {
synchronized(BaseServer.this) {
for(Iterator> it=conns.entrySet().iterator();it.hasNext();) {
Entry entry=it.next();
Connection c=entry.getValue();
if(c.isExpired(System.nanoTime())) {
Util.close(c);
it.remove();
}
}
}
Util.sleep(reaperInterval);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy