/* * JBoss, Home of Professional Open Source. * Copyright 2014, Red Hat, Inc., 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.wildfly.clustering.server.group; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.infinispan.Cache; import org.infinispan.distribution.DistributionManager; import org.infinispan.notifications.cachelistener.annotation.TopologyChanged; import org.infinispan.notifications.cachelistener.event.TopologyChangedEvent; import org.infinispan.notifications.cachemanagerlistener.annotation.Merged; import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged; import org.infinispan.notifications.cachemanagerlistener.event.ViewChangedEvent; import org.infinispan.remoting.transport.Address; import org.jboss.threads.JBossThreadFactory; import org.wildfly.clustering.group.Group; import org.wildfly.clustering.group.Node; import org.wildfly.clustering.server.logging.ClusteringServerLogger; import org.wildfly.clustering.service.concurrent.ClassLoaderThreadFactory; import org.wildfly.security.manager.WildFlySecurityManager; /** * {@link Group} implementation based on the topology of a cache. * @author Paul Ferraro */ @org.infinispan.notifications.Listener public class CacheGroup implements Group, AutoCloseable { private static ThreadFactory createThreadFactory(Class<?> targetClass) { PrivilegedAction<ThreadFactory> action = () -> new JBossThreadFactory(new ThreadGroup(targetClass.getSimpleName()), Boolean.FALSE, null, "%G - %t", null, null); return new ClassLoaderThreadFactory(WildFlySecurityManager.doUnchecked(action), targetClass.getClassLoader()); } private final Map<Listener, ExecutorService> listeners = new ConcurrentHashMap<>(); private final Cache<?, ?> cache; private final InfinispanNodeFactory factory; private final SortedMap<Integer, Boolean> views = Collections.synchronizedSortedMap(new TreeMap<>()); public CacheGroup(CacheGroupConfiguration config) { this.cache = config.getCache(); this.factory = config.getNodeFactory(); this.cache.getCacheManager().addListener(this); this.cache.addListener(this); } @Override public void close() { this.cache.removeListener(this); this.cache.getCacheManager().removeListener(this); // Cleanup any unregistered listeners this.listeners.values().forEach(executor -> { PrivilegedAction<List<Runnable>> action = () -> executor.shutdownNow(); WildFlySecurityManager.doUnchecked(action); }); this.listeners.clear(); } @Override public String getName() { return this.cache.getCacheManager().getClusterName(); } @Override public boolean isCoordinator() { return this.cache.getCacheManager().getAddress().equals(this.getCoordinator()); } @Override public Node getLocalNode() { return this.factory.createNode(this.cache.getCacheManager().getAddress()); } @Override public Node getCoordinatorNode() { return this.factory.createNode(this.getCoordinator()); } private Address getCoordinator() { DistributionManager dist = this.cache.getAdvancedCache().getDistributionManager(); return (dist != null) ? dist.getConsistentHash().getMembers().get(0) : this.cache.getCacheManager().getCoordinator(); } @Override public List<Node> getNodes() { List<Address> addresses = this.getAddresses(); List<Node> nodes = new ArrayList<>(addresses.size()); for (Address address: addresses) { nodes.add(this.factory.createNode(address)); } return nodes; } @Merged @ViewChanged public void viewChanged(ViewChangedEvent event) { // Record view status for use by @TopologyChanged event this.views.put(event.getViewId(), event.isMergeView()); } @TopologyChanged public void topologyChanged(TopologyChangedEvent<?, ?> event) { if (event.isPre()) return; List<Address> oldAddresses = event.getConsistentHashAtStart().getMembers(); List<Node> oldNodes = this.getNodes(oldAddresses); List<Address> newAddresses = event.getConsistentHashAtEnd().getMembers(); List<Node> newNodes = this.getNodes(newAddresses); Set<Address> obsolete = new HashSet<>(oldAddresses); obsolete.removeAll(newAddresses); this.factory.invalidate(obsolete); int viewId = event.getCache().getCacheManager().getTransport().getViewId(); Boolean status = this.views.get(viewId); boolean merged = (status != null) ? status.booleanValue() : false; this.listeners.forEach((listener, executor) -> { try { executor.submit(() -> { try { listener.membershipChanged(oldNodes, newNodes, merged); } catch (Throwable e) { ClusteringServerLogger.ROOT_LOGGER.warn(e.getLocalizedMessage(), e); } }); } catch (RejectedExecutionException e) { // Listener was unregistered } }); // Purge obsolete views this.views.headMap(viewId).clear(); } private List<Node> getNodes(List<Address> addresses) { List<Node> nodes = new ArrayList<>(addresses.size()); for (Address address: addresses) { nodes.add(this.factory.createNode(address)); } return nodes; } @Override public void addListener(Listener listener) { this.listeners.computeIfAbsent(listener, key -> Executors.newSingleThreadExecutor(createThreadFactory(listener.getClass()))); } @Override public void removeListener(Listener listener) { ExecutorService executor = this.listeners.remove(listener); if (executor != null) { PrivilegedAction<List<Runnable>> action = () -> executor.shutdownNow(); WildFlySecurityManager.doUnchecked(action); try { executor.awaitTermination(this.cache.getCacheConfiguration().transaction().cacheStopTimeout(), TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } private List<Address> getAddresses() { DistributionManager dist = this.cache.getAdvancedCache().getDistributionManager(); return (dist != null) ? dist.getConsistentHash().getMembers() : this.cache.getCacheManager().getMembers(); } }