package org.jgroups.protocols.pbcast; import org.jgroups.Address; import org.jgroups.Event; import org.jgroups.Message; import org.jgroups.View; import org.jgroups.util.Digest; import org.jgroups.util.MergeId; import org.jgroups.util.MutableDigest; import org.jgroups.util.Tuple; import java.util.*; /** * Coordinator role of the Group MemberShip (GMS) protocol. Accepts JOIN and LEAVE requests and emits view changes * accordingly. * @author Bela Ban */ public class CoordGmsImpl extends ServerGmsImpl { private static final Long MAX_SUSPEND_TIMEOUT=30000L; public CoordGmsImpl(GMS g) { super(g); } public MergeId getMergeId() { return merger.getMergeId(); } public void init() throws Exception { super.init(); merger.cancelMerge(null); } public void join(Address mbr,boolean useFlushIfPresent) { wrongMethod("join"); } public void joinWithStateTransfer(Address mbr,boolean useFlushIfPresent) { wrongMethod("join"); } /** The coordinator itself wants to leave the group */ public void leave(Address mbr) { if(mbr == null) { if(log.isErrorEnabled()) log.error("member's address is null !"); return; } if(mbr.equals(gms.local_addr)) leaving=true; gms.getViewHandler().add(new Request(Request.LEAVE, mbr, false)); gms.getViewHandler().stop(true); // wait until all requests have been processed, then close the queue and leave // If we're the coord leaving, ignore gms.leave_timeout: https://issues.jboss.org/browse/JGRP-1509 long timeout=(long)(Math.max(gms.leave_timeout, gms.view_ack_collection_timeout) * 1.10); gms.getViewHandler().waitUntilCompleted(timeout); } public void suspect(Address mbr) { if(mbr.equals(gms.local_addr)) { if(log.isWarnEnabled()) log.warn("I am the coord and I'm suspected -- will probably leave shortly"); return; } Collection<Request> suspected=new LinkedHashSet<Request>(1); suspected.add(new Request(Request.SUSPECT,mbr,true)); handleMembershipChange(suspected); } /** * Invoked upon receiving a MERGE event from the MERGE layer. Starts the merge protocol. * See description of protocol in DESIGN. * @param views A List of <em>different</em> views detected by the merge protocol */ public void merge(Map<Address, View> views) { merger.merge(views); } public void handleMergeResponse(MergeData data, MergeId merge_id) { merger.handleMergeResponse(data, merge_id); } public void handleMergeCancelled(MergeId merge_id) { merger.handleMergeCancelled(merge_id); } /** * Fetches the digests from all members and installs them again. Used only for diagnosis and support; don't * use this otherwise ! */ void fixDigests() { merger.fixDigests(); } public void handleMembershipChange(Collection<Request> requests) { boolean joinAndStateTransferInitiated=false; boolean useFlushIfPresent=gms.use_flush_if_present; Collection<Address> new_mbrs=new LinkedHashSet<Address>(requests.size()); Collection<Address> suspected_mbrs=new LinkedHashSet<Address>(requests.size()); Collection<Address> leaving_mbrs=new LinkedHashSet<Address>(requests.size()); for(Request req: requests) { switch(req.type) { case Request.JOIN: new_mbrs.add(req.mbr); useFlushIfPresent=req.useFlushIfPresent; break; case Request.JOIN_WITH_STATE_TRANSFER: new_mbrs.add(req.mbr); joinAndStateTransferInitiated=true; useFlushIfPresent=req.useFlushIfPresent; break; case Request.LEAVE: if(req.suspected) suspected_mbrs.add(req.mbr); else leaving_mbrs.add(req.mbr); break; case Request.SUSPECT: suspected_mbrs.add(req.mbr); break; } } new_mbrs.remove(gms.local_addr); // remove myself - cannot join myself (already joined) if(gms.getViewId() == null) { // we're probably not the coord anymore (we just left ourselves), let someone else do it // (client will retry when it doesn't get a response) if(log.isDebugEnabled()) log.debug("gms.view_id is null, I'm not the coordinator anymore (leaving=" + String.valueOf(leaving) + "); the new coordinator will handle the leave request"); return; } List<Address> current_members=gms.members.getMembers(); leaving_mbrs.retainAll(current_members); // remove all elements of leaving_mbrs which are not current members if(suspected_mbrs.remove(gms.local_addr)) { if(log.isWarnEnabled()) log.warn("I am the coord and I'm being suspected -- will probably leave shortly"); } suspected_mbrs.retainAll(current_members); // remove all elements of suspected_mbrs which are not current members // for the members that have already joined, return the current digest and membership for(Iterator<Address> it=new_mbrs.iterator(); it.hasNext();) { Address mbr=it.next(); if(gms.members.contains(mbr)) { // already joined: return current digest and membership if(log.isWarnEnabled()) log.warn(mbr + " already present; returning existing view " + gms.view); Tuple<View,Digest> tuple=gms.getViewAndDigest(); if(tuple != null) { JoinRsp join_rsp=new JoinRsp(tuple.getVal1(), tuple.getVal2()); gms.sendJoinResponse(join_rsp, mbr); } it.remove(); } } if(new_mbrs.isEmpty() && leaving_mbrs.isEmpty() && suspected_mbrs.isEmpty()) { if(log.isTraceEnabled()) log.trace("found no members to add or remove, will not create new view"); return; } View new_view=gms.getNextView(new_mbrs, leaving_mbrs, suspected_mbrs); if(new_view.size() == 0 && gms.local_addr != null && gms.local_addr.equals(new_view.getCreator())) { if(log.isTraceEnabled()) log.trace("view " + new_view + " is empty: will not multicast it (last view)"); if(leaving) { gms.initState(); // in case connect() is called again } return; } if(log.isTraceEnabled()) log.trace(gms.local_addr + ": new members=" + new_mbrs + ", suspected=" + suspected_mbrs + ", leaving=" + leaving_mbrs + ", new view: " + new_view); JoinRsp join_rsp=null; boolean hasJoiningMembers=!new_mbrs.isEmpty(); try { boolean successfulFlush =!useFlushIfPresent || !gms.flushProtocolInStack || gms.startFlush(new_view); if(!successfulFlush && hasJoiningMembers) { // Don't send a join response if the flush fails (http://jira.jboss.org/jira/browse/JGRP-759) // The joiner should block until the previous FLUSH completed sendLeaveResponses(leaving_mbrs); // we still have to send potential leave responses // but let the joining client timeout and send another join request return; } // we cannot garbage collect during joining a new member *if* we're the only member // Example: {A}, B joins, after returning JoinRsp to B, A garbage collects messages higher than those // in the digest returned to the client, so the client will *not* be able to ask for retransmission // of those messages if he misses them if(hasJoiningMembers) { gms.getDownProtocol().down(new Event(Event.SUSPEND_STABLE, MAX_SUSPEND_TIMEOUT)); Digest tmp=gms.getDigest(); // get existing digest MutableDigest join_digest=null; if(tmp == null){ log.error("received null digest from GET_DIGEST: will cause JOIN to fail"); } else { // create a new digest, which contains the new members, minus left members join_digest=new MutableDigest(tmp.size() + new_mbrs.size()); for(Digest.DigestEntry entry: tmp) { Address mbr=entry.getMember(); if(new_view.containsMember(mbr)) join_digest.add(mbr, entry.getHighestDeliveredSeqno(), entry.getHighestReceivedSeqno(), false); } for(Address member: new_mbrs) join_digest.add(member, 0, 0); // ... and add the new members. their first seqno will be 1 } join_rsp=new JoinRsp(new_view, join_digest != null? join_digest.copy() : null); } sendLeaveResponses(leaving_mbrs); // no-op if no leaving members gms.castViewChange(new_view,join_rsp != null? join_rsp.getDigest() : null,join_rsp,new_mbrs); } finally { if(hasJoiningMembers) gms.getDownProtocol().down(new Event(Event.RESUME_STABLE)); if(!joinAndStateTransferInitiated && useFlushIfPresent) gms.stopFlush(); if(leaving) { gms.initState(); // in case connect() is called again } } } /** * Called by the GMS when a VIEW is received. * @param new_view The view to be installed * @param digest If view is a MergeView, digest contains the seqno digest of all members and has to * be set by GMS */ public void handleViewChange(View new_view, Digest digest) { List<Address> mbrs=new_view.getMembers(); if(leaving && !mbrs.contains(gms.local_addr)) return; gms.installView(new_view, digest); } public void stop() { super.stop(); // sets leaving=false merger.stop(); } private void sendLeaveResponses(Collection<Address> leaving_members) { for(Address address: leaving_members){ Message msg=new Message(address, null, null); // send an ack to the leaving member msg.setFlag(Message.OOB); GMS.GmsHeader hdr=new GMS.GmsHeader(GMS.GmsHeader.LEAVE_RSP); msg.putHeader(gms.getId(), hdr); gms.getDownProtocol().down(new Event(Event.MSG, msg)); } } }