![JAR search and dependency download from the Maven repository](/logo.png)
bboss.org.jgroups.protocols.MERGE2 Maven / Gradle / Ivy
package bboss.org.jgroups.protocols;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import bboss.org.jgroups.Address;
import bboss.org.jgroups.Event;
import bboss.org.jgroups.Message;
import bboss.org.jgroups.View;
import bboss.org.jgroups.ViewId;
import bboss.org.jgroups.annotations.DeprecatedProperty;
import bboss.org.jgroups.annotations.GuardedBy;
import bboss.org.jgroups.annotations.MBean;
import bboss.org.jgroups.annotations.ManagedAttribute;
import bboss.org.jgroups.annotations.ManagedOperation;
import bboss.org.jgroups.annotations.Property;
import bboss.org.jgroups.stack.Protocol;
import bboss.org.jgroups.util.TimeScheduler;
import bboss.org.jgroups.util.Util;
/**
* Protocol to discover subgroups; e.g., existing due to a network partition (that healed). Example: group
* {p,q,r,s,t,u,v,w} is split into 3 subgroups {p,q}, {r,s,t,u} and {v,w}. This protocol will eventually send
* a MERGE event with the coordinators of each subgroup up the stack: {p,r,v}. Note that - depending on the time
* of subgroup discovery - there could also be 2 MERGE events, which first join 2 of the subgroups, and then the
* resulting group to the last subgroup. The real work of merging the subgroups into one larger group is done
* somewhere above this protocol (typically in the GMS protocol).
* This protocol works as follows:
*
* - If coordinator: periodically retrieve the initial membership (using the FIND_INITIAL_MBRS event provided e.g.
* by PING or TCPPING protocols. This list contains {coord,addr} pairs.
*
- If there is more than 1 coordinator:
*
* - Get all coordinators
*
- Create a MERGE event with the list of coordinators as argument
*
- Send the event up the stack
*
*
*
*
*
* Requires: FIND_INITIAL_MBRS event from below
* Provides: sends MERGE event with list of coordinators up the stack
* @author Bela Ban, Oct 16 2001
* @version $Id: MERGE2.java,v 1.80 2010/04/15 13:00:41 belaban Exp $
*/
@MBean(description="Protocol to discover subgroups existing due to a network partition")
@DeprecatedProperty(names={"use_separate_thread"})
public class MERGE2 extends Protocol {
/* ----------------------------------------- Properties -------------------------------------------------- */
@Property(description="Minimum time in msbetween runs to discover other clusters")
protected long min_interval=5000;
@Property(description="Maximum time in ms between runs to discover other clusters")
protected long max_interval=20000;
@Property(description="Number of inconsistent views with only 1 coord after a MERGE event is sent up")
protected int inconsistent_view_threshold=1;
@Property(description="When receiving a multicast message, checks if the sender is member of the cluster. " +
"If not, initiates a merge")
protected boolean merge_fast=true;
@Property(description="The delay (in milliseconds) after which a merge fast execution is started")
protected long merge_fast_delay=1000;
/* ---------------------------------------------- JMX -------------------------------------------------------- */
@ManagedAttribute(writable=false, description="whether or not a merge task is currently running " +
"(should be the case in a coordinator")
public boolean isMergeTaskRunning() {
return task.isRunning();
}
/* --------------------------------------------- Fields ------------------------------------------------------ */
private Address local_addr=null;
private View view;
private final Set
members=new HashSet();
private final Set merge_candidates=new HashSet();
private final FindSubgroupsTask task=new FindSubgroupsTask();
private volatile boolean is_coord=false;
private TimeScheduler timer;
@ManagedAttribute(description="Number of inconsistent 1-coord views until a MERGE event is sent up the stack")
private int num_inconsistent_views=0;
public MERGE2() {
}
public void init() throws Exception {
timer=getTransport().getTimer();
if(timer == null)
throw new Exception("timer cannot be retrieved");
if(min_interval <= 0 || max_interval <= 0) {
throw new Exception("min_interval and max_interval have to be > 0");
}
if(max_interval <= min_interval) {
throw new Exception ("max_interval has to be greater than min_interval");
}
}
public long getMinInterval() {
return min_interval;
}
public void setMinInterval(long i) {
min_interval=i;
}
public long getMaxInterval() {
return max_interval;
}
public void setMaxInterval(long l) {
max_interval=l;
}
public Vector requiredDownServices() {
Vector retval=new Vector(1);
retval.addElement(new Integer(Event.FIND_INITIAL_MBRS));
return retval;
}
/** Discovers members and detects whether we have multiple coordinator. If so, kicks off a merge */
@ManagedOperation
public void sendMergeSolicitation() {
task.findAndNotify();
}
@ManagedOperation public void startMergeTask() {task.start();}
@ManagedOperation public void stopMergeTask() {task.stop();}
public void stop() {
is_coord=false;
merge_candidates.clear();
task.stop();
}
public Object down(Event evt) {
switch(evt.getType()) {
case Event.VIEW_CHANGE:
Object ret=down_prot.down(evt);
view=(View)evt.getArg();
Vector mbrs=view.getMembers();
if(mbrs == null || mbrs.isEmpty() || local_addr == null) {
task.stop();
return ret;
}
members.clear();
members.addAll(mbrs);
merge_candidates.removeAll(members);
Address coord=mbrs.elementAt(0);
if(coord.equals(local_addr)) {
is_coord=true;
task.start(); // start task if we became coordinator (doesn't start if already running)
}
else {
// if we were coordinator, but are no longer, stop task. this happens e.g. when we merge and someone
// else becomes the new coordinator of the merged group
if(is_coord) {
is_coord=false;
}
task.stop();
}
return ret;
case Event.SET_LOCAL_ADDRESS:
local_addr=(Address)evt.getArg();
return down_prot.down(evt);
default:
return down_prot.down(evt); // Pass on to the layer below us
}
}
public Object up(Event evt) {
switch(evt.getType()) {
case Event.MSG:
if(!merge_fast)
break;
Message msg=(Message)evt.getArg();
Address dest=msg.getDest();
boolean multicast=dest == null || dest.isMulticastAddress();
if(!multicast)
break;
final Address sender=msg.getSrc();
if(!members.contains(sender) && merge_candidates.add(sender)) {
timer.schedule(new Runnable() {
public void run() {
if(!members.contains(sender))
task.findAndNotify();
}
}, merge_fast_delay, TimeUnit.MILLISECONDS);
}
break;
}
return up_prot.up(evt);
}
/**
* Task periodically executing (if role is coordinator). Gets the initial membership and determines
* whether there are subgroups (multiple coordinators for the same group). If yes, it sends a MERGE event
* with the list of the coordinators up the stack
*/
private class FindSubgroupsTask {
@GuardedBy("this")
private Future> future;
private Lock lock=new ReentrantLock();
public synchronized void start() {
if(future == null || future.isDone() || future.isCancelled()) {
future=timer.scheduleWithFixedDelay(new Runnable() {
public void run() {
findAndNotify();
}
}, Math.max(5000L, computeInterval()), computeInterval(), TimeUnit.MILLISECONDS);
}
}
public synchronized void stop() {
if(future != null) {
future.cancel(true);
future=null;
}
}
public synchronized boolean isRunning() {
return future != null && !future.isDone() && !future.isCancelled();
}
public void findAndNotify() {
if(lock.tryLock()) {
try {
_findAndNotify();
}
finally {
lock.unlock();
}
}
}
private void _findAndNotify() {
List discovery_rsps=findAllMembers();
if(log.isTraceEnabled()) {
StringBuilder sb=new StringBuilder();
sb.append("Discovery results:\n");
for(PingData data: discovery_rsps)
sb.append("[" + data.getAddress() + "]: " + data.getView()).append("\n");
log.trace(sb);
}
// Create a map of senders and the views they sent
Map views=getViews(discovery_rsps);
// A list of different views
List different_views=detectDifferentViews(views);
if(different_views.size() <= 1) {
num_inconsistent_views=0;
return;
}
Collection merge_participants=Util.determineMergeParticipants(views);
if(merge_participants.size() == 1) {
if(num_inconsistent_views < inconsistent_view_threshold) {
if(log.isDebugEnabled())
log.debug("dropping MERGE for inconsistent views " + Util.printViews(different_views) +
" as inconsistent view threshold (" + inconsistent_view_threshold +
") has not yet been reached (" + num_inconsistent_views + ")");
num_inconsistent_views++;
return;
}
else
num_inconsistent_views=0;
}
else
num_inconsistent_views=0;
if(log.isDebugEnabled()) {
StringBuilder sb=new StringBuilder();
sb.append(local_addr + " found different views : " + Util.printViews(different_views) +
"; sending up MERGE event with merge participants " + merge_participants + ".\n");
sb.append("Discovery results:\n");
for(PingData data: discovery_rsps)
sb.append("[" + data.getAddress() + "]: " + data.getView()).append("\n");
log.debug(sb.toString());
}
Event evt=new Event(Event.MERGE, views);
try {
up_prot.up(evt);
}
catch(Throwable t) {
log.error("failed sending up MERGE event", t);
}
}
/**
* Returns a random value within [min_interval - max_interval]
*/
long computeInterval() {
return min_interval + Util.random(max_interval - min_interval);
}
/** Returns a list of PingData with only the view from members around the cluster */
@SuppressWarnings("unchecked")
private List findAllMembers() {
List retval=(List)down_prot.down(new Event(Event.FIND_ALL_MBRS));
if(retval == null) return Collections.emptyList();
if(is_coord && local_addr != null) {
PingData tmp=new PingData(local_addr, view, true);
//let's make sure that we add ourself as a coordinator
if(!retval.contains(tmp))
retval.add(tmp);
}
return retval;
}
public Map getViews(List initial_mbrs) {
Map retval=new HashMap();
for(PingData response: initial_mbrs) {
if(!response.isServer())
continue;
Address sender=response.getAddress();
View view=response.getView();
if(sender == null || view == null)
continue;
retval.put(sender,view);
}
return retval;
}
public List detectDifferentViews(Map map) {
final List ret=new ArrayList();
for(View view: map.values()) {
if(view == null)
continue;
ViewId vid=view.getVid();
if(!Util.containsViewId(ret, vid))
ret.add(view);
}
return ret;
}
}
}