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

org.jgroups.protocols.FD_SOCK2 Maven / Gradle / Ivy

package org.jgroups.protocols;

import org.jgroups.*;
import org.jgroups.annotations.*;
import org.jgroups.blocks.cs.Connection;
import org.jgroups.blocks.cs.ConnectionListener;
import org.jgroups.blocks.cs.NioServer;
import org.jgroups.blocks.cs.Receiver;
import org.jgroups.conf.AttributeType;
import org.jgroups.stack.IpAddress;
import org.jgroups.stack.Protocol;
import org.jgroups.util.*;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.Future;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static java.util.concurrent.TimeUnit.MILLISECONDS;


/**
 * Failure detection protocol based on TCP connections, successor to {@link FD_SOCK}.
 * @author Bela Ban April 27 2021
 */
@MBean(description="Failure detection protocol based on sockets connecting members")
public class FD_SOCK2 extends Protocol implements Receiver, ConnectionListener, ProcessingQueue.Handler {

    @LocalAddress
    @Property(description="The NIC on which the ServerSocket should listen on. " +
      "The following special values are also recognized: GLOBAL, SITE_LOCAL, LINK_LOCAL and NON_LOOPBACK",
              systemProperty={Global.BIND_ADDR},writable=false)
    protected InetAddress                    bind_addr;

    @Property(description="Offset from the transport's bind port")
    protected int                            offset=100;

    @Property(description="Number of ports to probe for finding a free port")
    protected int                            port_range=3;

    @Property(description="Start port for client socket. Default value of 0 picks a random port")
    protected int                            client_bind_port;

    @Property(description="Use \"external_addr\" if you have hosts on different networks behind firewalls. On each " +
      "firewall, set up a port forwarding rule to the local IP (e.g. 192.168.1.100) of the host, then on each host, " +
      "set the \"external_addr\" TCP transport attribute to the external (public IP) address of the firewall",
              systemProperty=Global.EXTERNAL_ADDR,writable=false)
    protected InetAddress                    external_addr;

    @Property(description="Used to map the internal port (bind_port) to an external port. Only used if > 0",
              systemProperty=Global.EXTERNAL_PORT,writable=false)
    protected int                            external_port;

    @Property(description="Interval for broadcasting suspect messages",type=AttributeType.TIME)
    protected long                           suspect_msg_interval=5000;

    @Property(description="Max time (ms) to wait for a connect attempt",type=AttributeType.TIME)
    protected int                            connect_timeout=1000;

    @Property(description="The lowest port the FD_SOCK server can listen on. Needed when wrapping around, looking " +
      "for ports. See https://issues.redhat.com/browse/JGRP-2560 for details")
    protected int                            min_port=1024;

    @Property(description="The highest port the FD_SOCK server can listen on. Needed when wrapping around, looking " +
      "for ports. See https://issues.redhat.com/browse/JGRP-2560 for details.")
    protected int                            max_port=0xFFFF+1; // 65536

    @Property(description="SO_LINGER in seconds. Default of -1 disables it")
    protected int                            linger=-1; // SO_LINGER (number of seconds, -1 disables it)

    @ManagedAttribute(description="Number of suspect events emitted")
    protected int                            num_suspect_events;

    @ManagedAttribute(description="True when this member is leaving the cluster, set to false when joining")
    protected volatile boolean               shutting_down;

    @ManagedAttribute(description="List of pingable members of a cluster")
    protected final Membership               pingable_mbrs=new Membership();

    @ManagedAttribute(description="List of the current cluster members")
    protected final Membership               members=new Membership();

    @ManagedAttribute(description="List of currently suspected members")
    protected final Membership               suspected_mbrs=new Membership();

    @ManagedAttribute(description="The cluster we've joined. Set on joining a cluster, null when unconnected")
    protected String                         cluster;

    protected NioServer                      srv;
    protected final PingDest                 ping_dest=new PingDest(); // address of the member we monitor
    protected TimeScheduler                  timer;
    protected final BroadcastTask            bcast_task=new BroadcastTask(); // to resend SUSPECT message (until view change)
    protected final ProcessingQueue req_handler=new ProcessingQueue().setHandler(this);
    protected final BoundedList      suspect_history=new BoundedList<>(20);


    public FD_SOCK2() {
    }


    @ManagedAttribute(description="The number of currently suspected members")
    public int         getNumSuspectedMembers()          {return suspected_mbrs.size();}

