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

org.jgroups.stack.RouterStubManager Maven / Gradle / Ivy


package org.jgroups.stack;

import org.jgroups.Address;
import org.jgroups.PhysicalAddress;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.util.SocketFactory;
import org.jgroups.util.TimeScheduler;
import org.jgroups.util.Util;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * Manages a list of RouterStubs (e.g. health checking, reconnecting etc.
 * @author Vladimir Blagojevic
 * @author Bela Ban
 */
public class RouterStubManager implements Runnable, RouterStub.CloseListener {
    protected final List stubs=new ArrayList<>();
    protected final TimeScheduler    timer;
    protected final String           cluster_name;
    protected final Address          local_addr;
    protected final String           logical_name;
    protected final PhysicalAddress  phys_addr;
    protected final long             reconnect_interval; // reconnect interval (ms)
    protected boolean                use_nio=true;       // whether to use TcpClient or NioClient
    protected Future              reconnector_task, heartbeat_task, timeout_checker_task;
    protected final Log              log;
    protected SocketFactory          socket_factory;
    // Sends a heartbeat to the GossipRouter every heartbeat_interval ms (0 disables this)
    protected long                   heartbeat_interval;
    // Max time (ms) with no received message or heartbeat after which the connection to a GossipRouter is closed.
    // Ignored when heartbeat_interval is 0.
    protected long                   heartbeat_timeout;
    protected final Runnable         send_heartbeat=this::sendHeartbeat;
    protected final Runnable         check_timeouts=this::checkTimeouts;


    public RouterStubManager(Log log, TimeScheduler timer, String cluster_name, Address local_addr,
                             String logical_name, PhysicalAddress phys_addr, long reconnect_interval) {
        this.log=log != null? log : LogFactory.getLog(RouterStubManager.class);
        this.timer=timer;
        this.cluster_name=cluster_name;
        this.local_addr=local_addr;
        this.logical_name=logical_name;
        this.phys_addr=phys_addr;
        this.reconnect_interval=reconnect_interval;
    }

    public static RouterStubManager emptyGossipClientStubManager(Log log, TimeScheduler timer) {
        return new RouterStubManager(log, timer,null,null,null, null,0L);
    }
    

    public RouterStubManager useNio(boolean flag) {use_nio=flag; return this;}
    public boolean           reconnectorRunning() {return reconnector_task != null && !reconnector_task.isDone();}
    public boolean           heartbeaterRunning() {return heartbeat_task != null && !heartbeat_task.isDone();}
    public boolean           timeouterRunning()   {return timeout_checker_task != null && !timeout_checker_task.isDone();}


    public RouterStubManager socketFactory(SocketFactory socket_factory) {
        this.socket_factory=socket_factory;
        return this;
    }

    public RouterStubManager heartbeat(long heartbeat_interval, long heartbeat_timeout) {
        if(heartbeat_interval <= 0) {
            // disable heartbeating
            stopHeartbeatTask();
            stopTimeoutChecker();
            stubs.forEach(s -> s.handleHeartbeats(false));
            this.heartbeat_interval=0;
            return this;
        }
        if(heartbeat_interval >= heartbeat_timeout)
            throw new IllegalArgumentException(String.format("heartbeat_interval (%d) must be < than heartbeat_timeout (%d)",
                                                             heartbeat_interval, heartbeat_timeout));
        // enable heartbeating
        this.heartbeat_interval=heartbeat_interval;
        this.heartbeat_timeout=heartbeat_timeout;
        stubs.forEach(s -> s.handleHeartbeats(true));
        startHeartbeatTask();
        startTimeoutChecker();
        return this;
    }


    /**
     * Applies action to all connected RouterStubs
     */
    public void forEach(Consumer action) {
        synchronized(stubs) {
            stubs.stream().filter(RouterStub::isConnected).forEach(action);
        }
    }

    /**
     * Applies action to a randomly picked RouterStub that's connected
     * @param action
     */
    public void forAny(Consumer action) {
        RouterStub stub=findRandomConnectedStub();
        if(stub != null)
            action.accept(stub);
    }


    public RouterStub createAndRegisterStub(InetSocketAddress local, InetSocketAddress router_addr) {
        RouterStub stub=new RouterStub(local, router_addr, use_nio, this, socket_factory)
          .handleHeartbeats(heartbeat_interval > 0);
        synchronized(stubs) {
            this.stubs.add(stub);
        }
        return stub;
    }

    public RouterStub unregisterStub(InetSocketAddress router_addr_sa) {
        synchronized(stubs) {
            RouterStub s=stubs.stream().filter(st -> Objects.equals(st.remote_sa, router_addr_sa)).findFirst().orElse(null);
            if(s != null) {
                s.destroy();
                stubs.remove(s);
            }
            return s;
        }
    }


    public void connectStubs() {
        boolean failed_connect_attempts=false;
        synchronized(stubs) {
            for(RouterStub stub : stubs) {
                if(!stub.isConnected()) {
                    try {
                        stub.connect(cluster_name, local_addr, logical_name, phys_addr);
                    }
                    catch(Exception ex) {
                        failed_connect_attempts=true;
                    }
                }
            }
        }
        if(failed_connect_attempts)
            startReconnector();
    }

    
    public void disconnectStubs() {
        stopReconnector();
        synchronized(stubs) {
            for(RouterStub stub: stubs) {
                try {
                    stub.disconnect(cluster_name, local_addr);
                }
                catch(Throwable ignored) {
                }
            }
        }
    }
    
    public void destroyStubs() {
        stopReconnector();
        synchronized(stubs) {
            stubs.forEach(RouterStub::destroy);
            stubs.clear();
        }
    }

    public String printStubs() {
        return Util.printListWithDelimiter(stubs, ", ");
    }

    public String printReconnectList() {
        return stubs.stream().filter(s -> !s.isConnected())
          .map(s -> String.format("%s:%d", s.remote_sa.getHostString(), s.remote_sa.getPort()))
          .collect(Collectors.joining(", "));
    }

    public String print() {
        return String.format("Stubs: %s\nReconnect list: %s", printStubs(), printReconnectList());
    }

    public void run() {
        int failed_reconnect_attempts=0;
        synchronized(stubs) {
            for(RouterStub stub : stubs) {
                if(!stub.isConnected()) {
                    try {
                        stub.connect(this.cluster_name, this.local_addr, this.logical_name, this.phys_addr);
                        log.debug("%s: re-established connection to GossipRouter %s (group: %s)",
                                  local_addr, stub.remote(), this.cluster_name);
                    }
                    catch(Exception ex) {
                        failed_reconnect_attempts++;
                    }
                }
            }
        }
        if(failed_reconnect_attempts == 0)
            stopReconnector();
    }

    @Override
    public void closed(RouterStub stub) {
        try {
            if(log.isDebugEnabled())
                log.debug("%s: GossipRouter %s closed connection; starting reconnector task", local_addr, stub.remote());
            stub.destroy();
        }
        catch(Exception ignored) {

        }
        startReconnector();
    }


    protected synchronized void startReconnector() {
        if(reconnector_task == null || reconnector_task.isDone())
            reconnector_task=timer.scheduleWithFixedDelay(this, reconnect_interval, reconnect_interval, TimeUnit.MILLISECONDS);
    }

    protected synchronized void stopReconnector() {
        if(reconnector_task != null)
            reconnector_task.cancel(true);
    }

    protected synchronized void startHeartbeatTask() {
        if(heartbeat_task == null || heartbeat_task.isDone())
            heartbeat_task=timer.scheduleWithFixedDelay(this.send_heartbeat, heartbeat_interval, heartbeat_interval, TimeUnit.MILLISECONDS);
    }

    protected synchronized void stopHeartbeatTask() {
        stopTimeoutChecker();
        if(heartbeat_task != null)
            heartbeat_task.cancel(true);
    }

    protected synchronized void startTimeoutChecker() {
        if(timeout_checker_task == null || timeout_checker_task.isDone())
            timeout_checker_task=timer.scheduleWithFixedDelay(this.check_timeouts, heartbeat_timeout, heartbeat_timeout, TimeUnit.MILLISECONDS);
    }

    protected synchronized void stopTimeoutChecker() {
        if(timeout_checker_task != null)
            timeout_checker_task.cancel(true);
    }

    protected RouterStub findRandomConnectedStub() {
        RouterStub stub=null;
        synchronized(stubs) {
            while(connectedStubs() > 0) {
                RouterStub tmp=Util.pickRandomElement(stubs);
                if(tmp != null && tmp.isConnected())
                    return tmp;
            }
            return stub;
        }
    }

    protected void sendHeartbeat() {
        GossipData hb=new GossipData(GossipType.HEARTBEAT);
        forEach(s -> {
            try {
                s.writeRequest(hb);
            }
            catch(Exception ex) {
                log.error("failed sending heartbeat", ex);
            }
        });
    }

    protected void checkTimeouts() {
        forEach(st -> {
            long timeout=System.currentTimeMillis() - st.lastHeartbeat();
            if(timeout > heartbeat_timeout) {
                log.debug("%s: closed connection to GossipRouter %s as no heartbeat has been received for %s",
                          local_addr, st.remote(),
                          Util.printTime(timeout, TimeUnit.MILLISECONDS), st);
                st.destroy();
            }
        });
        if(disconnectedStubs())
            startReconnector();
    }

    // unsynchronized
    protected int connectedStubs() {
        return (int)stubs.stream().filter(RouterStub::isConnected).count();
    }

    public boolean disconnectedStubs() {
        synchronized(stubs) {
            return stubs.stream().anyMatch(st -> !st.isConnected());
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy