org.jgroups.stack.RouterStub 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).
package org.jgroups.stack;
import org.jgroups.Address;
import org.jgroups.PhysicalAddress;
import org.jgroups.annotations.GuardedBy;
import org.jgroups.blocks.cs.*;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.protocols.PingData;
import org.jgroups.util.ByteArrayDataInputStream;
import org.jgroups.util.ByteArrayDataOutputStream;
import org.jgroups.util.SocketFactory;
import org.jgroups.util.Util;
import java.io.DataInput;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.*;
import static java.lang.System.currentTimeMillis;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.jgroups.util.Util.printTime;
/**
* Client stub that talks to a remote GossipRouter via blocking or non-blocking TCP
* @author Bela Ban
*/
public class RouterStub extends ReceiverAdapter implements Comparable, ConnectionListener {
public interface StubReceiver {void receive(GossipData data);}
public interface MembersNotification {void members(List mbrs);}
public interface CloseListener {void closed(RouterStub stub);}
protected BaseServer client;
protected IpAddress local; // bind address
protected IpAddress remote; // address of remote GossipRouter
protected InetSocketAddress remote_sa; // address of remote GossipRouter, not resolved yet
protected final boolean use_nio;
protected StubReceiver receiver; // external consumer of data, e.g. TUNNEL
protected CloseListener close_listener;
protected SocketFactory socket_factory;
protected static final Log log=LogFactory.getLog(RouterStub.class);
// max number of ms to wait for socket establishment to GossipRouter
protected int sock_conn_timeout=3000;
protected boolean tcp_nodelay=true;
protected int linger=-1; // SO_LINGER (number of seconds, -1 disables it)
protected boolean handle_heartbeats;
// timestamp of last heartbeat (or message from GossipRouter)
protected volatile long last_heartbeat;
// Use bounded queues for sending (https://issues.redhat.com/browse/JGRP-2759)") in TCP
protected boolean non_blocking_sends;
// When sending and non_blocking, how many messages to queue max
protected int max_send_queue=128;
// map to correlate GET_MBRS requests and responses
protected final Map> get_members_map=new HashMap<>();
public RouterStub(InetSocketAddress local_sa, InetSocketAddress remote_sa, boolean use_nio, CloseListener l, SocketFactory sf) {
this(local_sa, remote_sa, use_nio, l, sf, -1);
}
public RouterStub(InetSocketAddress local_sa, InetSocketAddress remote_sa, boolean use_nio, CloseListener l,
SocketFactory sf, int linger) {
this(local_sa, remote_sa, use_nio, l, sf, linger, false, 0);
}
/**
* Creates a stub to a remote_sa {@link GossipRouter}.
* @param local_sa The local_sa bind address and port
* @param remote_sa The address:port of the GossipRouter
* @param use_nio Whether to use ({@link org.jgroups.protocols.TCP_NIO2}) or {@link org.jgroups.protocols.TCP}
* @param l The {@link CloseListener}
* @param sf The {@link SocketFactory} to use to create the client socket
* @param linger SO_LINGER timeout
* @param non_blocking_sends When true and a TcpClient is used, non-blocking sends are enabled
* (https://issues.redhat.com/browse/JGRP-2759)
* @param max_send_queue The max size of the send queue for non-blocking sends
*/
public RouterStub(InetSocketAddress local_sa, InetSocketAddress remote_sa, boolean use_nio, CloseListener l,
SocketFactory sf, int linger, boolean non_blocking_sends, int max_send_queue) {
this.local=local_sa != null? new IpAddress(local_sa.getAddress(), local_sa.getPort())
: new IpAddress((InetAddress)null,0);
this.remote_sa=Objects.requireNonNull(remote_sa);
this.use_nio=use_nio;
this.close_listener=l;
this.socket_factory=sf;
this.linger=linger;
this.non_blocking_sends=non_blocking_sends;
this.max_send_queue=max_send_queue;
if(resolveRemoteAddress()) // sets remote
client=createClient(sf);
}
public IpAddress local() {return local;}
public IpAddress remote() {return remote;}
public RouterStub receiver(StubReceiver r) {receiver=r; return this;}
public StubReceiver receiver() {return receiver;}
public boolean tcpNoDelay() {return tcp_nodelay;}
public RouterStub tcpNoDelay(boolean tcp_nodelay) {this.tcp_nodelay=tcp_nodelay; return this;}
public CloseListener connectionListener() {return close_listener;}
public RouterStub connectionListener(CloseListener l) {this.close_listener=l; return this;}
public int socketConnectionTimeout() {return sock_conn_timeout;}
public RouterStub socketConnectionTimeout(int timeout) {this.sock_conn_timeout=timeout; return this;}
public boolean useNio() {return use_nio;}
public IpAddress gossipRouterAddress() {return remote;}
public boolean isConnected() {return client != null && ((Client)client).isConnected();}
public RouterStub handleHeartbeats(boolean f) {handle_heartbeats=f; return this;}
public boolean handleHeartbeats() {return handle_heartbeats;}
public long lastHeartbeat() {return last_heartbeat;}
public int getLinger() {return linger;}
public RouterStub setLinger(int l) {this.linger=l; return this;}
public boolean nonBlockingSends() {return non_blocking_sends;}
public RouterStub nonBlockingSends(boolean b) {this.non_blocking_sends=b; return this;}
public int maxSendQueue() {return max_send_queue;}
public RouterStub maxSendQueue(int s) {this.max_send_queue=s; return this;}
/**
* Registers mbr with the GossipRouter under the given group, with the given logical name and physical address.
* Establishes a connection to the GossipRouter and sends a CONNECT message.
* @param group The group cluster name under which to register the member
* @param addr The address of the member
* @param logical_name The logical name of the member
* @param phys_addr The physical address of the member
* @throws Exception Thrown when the registration failed
*/
public void connect(String group, Address addr, String logical_name, PhysicalAddress phys_addr) throws Exception {
synchronized(this) {
_doConnect();
}
if(handle_heartbeats)
last_heartbeat=currentTimeMillis();
try {
writeRequest(new GossipData(GossipType.REGISTER, group, addr, logical_name, phys_addr));
}
catch(Exception ex) {
throw new Exception(String.format("connection to %s failed: %s", group, ex));
}
}
public synchronized void connect() throws Exception {
_doConnect();
}
@GuardedBy("lock")
protected void _doConnect() throws Exception {
if(client != null)
client.start();
else {
if(resolveRemoteAddress() && (client=createClient(this.socket_factory)) != null)
client.start();
else
throw new IllegalStateException("client could not be created as remote address has not yet been resolved");
}
}
public void disconnect(String group, Address addr) throws Exception {
if(isConnected())
writeRequest(new GossipData(GossipType.UNREGISTER, group, addr));
}
public void destroy() {
Util.close(client);
}
/**
* Fetches a list of {@link PingData} from the GossipRouter, one for each member in the given group. This call
* returns immediately and when the results are available, the
* {@link org.jgroups.stack.RouterStub.MembersNotification#members(List)} callback will be invoked.
* @param group The group for which we need members information
* @param callback The callback to be invoked.
*/
public void getMembers(final String group, MembersNotification callback) throws Exception {
if(callback == null)
return;
// if(!isConnected()) throw new Exception ("not connected");
synchronized(get_members_map) {
List set=get_members_map.computeIfAbsent(group, k -> new ArrayList<>());
set.add(callback);
}
try {
writeRequest(new GossipData(GossipType.GET_MBRS, group, null));
}
catch(Exception ex) {
removeResponse(group, callback);
throw new Exception(String.format("connection to %s broken. Could not send %s request: %s",
gossipRouterAddress(), GossipType.GET_MBRS, ex));
}
}
public void sendToAllMembers(String group, Address sender, byte[] data, int offset, int length) throws Exception {
sendToMember(group, null, sender, data, offset, length); // null destination represents mcast
}
public void sendToMember(String group, Address dest, Address sender, byte[] data, int offset, int length) throws Exception {
try {
writeRequest(new GossipData(GossipType.MESSAGE, group, dest, data, offset, length).setSender(sender));
}
catch(Exception ex) {
throw new Exception(String.format("connection to %s broken. Could not send message to %s: %s",
gossipRouterAddress(), dest, ex));
}
}
@Override
public void receive(Address sender, byte[] buf, int offset, int length) {
receive(sender, new ByteArrayDataInputStream(buf, offset, length));
}
@Override
public void receive(Address sender, DataInput in) {
try {
GossipData data=new GossipData();
data.readFrom(in);
switch(data.getType()) {
case HEARTBEAT:
break;
case MESSAGE:
case SUSPECT:
if(receiver != null)
receiver.receive(data);
break;
case GET_MBRS_RSP:
notifyResponse(data.getGroup(), data.getPingData());
break;
}
if(handle_heartbeats)
last_heartbeat=currentTimeMillis();
} catch(Exception ex) {
log.error(Util.getMessage("FailedReadingData"), ex);
}
}
@Override
public void connectionClosed(Connection conn) {
if(close_listener != null)
close_listener.closed(this);
}
@Override
public void connectionEstablished(Connection conn) {
}
@Override public int compareTo(RouterStub o) {
return remote.compareTo(o.remote);
}
public int hashCode() {return remote.hashCode();}
public boolean equals(Object obj) {
return compareTo((RouterStub)obj) == 0;
}
public String toString() {
return String.format("RouterStub[local=%s, router_host=%s %s] - age: %s",
client != null? client.localAddress() : "n/a", remote,
isConnected()? "connected" : "disconnected",
isConnected()? printTime(currentTimeMillis()-last_heartbeat, MILLISECONDS) : "n/a");
}
/** Creates remote from remote_sa. If the latter is unresolved, tries to resolve it one more time (e.g. via DNS) */
protected boolean resolveRemoteAddress() {
if(this.remote != null)
return true;
if(this.remote_sa.isUnresolved()) {
this.remote_sa=new InetSocketAddress(remote_sa.getHostString(), remote_sa.getPort());
if(this.remote_sa.isUnresolved())
return false;
}
this.remote=new IpAddress(remote_sa.getAddress(), remote_sa.getPort());
return true;
}
protected BaseServer createClient(SocketFactory sf) {
BaseServer cl=use_nio? new NioClient(local, remote)
: new TcpClient(local, remote).nonBlockingSends(non_blocking_sends).maxSendQueue(max_send_queue);
if(sf != null) cl.socketFactory(sf);
cl.receiver(this);
cl.addConnectionListener(this);
cl.socketConnectionTimeout(sock_conn_timeout).tcpNodelay(tcp_nodelay).linger(linger);
return cl;
}
public void writeRequest(GossipData req) throws Exception {
int size=req.serializedSize();
ByteArrayDataOutputStream out=new ByteArrayDataOutputStream(size+5);
req.writeTo(out);
client.send(remote, out.buffer(), 0, out.position());
}
protected void removeResponse(String group, MembersNotification notif) {
synchronized(get_members_map) {
List set=get_members_map.get(group);
if(set == null || set.isEmpty()) {
get_members_map.remove(group);
return;
}
if(set.remove(notif) && set.isEmpty())
get_members_map.remove(group);
}
}
protected void notifyResponse(String group, List list) {
if(group == null)
return;
if(list == null)
list=Collections.emptyList();
synchronized(get_members_map) {
List set=get_members_map.get(group);
while(set != null && !set.isEmpty()) {
try {
MembersNotification rsp=set.remove(0);
rsp.members(list);
}
catch(Throwable t) {
log.error("failed notifying %s: %s", group, t);
}
}
get_members_map.remove(group);
}
}
}