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.*;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.Callable;
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 of 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;}
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) {
if(isConnected())
throw new IllegalStateException("name cannot be set if channel is connected (should be done before)");
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. */
@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
*
*/
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();
try {
if(msg instanceof Refcountable)
((Refcountable)msg).incr();
down(msg);
}
finally {
if(msg instanceof Refcountable)
((Refcountable)msg).decr();
}
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 */
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
*/
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
*/
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 */
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
*/
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;
}
/**
* Callback method
* Called by the ProtocolStack when a message is received.
* @param evt the event carrying the message from the protocol stack
*/
public Object up(Event evt) {
switch(evt.getType()) {
case Event.VIEW_CHANGE:
view=evt.getArg();
// Bela&Vladimir Oct 27th,2006 (JGroups 2.4): we need to set connected=true because a client can
// call channel.getView() in viewAccepted() callback invoked on this thread (see Event.VIEW_CHANGE handling below)
// not good: we are only connected when we returned from connect() - bela June 22 2007
// Changed: when a channel gets a view of which it is a member then it should be
// connected even if connect() hasn't returned yet ! (bela Noc 2010)
if(state != State.CONNECTED)
state=State.CONNECTED;
break;
case Event.CONFIG:
Map cfg=evt.getArg();
if(cfg != null) {
if(cfg.containsKey("state_transfer")) {
state_transfer_supported=(Boolean)cfg.get("state_transfer");
}
if(cfg.containsKey("flush_supported")) {
flush_supported=(Boolean)cfg.get("flush_supported");
}
}
break;
case Event.GET_STATE_OK:
StateTransferResult result=evt.getArg();
if(up_handler != null) {
try {
Object retval=up_handler.up(evt);
state_promise.setResult(result);
return retval;
}
catch(Throwable t) {
state_promise.setResult(new StateTransferResult(t));
}
}
if(receiver != null) {
try {
if(result.hasBuffer()) {
byte[] tmp_state=result.getBuffer();
ByteArrayInputStream input=new ByteArrayInputStream(tmp_state);
receiver.setState(input);
}
state_promise.setResult(result);
}
catch(Throwable t) {
state_promise.setResult(new StateTransferResult(t));
}
}
break;
case Event.STATE_TRANSFER_INPUTSTREAM_CLOSED:
state_promise.setResult(evt.getArg());
break;
case Event.STATE_TRANSFER_INPUTSTREAM:
// Oct 13,2006 moved to down() when Event.STATE_TRANSFER_INPUTSTREAM_CLOSED is received
// state_promise.setResult(is != null? Boolean.TRUE : Boolean.FALSE);
if(up_handler != null)
return up_handler.up(evt);
InputStream is=evt.getArg();
if(is != null && receiver != null) {
try {
receiver.setState(is);
}
catch(Throwable t) {
throw new RuntimeException("failed calling setState() in state requester", t);
}
}
break;
case Event.STATE_TRANSFER_OUTPUTSTREAM:
if(receiver != null && evt.getArg() != null) {
try {
receiver.getState(evt.getArg());
}
catch(Exception e) {
throw new RuntimeException("failed calling getState() in state provider", e);
}
}
break;
case Event.GET_LOCAL_ADDRESS:
return local_addr;
default:
break;
}
// If UpHandler is installed, pass all events to it and return (UpHandler is e.g. a building block)
if(up_handler != null)
return up_handler.up(evt);
if(receiver != null)
return invokeCallback(evt.getType(), evt.getArg());
return null;
}
public Object up(Message msg) {
// discard local messages (sent by myself to me)
if(discard_own_messages && local_addr != null && msg.getSrc() != null && local_addr.equals(msg.getSrc()))
return null;
// If UpHandler is installed, pass all events to it and return (UpHandler is e.g. a building block)
if(up_handler != null)
return up_handler.up(msg);
if(receiver != null)
receiver.receive(msg);
return null;
}
/** Callback invoked by the protocol stack to deliver a message batch */
public JChannel up(MessageBatch batch) {
// discard local messages (sent by myself to me)
if(discard_own_messages && local_addr != null && batch.sender() != null && local_addr.equals(batch.sender()))
return this;
if(up_handler != null) {
try {
up_handler.up(batch);
}
catch(Throwable t) {
log.error(Util.getMessage("UpHandlerFailure"), t);
}
return this;
}
if(receiver != null) {
try {
receiver.receive(batch);
}
catch(Throwable t) {
log.error(Util.getMessage("ReceiverFailure"), t);
}
}
return this;
}
public String toString() {
return isConnected()? String.format("%s (%s)", address(), cluster_name) : super.toString();
}
@ManagedOperation
public String toString(boolean details) {
StringBuilder sb=new StringBuilder();
sb.append("local_addr=").append(local_addr).append('\n').append("cluster_name=")
.append(cluster_name).append('\n').append("my_view=").append(view).append('\n')
.append("state=").append(state).append('\n');
if(details) {
sb.append("discard_own_messages=").append(discard_own_messages).append('\n');
sb.append("state_transfer_supported=").append(state_transfer_supported).append('\n');
sb.append("props=").append(getProperties()).append('\n');
}
return sb.toString();
}
/* ----------------------------------- Private Methods ------------------------------------- */
protected boolean _preConnect(String cluster_name) throws Exception {
if(cluster_name == null)
throw new IllegalArgumentException("cluster name cannot be null");
if(state == State.CONNECTED) {
log.trace("already connected to %s", this.cluster_name);
return false;
}
checkClosed();
setAddress();
State old_state=state;
state=State.CONNECTING;
try {
startStack(cluster_name);
}
catch(Exception ex) {
state=old_state;
throw ex;
}
return true;
}
protected JChannel _connect(Event evt) throws Exception {
try {
down(evt);
return this;
}
catch(Exception ex) {
cleanup();
throw ex;
}
}
protected JChannel cleanup() {
stopStack(true, false);
state=State.OPEN;
return init();
}
protected JChannel getState(Address target, long timeout, Callable flushInvoker) throws Exception {
checkClosedOrNotConnected();
if(!state_transfer_supported)
throw new IllegalStateException("fetching state will fail as state transfer is not supported. "
+ "Add one of the state transfer protocols to your configuration");
if(target == null)
target=determineCoordinator();
if(Objects.equals(target, local_addr)) {
log.trace(local_addr + ": cannot get state from myself (" + target + "): probably the first member");
return this;
}
boolean initiateFlush=flushSupported() && flushInvoker != null;
if(initiateFlush) {
boolean successfulFlush=false;
try {
successfulFlush=flushInvoker.call();
}
catch(Throwable e) {
successfulFlush=false; // https://issues.redhat.com/browse/JGRP-759
}
if(!successfulFlush)
throw new IllegalStateException("Node " + local_addr + " could not flush the cluster for state retrieval");
}
state_promise.reset();
StateTransferInfo state_info=new StateTransferInfo(target, timeout);
long start=System.currentTimeMillis();
down(new Event(Event.GET_STATE, state_info));
StateTransferResult result=state_promise.getResult(state_info.timeout);
if(initiateFlush)
stopFlush();
if(result == null)
throw new StateTransferException("timeout during state transfer (" + (System.currentTimeMillis() - start) + "ms)");
if(result.hasException())
throw new StateTransferException("state transfer failed", result.getException());
return this;
}
protected Object invokeCallback(int type, Object arg) {
switch(type) {
case Event.VIEW_CHANGE:
receiver.viewAccepted((View)arg);
break;
case Event.GET_APPLSTATE:
byte[] tmp_state=null;
if(receiver != null) {
ByteArrayOutputStream output=new ByteArrayOutputStream(1024);
try {
receiver.getState(output);
tmp_state=output.toByteArray();
}
catch(Exception e) {
throw new RuntimeException(local_addr + ": failed getting state from application", e);
}
}
return new StateTransferInfo(null, 0L, tmp_state);
case Event.BLOCK:
receiver.block();
return true;
case Event.UNBLOCK:
receiver.unblock();
}
return null;
}
protected final JChannel init(ProtocolStackConfigurator configurator) throws Exception {
List configs=configurator.getProtocolStack();
// replace vars with system props
configs.forEach(ProtocolConfiguration::substituteVariables);
prot_stack=new ProtocolStack(this);
prot_stack.setup(configs, configurator); // Setup protocol stack (creates protocol, calls init() on them)
return this;
}
/** Initializes all variables. Used after close() or disconnect(), to be ready for new connect() */
protected JChannel init() {
if(local_addr != null)
down(new Event(Event.REMOVE_ADDRESS, local_addr));
local_addr=null;
cluster_name=null;
view=null;
return this;
}
protected JChannel startStack(String cluster_name) throws Exception {
checkClosed();
this.cluster_name=cluster_name;
prot_stack.startStack(); // calls start() in all protocols, from bottom to top
/*create a temporary view, assume this channel is the only member and is the coordinator*/
view=new View(local_addr, 0, Collections.singletonList(local_addr)); // create a dummy view
TP transport=prot_stack.getTransport();
transport.registerProbeHandler(probe_handler);
return this;
}
/** Generates local_addr. Sends down a REMOVE_ADDRESS (if existing address was present) and a SET_LOCAL_ADDRESS */
protected JChannel setAddress() {
if(name == null || name.isEmpty()) // generate a logical name if not set
name=Util.generateLocalName();
Address old_addr=local_addr;
local_addr=generateAddress(name);
if(name != null && !name.isEmpty()) {
log.info("local_addr: %s, name: %s", local_addr, name);
NameCache.add(local_addr, name);
}
if(old_addr != null)
down(new Event(Event.REMOVE_ADDRESS, old_addr));
for(Protocol p=prot_stack.getTopProtocol(); p != null; p=p.getDownProtocol())
p.setAddress(local_addr);
if(up_handler != null)
up_handler.setLocalAddress(local_addr);
return this;
}
protected Address generateAddress(String name) {
if(address_generators == null || address_generators.isEmpty())
return UUID.randomUUID();
if(address_generators.size() == 1)
return address_generators.get(0).generateAddress(name);
// at this point we have multiple AddressGenerators installed
Address[] addrs=new Address[address_generators.size()];
for(int i=0; i < addrs.length; i++)
addrs[i]=address_generators.get(i).generateAddress(name);
for(int i=0; i < addrs.length; i++) {
if(!(addrs[i] instanceof ExtendedUUID)) {
log.error("address generator %s does not subclass %s which is required if multiple address generators " +
"are installed, removing it", addrs[i].getClass().getSimpleName(), ExtendedUUID.class.getSimpleName());
addrs[i]=null;
}
}
ExtendedUUID uuid=null;
for(int i=0; i < addrs.length; i++) { // we only have ExtendedUUIDs in addrs
if(addrs[i] != null) {
if(uuid == null)
uuid=(ExtendedUUID)addrs[i];
else
uuid.addContents((ExtendedUUID)addrs[i]);
}
}
return uuid != null? uuid : UUID.randomUUID();
}
protected JChannel checkClosed() {
if(state == State.CLOSED)
throw new IllegalStateException("channel is closed");
return this;
}
protected JChannel checkClosedOrNotConnected() {
State tmp=state;
if(tmp == State.CLOSED)
throw new IllegalStateException("channel is closed");
if(!(tmp == State.CONNECTING || tmp == State.CONNECTED))
throw new IllegalStateException("channel is disconnected");
return this;
}
protected JChannel _close(boolean disconnect) {
Address old_addr=local_addr;
if(state == State.CLOSED)
return this;
if(disconnect)
disconnect(); // leave group if connected
stopStack(true, true);
state=State.CLOSED;
notifyChannelClosed(this);
init(); // sets local_addr=null; changed March 18 2003 (bela) -- prevented successful rejoining
if(old_addr != null)
NameCache.remove(old_addr);
return this;
}
protected JChannel stopStack(boolean stop, boolean destroy) {
if(prot_stack != null) {
try {
if(stop)
prot_stack.stopStack(cluster_name);
if(destroy)
prot_stack.destroy();
}
catch(Exception e) {
log.error(Util.getMessage("StackDestroyFailure"), e);
}
TP transport=prot_stack.getTransport();
if(transport != null)
transport.unregisterProbeHandler(probe_handler);
}
return this;
}
protected Address determineCoordinator() {
return view != null? view.getCoord() : null;
}
protected JChannel notifyChannelConnected(JChannel c) {
return notifyListeners(l -> l.channelConnected(c), "channelConnected");
}
protected JChannel notifyChannelDisconnected(JChannel c) {
return notifyListeners(l -> l.channelDisconnected(c), "channelDisconnected()");
}
protected JChannel notifyChannelClosed(JChannel c) {
return notifyListeners(l -> l.channelClosed(c), "channelClosed()");
}
protected JChannel notifyListeners(Consumer func, String msg) {
if(channel_listeners != null) {
try {
channel_listeners.forEach(func);
}
catch(Throwable t) {
log.error(Util.getMessage("CallbackException"), msg, t);
}
}
return this;
}
/* ------------------------------- End of Private Methods ---------------------------------- */
}