All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgroups.blocks.cs.BaseServer Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
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;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

/**
 * 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();
    }

    public void forAllConnections(BiConsumer c) {
        conns.forEach(c);
    }

    /** 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 toString(false); } public String toString(boolean details) { String s=String.format("%s (%s, %d conns)", getClass().getSimpleName(), local_addr, conns.size()); if(details && !conns.isEmpty()) { String tmp=conns.entrySet().stream().map(e -> String.format("%s: %s", e.getKey(), e.getValue())).collect(Collectors.joining("\n")); return String.format("%s:\n%s", s, tmp); } return s; } 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 - 2024 Weber Informatics LLC | Privacy Policy