    @ManagedAttribute(description="Ping destination")
    public String      getPingDest()                     {return String.format("%s", ping_dest);}

    @ManagedAttribute(description="The client state (CONNECTED / DISCONNECTED)")
    public String      getClientState()                  {return ping_dest.clientState().toString();}

    public InetAddress getBindAddress()                  {return bind_addr;}
    public FD_SOCK2    setBindAddress(InetAddress b)     {this.bind_addr=b; return this;}

    public InetAddress getExternalAddress()              {return external_addr;}
    public FD_SOCK2    setExternalAddress(InetAddress e) {this.external_addr=e; return this;}

    public int         getExternalPort()                 {return external_port;}
    public FD_SOCK2    setExternalPort(int e)            {this.external_port=e; return this;}

    public long        getSuspectMsgInterval()           {return suspect_msg_interval;}
    public FD_SOCK2    setSuspectMsgInterval(long s)     {this.suspect_msg_interval=s; return this;}

    public int         getClientBindPort()               {return client_bind_port;}
    public FD_SOCK2    setClientBindPort(int c)          {this.client_bind_port=c; return this;}

    public int         getPortRange()                    {return port_range;}
    public FD_SOCK2    setPortRange(int p)               {this.port_range=p; return this;}

    public int         getOffset()                       {return offset;}
    public FD_SOCK2    setOffset(int o)                  {this.offset=o; return this;}

    public int         getLinger()                       {return linger;}
    public FD_SOCK2    setLinger(int l)                  {this.linger=l; return this;}

    @ManagedAttribute(description="Actual port the server is listening on")
    public int getActualBindPort() {
        Address addr=srv != null? srv.localAddress() : null;
        return addr != null? ((IpAddress)addr).getPort() : 0;
    }

    @ManagedOperation(description="Print suspect history")
    public String printSuspectHistory() {return String.join("\n", suspect_history);}

    @ManagedOperation(description="Prints the connections to other FD_SOCK2 instances")
    public String printConnections() {return srv.printConnections();}


    public void start() throws Exception {
        super.start();
        TP transport=getTransport();
        timer=transport.getTimer();
        if(timer == null)
            throw new Exception("timer is null");
        PhysicalAddress addr=transport.getPhysicalAddress();
        int actual_port=((IpAddress)addr).getPort();
        int[] bind_ports=computeBindPorts(actual_port);
        srv=createServer(bind_ports);
        srv.receiver(this).clientBindPort(client_bind_port).usePeerConnections(true).addConnectionListener(this).linger(linger);
        srv.start();
        log.info("server listening on %s", bind_addr != null? srv.getChannel().getLocalAddress() : "*." + getActualBindPort());
    }

    public void stop() {
        Util.close(srv); // calls stop()
        pingable_mbrs.clear();
        suspected_mbrs.clear();
        bcast_task.clear();
        ping_dest.reset();
    }


    public void resetStats() {
        super.resetStats();
        num_suspect_events=0;
        suspect_history.clear();
    }




    public Object up(Message msg) {
        FdHeader hdr=msg.getHeader(this.id);
        if(hdr == null)
            return up_prot.up(msg);  // message did not originate from FD_SOCK2 layer, just pass up
        return handle(hdr, msg.getSrc());
    }

    public void up(MessageBatch batch) {
        for(Iterator it=batch.iterator(); it.hasNext();) {
            Message msg=it.next();
            FdHeader hdr=msg.getHeader(id);
            if(hdr != null) {
                it.remove();
                handle(hdr, msg.getSrc());
            }
        }
        if(!batch.isEmpty())
            up_prot.up(batch);
    }

    public Object down(Event evt) {
        switch(evt.getType()) {

            case Event.UNSUSPECT:
                broadcastUnuspectMessage(evt.getArg());
                break;

            case Event.CONNECT:
            case Event.CONNECT_WITH_STATE_TRANSFER:
            case Event.CONNECT_USE_FLUSH:
            case Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH:
                shutting_down=false;
                cluster=evt.getArg();
                break;

            case Event.DISCONNECT:
                shutting_down=true;
                cluster=null;
                break;

            case Event.VIEW_CHANGE:
                Object ret=down_prot.down(evt);
                handleView(evt.arg());
                return ret;
        }
        return down_prot.down(evt);
    }

