org.jgroups.JChannel Maven / Gradle / Ivy
package org.jgroups;
import org.jgroups.annotations.MBean;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.ManagedOperation;
import org.jgroups.conf.ConfiguratorFactory;
import org.jgroups.conf.ProtocolConfiguration;
import org.jgroups.conf.ProtocolStackConfigurator;
import org.jgroups.logging.Log;
import org.jgroups.logging.LogFactory;
import org.jgroups.protocols.TP;
import org.jgroups.stack.*;
import org.jgroups.util.UUID;
import org.jgroups.util.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.InputStream;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Consumer;
/**
* A channel represents a group communication endpoint (like a socket). An application joins a cluster by connecting
* the channel to a cluster name and leaves it by disconnecting. Messages sent over the channel are received by all
* cluster members that are connected to the same cluster (that is, all members that have the same cluster name).
*
* The state machine for a channel is as follows: a channel is created (unconnected). The
* channel is connected to a cluster (connected). Messages can now be sent and received. The
* channel is disconnected from the cluster (unconnected). The channel could now be connected
* to a different cluster again. The channel is closed (closed).
*
* Only a single sender is allowed to be connected to a channel at a time, but there can be more than one channel
* in an application.
*
* Messages can be sent to the cluster members using the send method and messages can be received by setting
* a {@link Receiver} in {@link #setReceiver(Receiver)} and implementing the {@link Receiver#receive(Message)} callback.
*
* @author Bela Ban
* @since 2.0
*/
@MBean(description="JGroups channel")
public class JChannel implements Closeable {
public enum State {
OPEN, // initial state, after channel has been created, or after a disconnect()
CONNECTING, // when connect() is called
CONNECTED, // after successful connect()
CLOSED // after close() has been called
}
protected Receiver receiver;
protected Address local_addr;
protected String name;
protected String cluster_name;
protected View view;
protected volatile State state=State.OPEN;
protected ProtocolStack prot_stack;
protected UpHandler up_handler; // when set, all events are passed to the UpHandler
protected Set channel_listeners;
protected final Log log=LogFactory.getLog(getClass());
protected List address_generators;
protected final Promise state_promise=new Promise<>();
protected boolean state_transfer_supported; // true if state transfer prot is in the stack
protected volatile boolean flush_supported; // true if FLUSH is present in the stack
protected final DiagnosticsHandler.ProbeHandler probe_handler=new JChannelProbeHandler(this);
@ManagedAttribute(description="Collect channel statistics",writable=true)
protected boolean stats=true;
@ManagedAttribute(description="Whether or not to discard messages sent by this channel",writable=true)
protected boolean discard_own_messages;
/**
* Creates a JChannel without a protocol stack; used for programmatic creation of channel and protocol stack
* @param create_protocol_stack If true, the default config is used. If false, no protocol stack is created
*/
public JChannel(boolean create_protocol_stack) {
if(create_protocol_stack) {
try {
init(ConfiguratorFactory.getStackConfigurator(Global.DEFAULT_PROTOCOL_STACK));
}
catch(Exception e) {
throw new RuntimeException(e);
}
}
}
/** Creates a {@code JChannel} with the default stack */
public JChannel() throws Exception {
this(Global.DEFAULT_PROTOCOL_STACK);
}
/**
* Constructs a JChannel instance with the protocol stack configuration based upon the specified properties parameter.
* @param props A file containing a JGroups XML configuration or a URL pointing to an XML configuration
*/
public JChannel(String props) throws Exception {
this(ConfiguratorFactory.getStackConfigurator(props));
}
/**
* Creates a channel with a configuration based on an input stream.
* @param input An input stream, pointing to a streamed configuration. It is the caller's resposibility to close
* the input stream after the constructor returns
*/
public JChannel(InputStream input) throws Exception {
this(ConfiguratorFactory.getStackConfigurator(input));
}
/**
* Constructs a JChannel with the protocol stack configuration contained by the protocol stack configurator parameter.
*
* All the public constructors of this class eventually delegate to this method.
* @param configurator A protocol stack configurator containing a JGroups protocol stack configuration.
*/
public JChannel(ProtocolStackConfigurator configurator) throws Exception {
init(configurator);
}
/**
* Creates a channel from an array of protocols. Note that after a {@link org.jgroups.JChannel#close()}, the protocol
* list should not be reused, ie. new JChannel(protocols) would reuse the same protocol list, and this
* might lead to problems!
* @param protocols The list of protocols, from bottom to top, ie. the first protocol in the list is the transport,
* the last the top protocol
*/
public JChannel(Protocol ... protocols) throws Exception {
this(Arrays.asList(protocols));
}
/**
* Creates a channel from a list of protocols. Note that after a {@link org.jgroups.JChannel#close()}, the protocol
* list should not be reused, ie. new JChannel(protocols) would reuse the same protocol list, and this
* might lead to problems !
* @param protocols The list of protocols, from bottom to top, ie. the first protocol in the list is the transport,
* the last the top protocol
*/
public JChannel(List protocols) throws Exception {
prot_stack=new ProtocolStack().setChannel(this);
for(Protocol prot: protocols) {
if(prot != null) {
prot_stack.addProtocol(prot);
prot.setProtocolStack(prot_stack);
}
}
prot_stack.init();
prot_stack.getTransport().getDiagnosticsHandler().setEnabled(false);
StackType ip_version=Util.getIpStackType();
TP transport=(TP)protocols.get(0);
InetAddress resolved_addr=Configurator.getValueFromObject(transport, "bind_addr");
if(resolved_addr != null)
ip_version=resolved_addr instanceof Inet6Address? StackType.IPv6 : StackType.IPv4;
else if(ip_version == StackType.Dual)
ip_version=StackType.IPv4; // prefer IPv4 addresses
// Substitute vars with defined system props (if any)
List prots=prot_stack.getProtocols();
Map map=new HashMap<>();
for(Protocol prot: prots)
Configurator.resolveAndAssignFields(prot, map, ip_version);
}
public Receiver getReceiver() {return receiver;}
public JChannel setReceiver(Receiver r) {receiver=r; return this;}
public JChannel receiver(Receiver r) {return setReceiver(r);}
public Address getAddress() {return address();}
public Address address() {return state == State.CLOSED ? null : local_addr;}
public String getName() {return name;}
public String name() {return name;}
public JChannel name(String name) {return setName(name);}
public String clusterName() {return getClusterName();}
public View getView() {return view();}
public View view() {return state == State.CONNECTED ? view : null;}
public ProtocolStack getProtocolStack() {return prot_stack;}
public ProtocolStack stack() {return prot_stack;}
public UpHandler getUpHandler() {return up_handler;}
public JChannel setUpHandler(UpHandler h) {this.up_handler=h; return this;}
public boolean getStats() {return stats;}
public boolean stats() {return stats;}
public JChannel setStats(boolean stats) {this.stats=stats; return this;}
public JChannel stats(boolean stats) {this.stats=stats; return this;}
public boolean getDiscardOwnMessages() {return discard_own_messages;}
public JChannel setDiscardOwnMessages(boolean flag) {discard_own_messages=flag; return this;}
@Deprecated(since="5.3.5",forRemoval=true)
public boolean flushSupported() {return flush_supported;}
@ManagedAttribute(name="address")
public String getAddressAsString() {return local_addr != null? local_addr.toString() : "n/a";}
@ManagedAttribute(name="address_uuid")
public String getAddressAsUUID() {return local_addr instanceof UUID? ((UUID)local_addr).toStringLong() : null;}
/** Sets the logical name for the channel. The name will stay associated with this channel for the channel's lifetime
* (until close() is called). This method must be called before calling connect() */
@ManagedAttribute(writable=true, description="The logical name of this channel. Stays with the channel until " +
"the channel is closed")
public JChannel setName(String name) {
if(name == null || isConnected())
return this;
this.name=name;
if(local_addr != null)
NameCache.add(local_addr, this.name);
return this;
}
@ManagedAttribute(description="Returns cluster name this channel is connected to")
public String getClusterName() {return state == State.CONNECTED? cluster_name : null;}
@ManagedAttribute(name="view")
public String getViewAsString() {View v=getView(); return v != null ? v.toString() : "n/a";}
@ManagedAttribute(description="The current state")
public String getState() {return state.toString();}
@ManagedAttribute public boolean isOpen() {return state != State.CLOSED;}
@ManagedAttribute public boolean isConnected() {return state == State.CONNECTED;}
@ManagedAttribute public boolean isConnecting() {return state == State.CONNECTING;}
@ManagedAttribute public boolean isClosed() {return state == State.CLOSED;}
@ManagedAttribute public static String getVersion() {return Version.printDescription();}
/** Adds a ChannelListener that will be notified when a connect, disconnect or close occurs */
public synchronized JChannel addChannelListener(ChannelListener listener) {
if(listener == null)
return this;
if(channel_listeners == null)
channel_listeners=new CopyOnWriteArraySet<>();
channel_listeners.add(listener);
return this;
}
public synchronized JChannel removeChannelListener(ChannelListener listener) {
if(channel_listeners != null && listener != null)
channel_listeners.remove(listener);
return this;
}
public synchronized JChannel clearChannelListeners() {
if(channel_listeners != null)
channel_listeners.clear();
return this;
}
/**
* Sets the new {@link AddressGenerator}. New addresses will be generated using the new generator. This
* should not be done while a channel is connected, but before connecting.
* @param address_generator
* @since 2.12
*/
public JChannel addAddressGenerator(AddressGenerator address_generator) {
if(address_generator == null)
return this;
if(address_generators == null)
address_generators=new ArrayList<>(3);
address_generators.add(address_generator);
return this;
}
public boolean removeAddressGenerator(AddressGenerator address_generator) {
return address_generator != null && address_generators != null && address_generators.remove(address_generator);
}
/**
* Returns the protocol stack configuration in string format. An example of this property is
* "UDP:PING:FDALL:STABLE:NAKACK2:UNICAST3:FRAG2:GMS"
*/
public String getProperties() {return prot_stack != null? prot_stack.printProtocolSpec(true) : null;}
/** Dumps all protocols in string format. If include_props is set, the attrs of each protocol are also printed */
@ManagedOperation
public String printProtocolSpec(boolean include_props) {
ProtocolStack ps=getProtocolStack();
return ps != null? ps.printProtocolSpec(include_props) : null;
}
/** Returns a map of statistics of the various protocols and of the channel itself */
@ManagedOperation
public Map> dumpStats() {
return prot_stack.dumpStats();
}
public Map> dumpStats(String protocol_name, List attrs) {
return prot_stack.dumpStats(protocol_name, attrs);
}
@ManagedOperation
public Map> dumpStats(String protocol_name) {
return prot_stack.dumpStats(protocol_name, null);
}
/**
* Joins the cluster. The application is now able to receive messages from cluster members, views and to send
* messages to (all or single) cluster members. This is a no-op if already connected.
* All channels connecting to the same cluster name form a cluster; messages sent to the cluster will
* be received by all cluster members.
* @param cluster_name The name of the cluster to join
* @exception Exception The protocol stack cannot be started
* @exception IllegalStateException The channel is closed
*/
@ManagedOperation(description="Connects the channel to a group")
public synchronized JChannel connect(String cluster_name) throws Exception {
return connect(cluster_name, true);
}
/** Connects the channel to a cluster. */
@Deprecated(since="5.3.5",forRemoval=true)
@ManagedOperation(description="Connects the channel to a group")
protected synchronized JChannel connect(String cluster_name, boolean useFlushIfPresent) throws Exception {
if(!_preConnect(cluster_name))
return this;
Event connect_event=new Event(useFlushIfPresent? Event.CONNECT_USE_FLUSH : Event.CONNECT, cluster_name);
_connect(connect_event);
state=State.CONNECTED;
notifyChannelConnected(this);
return this;
}
/**
* Joins the cluster and gets the state from a specified state provider.
*
* This method essentially invokes connect and getState methods successively.
* If FLUSH protocol is in channel's stack definition only one flush is executed for both connecting and
* fetching state rather than two flushes if we invoke connect and getState in succession.
* If the channel is closed an exception will be thrown.
* @param cluster_name the cluster name to connect to. Cannot be null.
* @param target the state provider. If null state will be fetched from coordinator, unless this channel is coordinator.
* @param timeout the timeout for state transfer.
* @exception Exception Connecting to the cluster or state transfer was not successful
* @exception IllegalStateException The channel is closed and therefore cannot be used
*/
public synchronized JChannel connect(String cluster_name, Address target, long timeout) throws Exception {
return connect(cluster_name, target, timeout, true);
}
/**
* Joins the cluster and gets a state from a specified state provider.
* This method invokes {@code connect()} and then {@code getState}.
* If the FLUSH protocol is in the channel's stack definition, only one flush round is executed for both connecting and
* fetching the state rather than two flushes if we invoke {@code connect} and {@code getState} in succession.
* If the channel is closed a ChannelClosed exception will be thrown.
* @param cluster_name The cluster name to connect to. Cannot be null.
* @param target The state provider. If null, the state will be fetched from the coordinator, unless this channel
* is the coordinator.
* @param timeout The timeout for the state transfer.
* @exception Exception The protocol stack cannot be started, or the JOIN failed
* @exception IllegalStateException The channel is closed or disconnected
* @exception StateTransferException State transfer was not successful
*
*/
@Deprecated(since="5.3.5",forRemoval=true)
public synchronized JChannel connect(String cluster_name, Address target, long timeout,
boolean useFlushIfPresent) throws Exception {
if(!_preConnect(cluster_name))
return this;
boolean canFetchState=false;
try {
Event connect_event=new Event(useFlushIfPresent? Event.CONNECT_WITH_STATE_TRANSFER_USE_FLUSH : Event.CONNECT_WITH_STATE_TRANSFER, cluster_name);
_connect(connect_event);
state=State.CONNECTED;
notifyChannelConnected(this);
canFetchState=view != null && view.size() > 1;
if(canFetchState) // if I am not the only member in cluster then ...
getState(target, timeout, false); // fetch state from target
}
finally {
// stopFlush if we fetched the state or failed to connect...
if((flushSupported() && useFlushIfPresent) && (canFetchState || state != State.CONNECTED) )
stopFlush();
}
return this;
}
/**
* Leaves the cluster (disconnects the channel if it is connected). If the channel is closed or disconnected, this
* operation is ignored. The channel can then be used to join the same or a different cluster again.
* @see #connect(String)
*/
@ManagedOperation(description="Disconnects the channel if connected")
public synchronized JChannel disconnect() {
switch(state) {
case OPEN: case CLOSED:
break;
case CONNECTING: case CONNECTED:
if(cluster_name != null) {
try {
down(new Event(Event.DISCONNECT, local_addr)); // DISCONNECT is handled by each layer
}
catch(Throwable t) {
log.error(Util.getMessage("DisconnectFailure"), local_addr, t);
}
}
state=State.OPEN;
stopStack(true, false);
notifyChannelDisconnected(this);
init(); // sets local_addr=null; changed March 18 2003 (bela) -- prevented successful rejoining
break;
default:
throw new IllegalStateException("state " + state + " unknown");
}
return this;
}
/**
* Destroys the channel and its associated resources (e.g. the protocol stack). After a channel has been closed,
* invoking methods on it will throw a {@code ChannelClosed} exception (or results in a null operation).
* It is a no-op if the channel is already closed.
* If the channel is connected to a cluster, {@code disconnect()} will be called first.
*/
@ManagedOperation(description="Disconnects and destroys the channel")
public synchronized void close() {
_close(true); // by default disconnect before closing channel and close mq
}
/**
* Sends a message. The message contains
*
* - a destination address (Address). A {@code null} address sends the message to all cluster members.
*
- a source address. Can be left empty as it will be assigned automatically
*
- a byte buffer. The message contents.
*
- several additional fields. They can be used by application programs (or patterns). E.g. a message ID, flags etc
*
*
* @param msg the message to be sent. Destination and buffer should be set. A null destination
* means to send to all group members.
* @exception IllegalStateException thrown if the channel is disconnected or closed
*/
public JChannel send(Message msg) throws Exception {
if(msg == null)
throw new NullPointerException("msg is null");
checkClosedOrNotConnected();
down(msg);
return this;
}
/**
* Helper method to create a Message with given parameters and invoke {@link #send(Message)}.
* @param dst destination address for the message. If null, the message will be sent to all cluster members
* @param obj a serializable object. Will be marshalled into the byte buffer of the message. If it
* is not serializable, an exception will be thrown
* @throws Exception exception thrown if message sending was not successful
*/
public JChannel send(Address dst, Object obj) throws Exception {
Message msg=new ObjectMessage(dst, obj);
return send(msg);
}
/**
* Sends a message. See {@link #send(Address,byte[],int,int)} for details
* @param dst destination address for the message. If null, the message will be sent to all cluster members
* @param buf buffer message payload
* @throws Exception exception thrown if the message sending was not successful
*/
public JChannel send(Address dst, byte[] buf) throws Exception {
return send(new BytesMessage(dst, buf));
}
/**
* Sends a message to a destination.
* * @param dst the destination address. If null, the message will be sent to all cluster nodes (= cluster members)
* @param buf the buffer to be sent
* @param offset the offset into the buffer
* @param length the length of the data to be sent. Has to be <= buf.length - offset. This will send
* {@code length} bytes starting at {@code offset}
* @throws Exception thrown if send() failed
*/
public JChannel send(Address dst, byte[] buf, int offset, int length) throws Exception {
return send(new BytesMessage(dst, buf, offset, length));
}
/**
* Retrieves the full state from the target member.
*
* The state transfer is initiated by invoking getState() on this channel. The state provider in turn invokes the
* {@link Receiver#getState(java.io.OutputStream)} callback and sends the state to this node, the state receiver.
* After the state arrives at the state receiver, the {@link Receiver#setState(java.io.InputStream)} callback
* is invoked to install the state.
* @param target the state provider. If null the coordinator is used by default
* @param timeout the number of milliseconds to wait for the operation to complete successfully. 0
* waits forever until the state has been received
* @see Receiver#getState(java.io.OutputStream)
* @see Receiver#setState(java.io.InputStream)
* @exception IllegalStateException the channel was closed or disconnected, or the flush (if present) failed
* @exception StateTransferException raised if there was a problem during the state transfer
*/
public JChannel getState(Address target, long timeout) throws Exception {
return getState(target, timeout, true);
}
/** Retrieves state from the target member. See {@link #getState(Address,long)} for details */
@Deprecated(since="5.3.5",forRemoval=true)
public JChannel getState(Address target, long timeout, boolean useFlushIfPresent) throws Exception {
Callable flusher =() -> Util.startFlush(JChannel.this);
return getState(target, timeout, useFlushIfPresent?flusher:null);
}
/**
* Performs the flush of the cluster, ie. all pending application messages are flushed out of the cluster and
* all members ack their reception. After this call returns, no member will be allowed to send any
* messages until {@link #stopFlush()} is called.
* In the case of flush collisions (another member attempts flush at roughly the same time) start flush will
* fail by throwing an Exception. Applications can re-attempt flushing after certain back-off period.
* JGroups provides a helper random sleep time backoff algorithm for flush using Util class.
* @param automatic_resume if true call {@link #stopFlush()} after the flush
*/
@Deprecated(since="5.3.5",forRemoval=true)
public JChannel startFlush(boolean automatic_resume) throws Exception {
if(!flushSupported())
throw new IllegalStateException("Flush is not supported, add pbcast.FLUSH protocol to your configuration");
try {
down(new Event(Event.SUSPEND));
return this;
}
catch (Exception e) {
throw new Exception("Flush failed", e.getCause());
}
finally {
if (automatic_resume)
stopFlush();
}
}
/**
* Performs the flush of the cluster but only for the specified flush participants.
* All pending messages are flushed out but only for the flush participants. The remaining members in the cluster
* are not included in the flush. The list of flush participants should be a proper subset of the current view.
* If this flush is not automatically resumed it is an obligation of the application to invoke the matching
* {@link #stopFlush(List)} method with the same list of members used in {@link #startFlush(List, boolean)}.
* @param automatic_resume if true call {@link #stopFlush()} after the flush
*/
@Deprecated(since="5.3.5",forRemoval=true)
public JChannel startFlush(List flushParticipants, boolean automatic_resume) throws Exception {
if (!flushSupported())
throw new IllegalStateException("Flush is not supported, add pbcast.FLUSH protocol to your configuration");
View v = getView();
boolean validParticipants = v != null && v.getMembers().containsAll(flushParticipants);
if (!validParticipants)
throw new IllegalArgumentException("Current view " + v
+ " does not contain all flush participants " + flushParticipants);
try {
down(new Event(Event.SUSPEND, flushParticipants));
return this;
}
catch (Exception e) {
throw new Exception("Flush failed", e.getCause());
}
finally {
if (automatic_resume)
stopFlush(flushParticipants);
}
}
/** Stops the current flush round. Cluster members are unblocked and allowed to send new and pending messages */
@Deprecated(since="5.3.5",forRemoval=true)
public JChannel stopFlush() {
if(!flushSupported())
throw new IllegalStateException("Flush is not supported, add pbcast.FLUSH protocol to your configuration");
down(new Event(Event.RESUME));
return this;
}
/**
* Stops the current flush of the cluster for the specified flush participants. Flush participants are unblocked and
* allowed to send new and pending messages.
* It is an obligation of the application to invoke the matching {@link #startFlush(List, boolean)} method with the
* same list of members prior to invocation of this method.
* @param flushParticipants the flush participants
*/
@Deprecated(since="5.3.5",forRemoval=true)
public JChannel stopFlush(List flushParticipants) {
if(!flushSupported())
throw new IllegalStateException("Flush is not supported, add pbcast.FLUSH protocol to your configuration");
down(new Event(Event.RESUME, flushParticipants));
return this;
}
/**
* Sends an event down the protocol stack. Note that - contrary to {@link #send(Message)}, if the event is a message,
* no checks are performed whether the channel is closed or disconnected. Note that this method is not typically
* used by applications.
* @param evt the message to send down, encapsulated in an event
*/
public Object down(Event evt) {
return evt != null? prot_stack.down(evt) : null;
}
public Object down(Message msg) {
return msg != null? prot_stack.down(msg) : null;
}
/**
* Sends a message down asynchronously. The sending is executed in the transport's thread pool. If the pool is full
* and the message is marked as {@link org.jgroups.Message.TransientFlag#DONT_BLOCK}, then it will be dropped,
* otherwise it will be sent on the caller's thread.
* @param msg The message to be sent
* @param async Whether to send the message asynchronously
* @return A CompletableFuture of the result (or exception)
*/
public CompletableFuture