/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.ha.framework.server; import java.io.Serializable; 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.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import javax.management.MBeanServer; import javax.management.ObjectName; import org.jboss.ha.framework.interfaces.ClusterNode; import org.jboss.ha.framework.interfaces.DistributedReplicantManager; import org.jboss.ha.framework.interfaces.HAPartition; import org.jboss.logging.Logger; //import org.jboss.managed.api.ManagedOperation.Impact; //import org.jboss.managed.api.annotation.ManagementComponent; //import org.jboss.managed.api.annotation.ManagementObject; //import org.jboss.managed.api.annotation.ManagementObjectID; //import org.jboss.managed.api.annotation.ManagementOperation; //import org.jboss.managed.api.annotation.ManagementParameter; //import org.jboss.managed.api.annotation.ManagementProperties; //import org.jboss.managed.api.annotation.ManagementProperty; //import org.jboss.managed.api.annotation.ViewUse; /** * This class manages replicated objects. * * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>. * @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>. * @author Scott.stark@jboss.org * @author <a href="mailto:galder.zamarreno@jboss.com">Galder Zamarreno</a> * @author <a href="mailto:pferraro@redhat.com">Paul Ferraro</a> * @version $Revision: 89852 $ */ public class DistributedReplicantManagerImpl implements DistributedReplicantManagerImplMBean, HAPartition.HAMembershipExtendedListener, HAPartition.HAPartitionStateTransfer, AsynchEventHandler.AsynchEventProcessor { // Constants ----------------------------------------------------- static final String OBJECT_NAME_BASE = "jboss:service=DistributedReplicantManager"; static final String SERVICE_NAME = "DistributedReplicantManager"; private static final Class<?>[] add_types = new Class<?>[] { String.class, String.class, Serializable.class }; private static final Class<?>[] remove_types = new Class<?>[] { String.class, String.class }; // Attributes ---------------------------------------------------- private static final AtomicInteger threadID = new AtomicInteger(); private final ConcurrentMap<String, Serializable> localReplicants = new ConcurrentHashMap<String, Serializable>(); private final ConcurrentMap<String, ConcurrentMap<String, Serializable>> replicants = new ConcurrentHashMap<String, ConcurrentMap<String, Serializable>>(); private final ConcurrentMap<String, List<ReplicantListener>> keyListeners = new ConcurrentHashMap<String, List<ReplicantListener>>(); private Map<String, Integer> intraviewIdCache = new ConcurrentHashMap<String, Integer>(); private final HAPartition partition; /** The handler used to send replicant change notifications asynchronously */ private final AsynchEventHandler asynchHandler; private final Logger log; private String nodeName = null; // Works like a simple latch private volatile CountDownLatch partitionNameKnown = new CountDownLatch(1); // Static -------------------------------------------------------- // Constructors -------------------------------------------------- public DistributedReplicantManagerImpl(HAPartition partition) { super(); if (partition == null) { throw new NullPointerException("partition is null"); } this.partition = partition; this.log = Logger.getLogger(this.getClass().getName() + "." + partition.getPartitionName()); // JBAS-5068 Create the handler early so we don't risk NPEs this.asynchHandler = new AsynchEventHandler(this, "AsynchKeyChangeHandler"); } // Public -------------------------------------------------------- public void createService() throws Exception { if (this.partition == null) { throw new IllegalStateException("HAPartition property must be set before creating DistributedReplicantManager service"); } this.log.debug("registerRPCHandler"); this.partition.registerRPCHandler(SERVICE_NAME, this); this.log.debug("subscribeToStateTransferEvents"); this.partition.subscribeToStateTransferEvents(SERVICE_NAME, this); this.log.debug("registerMembershipListener"); this.partition.registerMembershipListener(this); } public void startService() throws Exception { this.nodeName = this.partition.getNodeName(); this.asynchHandler.start(); this.partitionNameKnown.countDown(); // partition name is now known! //log.info("mergemembers"); //mergeMembers(); } public void stopService() throws Exception { // Stop the asynch handler thread try { this.asynchHandler.stop(); } catch( Exception e) { this.log.warn("Failed to stop asynchHandler", e); } // Reset the latch this.partitionNameKnown = new CountDownLatch(1); } // NR 200505 : [JBCLUSTER-38] unbind at destroy public void destroyService() throws Exception { // we cleanly shutdown. This should be optimized. for (String key: this.localReplicants.keySet()) { this.removeLocal(key); // channel is disconnected, so don't try to notify cluster } if (this.partition != null) { this.partition.unregisterRPCHandler(SERVICE_NAME, this); this.partition.unsubscribeFromStateTransferEvents(SERVICE_NAME, this); this.partition.unregisterMembershipListener(this); } } public void registerWithJmx(MBeanServer server) throws Exception { server.registerMBean(this, this.getObjectName()); } public void unregisterWithJmx(MBeanServer server) throws Exception { server.unregisterMBean(this.getObjectName()); } private ObjectName getObjectName() throws Exception { return new ObjectName("jboss:service=" + SERVICE_NAME + ",partition=" + this.partition.getPartitionName()); } // @ManagementProperty(use={ViewUse.STATISTIC}, description="The partition's name") // @ManagementObjectID(type="DistributedReplicantManager") public String getPartitionName() { return this.partition.getPartitionName(); } // public void setHAPartition(HAPartition clusterPartition) // { // this.partition = clusterPartition; // } // @ManagementOperation(name="listDRMContent", // description="List all known keys and the nodes that have registered bindings", // impact=Impact.ReadOnly) public String listContent() throws Exception { StringBuilder result = new StringBuilder(); result.append("<pre>"); // we merge all replicants services: local only or not // for (String category: this.getAllServices()) { result.append("-----------------------------------------------\n"); result.append("Service : ").append(category).append("\n\n"); Serializable local = this.localReplicants.get(category); if (local == null) { result.append("\t- Service is *not* available locally\n"); } else { result.append("\t- Service *is* also available locally\n"); } Map<String, Serializable> content = this.replicants.get(category); if (content != null) { for (String location: content.keySet()) { result.append("\t- ").append(location).append("\n"); } } result.append ("\n"); } result.append ("</pre>"); return result.toString(); } // @ManagementOperation(name="listDRMContentAsXml", // description="List in XML format all known services and the nodes that have registered bindings", // impact=Impact.ReadOnly) public String listXmlContent() throws Exception { StringBuilder result = new StringBuilder(); result.append ("<ReplicantManager>\n"); // we merge all replicants services: local only or not // for (String category: this.getAllServices()) { result.append("\t<Service>\n"); result.append("\t\t<ServiceName>").append(category).append("</ServiceName>\n"); Serializable local = this.localReplicants.get(category); if (local != null) { result.append("\t\t<Location>\n"); result.append("\t\t\t<Name local=\"True\">").append (this.nodeName).append ("</Name>\n"); result.append("\t\t</Location>\n"); } Map<String, Serializable> content = this.replicants.get(category); if (content != null) { for (String location: content.keySet()) { result.append("\t\t<Location>\n"); result.append("\t\t\t<Name local=\"False\">").append (location).append ("</Name>\n"); result.append("\t\t</Location>\n"); } } result.append("\t</Service>\n"); } result.append("</ReplicantManager>\n"); return result.toString(); } // HAPartition.HAPartitionStateTransfer implementation ---------------------------------------------- public Serializable getCurrentState() { Map<String, ConcurrentMap<String, Serializable>> result = new HashMap<String, ConcurrentMap<String, Serializable>>(); for (String category: this.getAllServices()) { ConcurrentMap<String, Serializable> map = new ConcurrentHashMap<String, Serializable>(); ConcurrentMap<String, Serializable> content = this.replicants.get(category); if (content != null) { map.putAll(content); } Serializable local = this.localReplicants.get(category); if (local != null) { map.put(this.nodeName, local); } result.put(category, map); } // we add the intraviewid cache to the global result // return new Object[] { result, this.intraviewIdCache }; } @SuppressWarnings("unchecked") public void setCurrentState(Serializable newState) { Object[] globalState = (Object[]) newState; Map<String, ConcurrentMap<String, Serializable>> map = (Map) globalState[0]; this.replicants.putAll(map); this.intraviewIdCache = (Map) globalState[1]; if (this.log.isTraceEnabled()) { this.log.trace(this.nodeName + ": received new state, will republish local replicants"); } new MembersPublisher().start(); } // @ManagementOperation(name="getAllDRMServices", // description="Get a collection of the names of all keys for which we have bindings", // impact=Impact.ReadOnly) public Collection<String> getAllServices() { Set<String> services = new HashSet<String>(); services.addAll(this.localReplicants.keySet()); services.addAll(this.replicants.keySet()); return services; } // HAPartition.HAMembershipListener implementation ---------------------------------------------- @SuppressWarnings("unchecked") public void membershipChangedDuringMerge(Vector deadMembers, Vector newMembers, Vector allMembers, Vector originatingGroups) { // Here we only care about deadMembers. Purge all replicant lists of deadMembers // and then notify all listening nodes. // this.log.info("Merging partitions..."); this.log.info("Dead members: " + deadMembers.size()); this.log.info("Originating groups: " + originatingGroups); this.purgeDeadMembers(deadMembers, true); if (newMembers.size() > 0) { new MergeMembers().start(); } } @SuppressWarnings("unchecked") public void membershipChanged(Vector deadMembers, Vector newMembers, Vector allMembers) { // Here we only care about deadMembers. Purge all replicant lists of deadMembers // and then notify all listening nodes. // this.log.info("I am (" + this.nodeName + ") received membershipChanged event:"); this.log.info("Dead members: " + deadMembers.size() + " (" + deadMembers + ")"); this.log.info("New Members : " + newMembers.size() + " (" + newMembers + ")"); this.log.info("All Members : " + allMembers.size() + " (" + allMembers + ")"); this.purgeDeadMembers(deadMembers, false); // we don't need to merge members anymore } // AsynchEventHandler.AsynchEventProcessor implementation ----------------- public void processEvent(Object event) { KeyChangeEvent kce = (KeyChangeEvent) event; this.notifyKeyListeners(kce.key, kce.replicants, kce.merge); } static class KeyChangeEvent { String key; List<Serializable> replicants; boolean merge; } // DistributedReplicantManager implementation ---------------------------------------------- public void add(String key, Serializable replicant) throws Exception { if (this.log.isTraceEnabled()) { this.log.trace("add, key=" + key + ", value=" + replicant); } this.partitionNameKnown.await(); // we don't propagate until our name is known Object[] args = { key, this.nodeName, replicant }; this.partition.callMethodOnCluster(SERVICE_NAME, "_add", args, add_types, true); List<Serializable> replicants = null; synchronized (this.localReplicants) { this.localReplicants.put(key, replicant); replicants = this.getReplicants(key); } this.notifyKeyListeners(key, replicants, false); } public void remove(String key) throws Exception { this.partitionNameKnown.await(); // we don't propagate until our name is known // optimisation: we don't make a costly network call // if there is nothing to remove if (this.localReplicants.containsKey(key)) { Object[] args = { key, this.nodeName }; this.partition.callAsynchMethodOnCluster(SERVICE_NAME, "_remove", args, remove_types, true); this.removeLocal(key); } } private void removeLocal(String key) { List<Serializable> replicants = null; synchronized (this.localReplicants) { if (this.localReplicants.remove(key) != null) { replicants = this.getReplicants(key); } } if (replicants != null) { this.notifyKeyListeners(key, replicants, false); } } public Serializable lookupLocalReplicant(String key) { return this.localReplicants.get(key); } public List<Serializable> lookupReplicants(String key) { Serializable local = this.localReplicants.get(key); Map<String, Serializable> replicant = this.replicants.get(key); if (replicant == null) { return (local != null) ? Collections.singletonList(local) : null; } // JBAS-2677. Put the replicants in view order. ClusterNode[] nodes = this.partition.getClusterNodes(); List<Serializable> result = new ArrayList<Serializable>(nodes.length); for (ClusterNode node: nodes) { String name = node.getName(); if (local != null && this.nodeName.equals(name)) { result.add(local); } else { Serializable value = replicant.get(name); if (value != null) { result.add(value); } } } return result; } private List<Serializable> getReplicants(String key) { List<Serializable> result = this.lookupReplicants(key); if (result == null) { result = Collections.emptyList(); } return result; } // @ManagementOperation(name="lookupDRMNodeNames", // description="Returns the names of the nodes that have registered objects under the given key", // impact=Impact.ReadOnly, // params={@ManagementParameter(name="key", // description="The name of the service")}) @Deprecated public List<String> lookupReplicantsNodeNames(String key) { List<ClusterNode> nodes = this.lookupReplicantsNodes(key); if (nodes == null) return null; List<String> nodeNames = new ArrayList<String>(nodes.size()); for (ClusterNode node : nodes) { nodeNames.add(node.getName()); } return nodeNames; } public List<ClusterNode> lookupReplicantsNodes(String key) { boolean local = this.localReplicants.containsKey(key); Map<String, Serializable> replicant = this.replicants.get(key); if (replicant == null) { return local ? Collections.singletonList(this.partition.getClusterNode()) : null; } Set<String> keys = replicant.keySet(); ClusterNode[] nodes = this.partition.getClusterNodes(); List<ClusterNode> rtn = new ArrayList<ClusterNode>(nodes.length); for (ClusterNode node : nodes) { String name = node.getName(); if (local && this.nodeName.equals(name)) { rtn.add(this.partition.getClusterNode()); } else if (keys.contains(name)) { rtn.add(node); } } return rtn; } public void registerListener(String key, ReplicantListener subscriber) { List<ReplicantListener> list = new CopyOnWriteArrayList<ReplicantListener>(); List<ReplicantListener> existing = this.keyListeners.putIfAbsent(key, list); ((existing != null) ? existing : list).add(subscriber); } public void unregisterListener(String key, DistributedReplicantManager.ReplicantListener subscriber) { List<ReplicantListener> listeners = this.keyListeners.get(key); if (listeners != null) { listeners.remove(subscriber); this.keyListeners.remove(key, Collections.emptyList()); } } // @ManagementOperation(name="getDRMServiceViewId", // description="Returns a hash of the list of nodes that " + // "have registered an object for the given key", // impact=Impact.ReadOnly, // params={@ManagementParameter(name="key", // description="The name of the service")}) public int getReplicantsViewId(String key) { Integer result = this.intraviewIdCache.get(key); return (result != null) ? result.intValue() : 0; } // @ManagementOperation(name="isDRMMasterForService", // description="Returns whether the DRM considers this node to be the master for the given service", // impact=Impact.ReadOnly, // params={@ManagementParameter(name="key", description="The name of the service")}) public boolean isMasterReplica(String key) { if (this.log.isTraceEnabled()) { this.log.trace("isMasterReplica, key=" + key); } // if I am not a replicant, I cannot be the master... // if (!this.localReplicants.containsKey(key)) { if (this.log.isTraceEnabled()) { this.log.trace("no localReplicants, key=" + key + ", isMasterReplica=false"); } return false; } Map<String, Serializable> repForKey = this.replicants.get(key); if (repForKey == null) { if (this.log.isTraceEnabled()) { this.log.trace("no replicants, key=" + key + ", isMasterReplica=true"); } return true; } @SuppressWarnings("unchecked") Vector<String> allNodes = this.partition.getCurrentView(); for (String node: allNodes) { if (this.log.isTraceEnabled()) { this.log.trace("Testing member: " + node); } if (repForKey.containsKey(node)) { if (this.log.isTraceEnabled()) { this.log.trace("Member found in replicaNodes, isMasterReplica=false"); } return false; } else if (node.equals(this.nodeName)) { if (this.log.isTraceEnabled()) { this.log.trace("Member == nodeName, isMasterReplica=true"); } return true; } } return false; } // DistributedReplicantManager cluster callbacks ---------------------------------------------- /** * Cluster callback called when a new replicant is added on another node * @param key Replicant key * @param nodeName Node that add the current replicant * @param replicant Serialized representation of the replicant */ public void _add(String key, String nodeName, Serializable replicant) { if (this.log.isTraceEnabled()) { this.log.trace("_add(" + key + ", " + nodeName); } KeyChangeEvent event = new KeyChangeEvent(); event.key = key; synchronized (this.replicants) { this.addReplicant(key, nodeName, replicant); event.replicants = this.getReplicants(key); } try { this.asynchHandler.queueEvent(event); } catch (InterruptedException e) { Thread.currentThread().interrupt(); this.log.error("_add failed", e); } } /** * Cluster callback called when a replicant is removed by another node * @param key Name of the replicant key * @param nodeName Node that wants to remove its replicant for the give key */ public void _remove(String key, String nodeName) { KeyChangeEvent event = new KeyChangeEvent(); event.key = key; synchronized (this.replicants) { if (this.removeReplicant(key, nodeName)) { event.replicants = this.getReplicants(key); } } if (event.replicants != null) { try { this.asynchHandler.queueEvent(event); } catch (InterruptedException e) { Thread.currentThread().interrupt(); this.log.error("_remove failed", e); } } } protected boolean removeReplicant(String key, String nodeName) { Map<String, Serializable> replicant = this.replicants.get(key); if (replicant != null) { if (replicant.remove(nodeName) != null) { // If replicant map is empty, prune it this.replicants.remove(key, Collections.emptyMap()); return true; } } return false; } /** * Cluster callback called when a node wants to know our complete list of local replicants * @throws Exception Thrown if a cluster communication exception occurs * @return A java array of size 2 containing the name of our node in this cluster and the serialized representation of our state */ public Object[] lookupLocalReplicants() throws Exception { this.partitionNameKnown.await(); // we don't answer until our name is known Object[] rtn = { this.nodeName, this.localReplicants }; if (this.log.isTraceEnabled()) { this.log.trace("lookupLocalReplicants called ("+ rtn[0] + "). Return: " + this.localReplicants.size()); } return rtn; } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- protected int calculateReplicantsHash(List<ClusterNode> members) { int result = 0; for (ClusterNode member: members) { if (member != null) { result += member.getName().hashCode(); // no explicit overflow with int addition } } return result; } protected int updateReplicantsHashId(String key) { // we first get a list of all nodes names that replicate this key // List<ClusterNode> nodes = this.lookupReplicantsNodes(key); int result = 0; if ((nodes == null) || nodes.isEmpty()) { // no nore replicants for this key: we uncache our view id // this.intraviewIdCache.remove(key); } else { result = this.calculateReplicantsHash(nodes); this.intraviewIdCache.put(key, new Integer(result)); } return result; } /////////////// // DistributedReplicantManager API /////////////// /** * Add a replicant to the replicants map. * @param key replicant key name * @param nodeName name of the node that adds this replicant * @param replicant Serialized representation of the replica * @return true, if this replicant was newly added to the map, false otherwise */ protected boolean addReplicant(String key, String nodeName, Serializable replicant) { ConcurrentMap<String, Serializable> map = new ConcurrentHashMap<String, Serializable>(); ConcurrentMap<String, Serializable> existingMap = this.replicants.putIfAbsent(key, map); return (((existingMap != null) ? existingMap : map).put(nodeName, replicant) != null); } /** * Notifies, through a callback, the listeners for a given replicant that the set of replicants has changed * @param key The replicant key name * @param newReplicants The new list of replicants * @param merge is the notification the result of a cluster merge? * */ protected void notifyKeyListeners(String key, List<Serializable> newReplicants, boolean merge) { if (this.log.isTraceEnabled()) { this.log.trace("notifyKeyListeners"); } // we first update the intra-view id for this particular key // int newId = this.updateReplicantsHashId(key); List<ReplicantListener> listeners = this.keyListeners.get(key); if (listeners == null) { if (this.log.isTraceEnabled()) { this.log.trace("listeners is null"); } return; } if (this.log.isTraceEnabled()) { this.log.trace("notifying " + listeners.size() + " listeners for key change: " + key); } for (ReplicantListener listener: listeners) { if (listener != null) { listener.replicantsChanged(key, newReplicants, newId, merge); } } } protected void republishLocalReplicants() { try { if (this.log.isTraceEnabled()) { this.log.trace("Start Re-Publish local replicants in DRM"); } for (Map.Entry<String, Serializable> entry: this.localReplicants.entrySet()) { Serializable replicant = entry.getValue(); if (replicant != null) { String key = entry.getKey(); if (this.log.isTraceEnabled()) { this.log.trace("publishing, key=" + key + ", value=" + replicant); } Object[] args = { key, this.nodeName, replicant }; this.partition.callAsynchMethodOnCluster(SERVICE_NAME, "_add", args, add_types, true); this.notifyKeyListeners(key, this.getReplicants(key), false); } } if (this.log.isTraceEnabled()) { this.log.trace("End Re-Publish local replicants"); } } catch (Exception e) { this.log.error("Re-Publish failed", e); } } //////////////////// // Group membership API //////////////////// protected void mergeMembers() { try { this.log.debug("Start merging members in DRM service..."); ArrayList<?> rsp = this.partition.callMethodOnCluster(SERVICE_NAME, "lookupLocalReplicants", new Object[]{}, new Class[]{}, true); if (rsp.isEmpty()) { this.log.debug("No responses from other nodes during the DRM merge process."); } else { this.log.debug("The DRM merge process has received " + rsp.size() + " answers"); } // Record keys to be notified, and replicant list per key Map<String, List<Serializable>> notifications = new HashMap<String, List<Serializable>>(); // Perform add/remove and replicant lookup atomically synchronized (this.replicants) { for (Object o: rsp) { if (o == null) { this.log.warn("As part of the answers received during the DRM merge process, a NULL message was received!"); continue; } else if (o instanceof Throwable) { this.log.warn("As part of the answers received during the DRM merge process, a Throwable was received!", (Throwable) o); continue; } Object[] objs = (Object[]) o; String node = (String) objs[0]; @SuppressWarnings("unchecked") Map<String, Serializable> replicants = (Map<String, Serializable>) objs[1]; //FIXME: We don't remove keys in the merge process but only add new keys! for (Map.Entry<String, Serializable> entry: replicants.entrySet()) { String key = entry.getKey(); if (this.addReplicant(key, node, entry.getValue())) { notifications.put(key, null); } } // The merge process needs to remove some (now) unexisting keys for (Map.Entry<String, ConcurrentMap<String, Serializable>> entry: this.replicants.entrySet()) { String key = entry.getKey(); if (entry.getValue().containsKey(node)) { if (!replicants.containsKey(key)) { if (this.removeReplicant(key, node)) { notifications.put(key, null); } } } } } // Lookup replicants for each changed key for (Map.Entry<String, List<Serializable>> entry: notifications.entrySet()) { entry.setValue(this.getReplicants(entry.getKey())); } } // Notify recorded key changes for (Map.Entry<String, List<Serializable>> entry: notifications.entrySet()) { this.notifyKeyListeners(entry.getKey(), entry.getValue(), true); } this.log.debug("..Finished merging members in DRM service"); } catch (Exception ex) { this.log.error("merge failed", ex); } } /** * Get rid of dead members from replicant list. * * @param deadMembers the members that are no longer in the view * @param merge whether the membership change occurred during * a cluster merge */ protected void purgeDeadMembers(Vector<ClusterNode> deadMembers, boolean merge) { if (deadMembers.isEmpty()) return; this.log.debug("purgeDeadMembers, " + deadMembers); List<String> deadNodes = new ArrayList<String>(deadMembers.size()); for (ClusterNode member: deadMembers) { deadNodes.add(member.getName()); } for (Map.Entry<String, ConcurrentMap<String, Serializable>> entry: this.replicants.entrySet()) { String key = entry.getKey(); ConcurrentMap<String, Serializable> replicant = entry.getValue(); List<Serializable> replicants = null; synchronized (this.replicants) { if (replicant.keySet().removeAll(deadNodes)) { replicants = this.getReplicants(key); } } if (replicants != null) { this.notifyKeyListeners(key, replicants, merge); } } } /** */ protected void cleanupKeyListeners() { // NOT IMPLEMENTED YET } // Private ------------------------------------------------------- // Inner classes ------------------------------------------------- protected class MergeMembers extends Thread { public MergeMembers() { super("DRM Async Merger#" + threadID.getAndIncrement()); } /** * Called when the service needs to merge with another partition. This * process is performed asynchronously */ public void run() { DistributedReplicantManagerImpl.this.log.debug("Sleeping for 50ms before mergeMembers"); try { // if this thread invokes a cluster method call before // membershipChanged event completes, it could timeout/hang // we need to discuss this with Bela. Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } DistributedReplicantManagerImpl.this.mergeMembers(); } } protected class MembersPublisher extends Thread { public MembersPublisher() { super("DRM Async Publisher#" + threadID.getAndIncrement()); } /** * Called when service needs to re-publish its local replicants to other * cluster members after this node has joined the cluster. */ public void run() { DistributedReplicantManagerImpl.this.log.debug("DRM: Sleeping before re-publishing for 50ms just in case"); try { // if this thread invokes a cluster method call before // membershipChanged event completes, it could timeout/hang // we need to discuss this with Bela. Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } DistributedReplicantManagerImpl.this.republishLocalReplicants(); } } }