    public void receive(Address sender, byte[] buf, int offset, int length) {
        try {
            receive(sender, new ByteArrayDataInputStream(buf, offset, length));
        }
        catch(Exception e) {
            log.error("failed handling message received from " + sender, e);
        }
    }

    public void receive(Address sender, DataInput in) throws Exception {
        Message msg=new EmptyMessage();
        msg.readFrom(in);
        FdHeader hdr=msg.getHeader(id);
        if(hdr == null)
            throw new IllegalStateException(String.format("message from %s does not have a header (id=%d)", sender, id));
        switch(hdr.type) {
            case FdHeader.SUSPECT: case FdHeader.UNSUSPECT: // I should not receive these!
                break;

            case FdHeader.CONNECT:
                log.trace("%s: CONNECT <-- %s", local_addr, msg.src());
                FdHeader h=new FdHeader(FdHeader.CONNECT_RSP).cluster(cluster).serverAddress(local_addr);
                Message rsp=new EmptyMessage().setSrc(local_addr).putHeader(id, h);
                ByteArray buf=messageToBuffer(rsp);
                log.trace("%s: CONNECT-RSP[cluster=%s, srv=%s] --> %s", local_addr, cluster, local_addr, msg.src());
                srv.send(sender, buf.getArray(), buf.getOffset(), buf.getLength());
                break;

            case FdHeader.CONNECT_RSP:
                log.trace("%s: CONNECT-RSP <-- %s [cluster=%s, srv=%s]",
                          local_addr, msg.src(), hdr.cluster, hdr.srv);
                if(Objects.equals(this.cluster, hdr.cluster) && Objects.equals(hdr.srv, ping_dest.dest())) {
                    ping_dest.clientState(State.CONNECTED).setConnectResult(true);
                }
                else {
                    log.trace("%s: addresses don't match: my ping-dest=%s, server's address=%s",
                             local_addr, ping_dest.dest(), hdr.srv);
                    ping_dest.setConnectResult(false);
                }
                break;

            default:
                throw new IllegalStateException(String.format("type %d not known", hdr.type));
        }
    }

    public void connectionEstablished(Connection conn) {
        log.trace("%s: created connection to %s", local_addr, conn.peerAddress());
    }



    public void connectionClosed(Connection conn) {
        Address peer=conn != null? conn.peerAddress() : null;
        if(peer != null && Objects.equals(peer, ping_dest.destPhysical()) && !shutting_down) {
            Address dest=ping_dest.dest();
            if(dest != null) {
                log.debug("%s: connection to %s closed", local_addr, dest);
                pingable_mbrs.remove(dest);
                req_handler.add(new Request(Request.Type.ConnectToNextPingDest, dest));
            }
        }
    }

    protected Object handle(FdHeader hdr, Address sender) {
        switch(hdr.type) {
            case FdHeader.SUSPECT:
                if(hdr.mbrs != null) {
                    log.trace("%s: received SUSPECT message from %s: suspects=%s", local_addr, sender, hdr.mbrs);
                    suspect(hdr.mbrs);
                }
                break;

            case FdHeader.UNSUSPECT:
                if(hdr.mbrs != null) {
                    log.trace("%s: received UNSUSPECT message from %s: mbrs=%s", local_addr, sender, hdr.mbrs);
                    hdr.mbrs.forEach(this::unsuspect);
                    req_handler.add(new Request(Request.Type.ConnectToNextPingDest, null));
                }
                break;
        }
        return null;
    }

    protected NioServer createServer(int[] bind_ports) {
        for(int bind_port: bind_ports) {
            try {
                return new NioServer(getThreadFactory(), getSocketFactory(), bind_addr, bind_port, bind_port,
                                     external_addr, external_port, 0, "jgroups.nio.server.fd_sock");
            }
            catch(Exception ignored) {
            }
        }
        throw new IllegalStateException(String.format("%s: failed to find an available port in ports %s",
                                                      local_addr, Arrays.toString(bind_ports)));
    }

    protected void closeConnectionToPingDest() {
        if(!ping_dest.connected())
            return;
        try {
            ping_dest.clientState(State.DISCONNECTED);
            if(srv.closeConnection(ping_dest.destPhysical(), false) == false)
                return;
            log.debug("%s: connection to %s closed", local_addr, ping_dest);
        }
        finally {
            ping_dest.reset();
        }
    }

    public void handle(Request req) throws Exception {
        switch(req.type) {
            case ConnectToNextPingDest:
                connectToNextPingDest(req.suspect);
                break;
            case CloseConnectionToPingDest:
                closeConnectionToPingDest();
                break;
        }
    }

    protected void handleView(View v) {
        final List
new_mbrs=v.getMembers(); members.set(new_mbrs); suspected_mbrs.retainAll(new_mbrs); bcast_task.adjustSuspects(new_mbrs); pingable_mbrs.set(new_mbrs); if(v.size() < 2) req_handler.add(new Request(Request.Type.CloseConnectionToPingDest, null)); else req_handler.add(new Request(Request.Type.ConnectToNextPingDest, null)); } protected void connectToNextPingDest(Address already_suspect) { List
suspected_members=new ArrayList<>(); if(already_suspect != null) suspected_members.add(already_suspect); while(!pingable_mbrs.isEmpty()) { // connectTo() removes unreachable members from pingable_mbrs Address new_ping_dest=pingable_mbrs.getNext(local_addr); // get the neighbor to my right boolean hasNewPingDest=ping_dest.destChanged(new_ping_dest); if(hasNewPingDest) { if(connectTo(new_ping_dest, this.members)) // also sets ping_dest.dest if successful break; else { if(!ping_dest.connected()) { pingable_mbrs.remove(new_ping_dest); suspected_members.add(new_ping_dest); } } } else break; } if(!suspected_members.isEmpty()) broadcastSuspectMessage(suspected_members); } protected boolean connectTo(Address new_ping_dest, Membership mbrs) { Address old_dest=ping_dest.dest(); IpAddress old_dest_physical=ping_dest.destPhysical(); List dests=getPhysicalAddresses(new_ping_dest); ping_dest.reset().dest(new_ping_dest); log.debug("%s: trying to connect to %s", local_addr, new_ping_dest); long start=System.currentTimeMillis(); for(IpAddress d: dests) { if(connectTo(d, new_ping_dest)) { long time=System.currentTimeMillis() - start; ping_dest.dest(new_ping_dest).destPhysical(d).clientState(State.CONNECTED); log.debug("%s: connected successfully to %s (%s) in %d ms", local_addr, ping_dest.dest(), d, time); // Close the connection to the previous ping_dest if it was *not* our neighbor to the left: // 1. {A,B}: A - B, B - A // '-' denotes a bidirectional connection // 2. {A,B,C}: A - B, B - C, C - A // 3. {A,B,C,D}: A - B, B - C, C - D, D - A: C can now close its connection to A Address left_mbr=mbrs.getPrevious(local_addr); if(old_dest != null && !Objects.equals(old_dest, left_mbr)) { srv.closeConnection(old_dest_physical, false); // close connection to previous ping_dest log.trace("%s: closed connection to previous ping-dest %s (%s)", local_addr, old_dest, old_dest_physical); } return true; } } return false; } protected boolean connectTo(IpAddress dest, Address logical_addr) { Message msg=new EmptyMessage().setSrc(local_addr).putHeader(id, new FdHeader(FdHeader.CONNECT).serverAddress(logical_addr)); try { ByteArray buf=messageToBuffer(msg); log.trace("%s: CONNECT --> %s (%s)", local_addr, logical_addr, dest); ping_dest.resetConnectResult(); boolean existing_connection=srv.hasConnection(dest); srv.send(dest, buf.getArray(), buf.getOffset(), buf.getLength()); ping_dest.waitForConnect(connect_timeout); // returns on CONNECT-RSP, connection exception or timeout if(ping_dest.connected()) return true; if(!existing_connection) // close a new connection srv.closeConnection(dest); return false; } catch(Exception ex) { log.trace("%s: failed connecting to %s: %s", local_addr, dest, ex.getMessage()); return false; } } /** Returns the physical addresses for a in range [a+offset..a+offset+port_range */ protected List getPhysicalAddresses(Address a) { IpAddress pa=(IpAddress)down_prot.down(new Event(Event.GET_PHYSICAL_ADDRESS, a)); if(pa == null) return Collections.emptyList(); InetAddress addr=pa.getIpAddress(); int actual_port=pa.getPort(); int[] bind_ports=computeBindPorts(actual_port); return IntStream.of(bind_ports).boxed().map(p -> new IpAddress(addr, p)).collect(Collectors.toList()); } public static ByteArray messageToBuffer(Message msg) throws Exception { ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(msg.size()); msg.writeTo(out); return out.getBuffer(); } protected int[] computeBindPorts(int actual_port) { int[] bind_ports=new int[port_range+1]; for(int i=0; i <= port_range; i++) { int port=(actual_port+offset+i) % max_port; if(port < min_port) port=port+min_port; bind_ports[i]=port; } return bind_ports; } protected void suspect(Collection
suspects) { if(suspects == null) return; suspects.remove(local_addr); suspects.forEach(suspect -> suspect_history.add(String.format("%s: %s", new Date(), suspect))); suspected_mbrs.add(suspects); Collection
suspects_copy=suspected_mbrs.getMembers(); // returns a copy if(suspects_copy.isEmpty()) return; // Check if we're coord, then send up the stack, make a copy (https://issues.redhat.com/browse/JGRP-2552) Membership eligible_mbrs=this.members.copy().remove(suspected_mbrs.getMembers()); if(eligible_mbrs.isCoord(local_addr)) { log.debug("%s: suspecting %s", local_addr, suspects_copy); up_prot.up(new Event(Event.SUSPECT, suspects_copy)); down_prot.down(new Event(Event.SUSPECT, suspects_copy)); } } protected void unsuspect(Address mbr) { if(mbr == null) return; suspected_mbrs.remove(mbr); bcast_task.removeSuspect(mbr); pingable_mbrs.add(mbr); } /** * Sends a SUSPECT message to all group members. Only the coordinator (or the next member in line if the coord * itself is suspected) will react to this message by installing a new view. To overcome the unreliability * of the SUSPECT message (it may be lost because we are not above any retransmission layer), the following scheme * is used: after sending the SUSPECT message, it is also added to the broadcast task, which will periodically * re-send the SUSPECT until a view is received in which the suspected process is not a member anymore. The reason is * that - at one point - either the coordinator or another participant taking over for a crashed coordinator, will * react to the SUSPECT message and issue a new view, at which point the broadcast task stops. */ protected void broadcastSuspectMessage(List
suspected_members) { if(suspected_members == null || suspected_members.isEmpty()) return; log.debug("%s: broadcasting suspect(%s)", local_addr, suspected_members); // 1. Send a SUSPECT message right away; the broadcast task will take some time to send it (sleeps first) FdHeader hdr=new FdHeader(FdHeader.SUSPECT).mbrs(suspected_members); Message suspect_msg=new EmptyMessage().putHeader(this.id, hdr); down_prot.down(suspect_msg); // 2. Add to broadcast task and start if not running. The task ends when suspected mbrs are removed from mbrship bcast_task.addSuspects(suspected_members); if(stats) num_suspect_events++; } protected void broadcastUnuspectMessage(Address mbr) { if(mbr == null) return; log.debug("%s: broadcasting unsuspect(%s)", local_addr, mbr); // 1. Send a SUSPECT message right away; the broadcast task will take some time to send it (sleeps first) FdHeader hdr=new FdHeader(FdHeader.UNSUSPECT).mbrs(Collections.singleton(mbr)); Message suspect_msg=new EmptyMessage().putHeader(this.id, hdr); down_prot.down(suspect_msg); } protected enum State {DISCONNECTED, CONNECTED} protected static class PingDest { protected Address dest; protected IpAddress dest_physical; protected State client_state=State.DISCONNECTED; protected final Promise connect_promise=new Promise<>(); protected Address dest() {return dest;} protected PingDest dest(Address d) {dest=d; return this;} protected IpAddress destPhysical() {return dest_physical;} protected PingDest destPhysical(IpAddress d) {dest_physical=d; return this;} protected State clientState() {return client_state;} protected PingDest clientState(State s) {client_state=s; return this;} protected boolean connected() {return client_state == State.CONNECTED;} protected boolean destChanged(Address a) {return a != null && !Objects.equals(a, dest);} protected boolean waitForConnect(long time) {return connect_promise.getResult(time);} protected PingDest setConnectResult(boolean b) {connect_promise.setResult(b); return this;} protected PingDest resetConnectResult() {connect_promise.reset(true); return this;} protected PingDest reset() { dest=dest_physical=null; client_state=State.DISCONNECTED; connect_promise.reset(true); return this; } public String toString() { return String.format("%s [%s %s]", dest, dest_physical, client_state); } } public static class FdHeader extends Header { public static final byte SUSPECT = 1; // mbrs public static final byte UNSUSPECT = 2; // mbrs public static final byte CONNECT = 3; public static final byte CONNECT_RSP = 4; // cluster, local addr of server protected byte type; protected Collection
mbrs; protected Address srv; // address of the server, set on CONNECT_RSP protected String cluster; // cluster of the server, set on CONNECT_RSP public FdHeader() { } public FdHeader(byte type) {this.type=type;} public short getMagicId() {return 93;} public Supplier create() {return FdHeader::new;} public FdHeader mbrs(Collection
m) {this.mbrs=m; return this;} public FdHeader serverAddress(Address a) {srv=a; return this;} public FdHeader cluster(String name) {cluster=name; return this;} @Override public int serializedSize() { int retval=Global.BYTE_SIZE + Global.INT_SIZE; // type + mbrs size if(mbrs != null) for(Address m: mbrs) retval+=Util.size(m); retval+=Util.size(cluster) + Util.size(srv); return retval; } @Override public void writeTo(DataOutput out) throws IOException { out.writeByte(type); int size=mbrs != null? mbrs.size() : 0; out.writeInt(size); if(size > 0) for(Address address: mbrs) Util.writeAddress(address, out); Bits.writeString(cluster, out); Util.writeAddress(srv, out); } @Override public void readFrom(DataInput in) throws IOException, ClassNotFoundException { type=in.readByte(); int size=in.readInt(); if(size > 0) { mbrs=new HashSet<>(); for(int i=0; i < size; i++) mbrs.add(Util.readAddress(in)); } cluster=Bits.readString(in); srv=Util.readAddress(in); } public String toString() { return String.format("%s%s%s%s", type2String(type), mbrs != null? ", mbrs=" + mbrs : "", srv != null? ", srv=" + srv : "", cluster != null? ", cluster=" + cluster : ""); } protected static String type2String(byte type) { switch(type) { case SUSPECT: return "SUSPECT"; case UNSUSPECT: return "UNSUSPECT"; case CONNECT: return "CONNECT"; case CONNECT_RSP: return "CONNECT_RSP"; default: return "unknown type (" + type + ')'; } } } protected static class Request { enum Type {ConnectToNextPingDest, CloseConnectionToPingDest}; protected final Type type; protected final Address suspect; public Request(Type type, Address suspect) { this.type=type; this.suspect=suspect; } public String toString() { return String.format("%s (suspect=%s)", type, suspect); } } /** * Task which periodically broadcasts a list of suspected members. The goal is not to lose a SUSPECT message: since * these are broadcast unreliably, they might get dropped. The BroadcastTask makes sure they are retransmitted until * a view has been received which doesn't contain the suspected members any longer. Then the task terminates. */ protected class BroadcastTask implements Runnable { protected final Set
suspects=new HashSet<>(); protected Future future; protected synchronized void addSuspects(List
mbrs) { if(mbrs == null || mbrs.isEmpty()) return; List
tmp=new ArrayList<>(mbrs); tmp.retainAll(members.getMembers()); // removes non-members from mbrs; copy is required or else CCME if(suspects.addAll(tmp)) startTask(); } protected synchronized void removeSuspect(Address suspect) { if(suspect == null) return; if(suspects.remove(suspect) && suspects.isEmpty()) stopTask(); } /** Removes all elements from suspects that are not in the new membership */ protected synchronized void adjustSuspects(List
mbrs) { if(mbrs == null || mbrs.isEmpty()) return; if(suspects.retainAll(mbrs) && suspects.isEmpty()) stopTask(); } protected synchronized void clear() { suspects.clear(); stopTask(); } @GuardedBy("this") protected void startTask() { if(future == null || future.isDone()) { future=timer.scheduleWithFixedDelay(this, suspect_msg_interval, suspect_msg_interval, MILLISECONDS, getTransport() instanceof TCP); } } @GuardedBy("this") protected void stopTask() { if(future != null) { future.cancel(false); future=null; } } public void run() { log.trace("%s: broadcasting SUSPECT message (suspected_mbrs=%s)", local_addr, suspects); FdHeader hdr; synchronized(this) { if(suspects.isEmpty()) { stopTask(); return; } hdr=new FdHeader(FdHeader.SUSPECT).mbrs(new HashSet<>(suspects)); } // mcast SUSPECT to all members Message suspect_msg=new EmptyMessage().putHeader(id, hdr); down_prot.down(suspect_msg); } public String toString() { return FD_SOCK2.class.getSimpleName() + ": " + getClass().getSimpleName(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy