All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glowroot.central.util.ClusterManager Maven / Gradle / Ivy

/*
 * Copyright 2017-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.glowroot.central.util;

import java.io.File;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.util.regex.Pattern;

import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfiguration;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.eviction.EvictionStrategy;
import org.infinispan.eviction.EvictionType;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.remoting.transport.Address;
import org.infinispan.util.function.SerializableFunction;
import org.infinispan.util.function.TriConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.glowroot.central.util.AsyncCache.AsyncCacheLoader;
import org.glowroot.central.util.Cache.CacheLoader;
import org.glowroot.common2.repo.util.LockSet;
import org.glowroot.common2.repo.util.LockSet.LockSetImpl;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;

public abstract class ClusterManager {

    private static final Logger logger = LoggerFactory.getLogger(ClusterManager.class);

    public static ClusterManager create() {
        return new NonClusterManager();
    }

    public static ClusterManager create(File confDir, Map jgroupsProperties) {
        Map properties = Maps.newHashMap(jgroupsProperties);
        String jgroupsConfigurationFile = properties.remove("jgroups.configurationFile");
        if (jgroupsConfigurationFile != null) {
            String initialNodes = properties.get("jgroups.initialNodes");
            if (initialNodes != null) {
                // transform from "host1:port1,host2:port2,..." to "host1[port1],host2[port2],..."
                properties.put("jgroups.initialNodes",
                        Pattern.compile(":([0-9]+)").matcher(initialNodes).replaceAll("[$1]"));
            }
            return new ClusterManagerImpl(confDir, jgroupsConfigurationFile, properties);
        } else {
            return new NonClusterManager();
        }
    }

    public abstract  Cache createPerAgentCache(
            String cacheName, int size, CacheLoader loader);

    public abstract  AsyncCache createPerAgentAsyncCache(
            String cacheName, int size, AsyncCacheLoader loader);

    public abstract  Cache createSelfBoundedCache(
            String cacheName, CacheLoader loader);

    public abstract  LockSet createReplicatedLockSet(
            String mapName, long expirationTime, TimeUnit expirationUnit);

    public abstract  ConcurrentMap createReplicatedMap(
            String mapName);

    public abstract  ConcurrentMap createReplicatedMap(
            String mapName, long expirationTime, TimeUnit expirationUnit);

    public abstract  DistributedExecutionMap createDistributedExecutionMap(
            String cacheName);

    public abstract void close() throws InterruptedException;

    private static class ClusterManagerImpl extends ClusterManager {

        private final EmbeddedCacheManager cacheManager;
        private final Executor executor;

        private ClusterManagerImpl(File confDir, String jgroupsConfigurationFile,
                Map jgroupsProperties) {
            GlobalConfiguration configuration = new GlobalConfigurationBuilder()
                    .transport()
                    .defaultTransport()
                    .addProperty("configurationFile",
                            getConfigurationFilePropertyValue(confDir, jgroupsConfigurationFile))
                    .globalJmxStatistics()
                    .enable()
                    .build();
            cacheManager = doWithSystemProperties(jgroupsProperties,
                    () -> new DefaultCacheManager(configuration));
            executor = MoreExecutors2.newCachedThreadPool("Cluster-Manager-Worker");
        }

        @Override
        public  Cache createPerAgentCache(
                String cacheName, int size, CacheLoader loader) {
            cacheManager.defineConfiguration(cacheName, createCacheConfiguration(size));
            return new CacheImpl(cacheManager.getCache(cacheName), loader);
        }

        @Override
        public  AsyncCache createPerAgentAsyncCache(
                String cacheName, int size, AsyncCacheLoader loader) {
            cacheManager.defineConfiguration(cacheName, createCacheConfiguration(size));
            return new AsyncCacheImpl(cacheManager.getCache(cacheName), loader, executor);
        }

        @Override
        public  Cache createSelfBoundedCache(
                String cacheName, CacheLoader loader) {
            ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
            configurationBuilder.clustering()
                    .cacheMode(CacheMode.INVALIDATION_ASYNC)
                    .jmxStatistics()
                    .enable();
            cacheManager.defineConfiguration(cacheName, configurationBuilder.build());
            return new CacheImpl(cacheManager.getCache(cacheName), loader);
        }

        @Override
        public  LockSet createReplicatedLockSet(
                String mapName, long expirationTime, TimeUnit expirationUnit) {
            return new LockSetImpl(createReplicatedMap(mapName, expirationTime, expirationUnit));
        }

        @Override
        public  ConcurrentMap createReplicatedMap(
                String mapName) {
            return createReplicatedMap(mapName, -1, MILLISECONDS);
        }

        @Override
        public  ConcurrentMap createReplicatedMap(
                String mapName, long expirationTime, TimeUnit expirationUnit) {
            ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
            configurationBuilder.clustering()
                    .cacheMode(CacheMode.REPL_ASYNC)
                    .expiration()
                    .lifespan(expirationTime, expirationUnit)
                    .jmxStatistics()
                    .enable();
            cacheManager.defineConfiguration(mapName, configurationBuilder.build());
            return cacheManager.getCache(mapName);
        }

        @Override
        public  DistributedExecutionMap createDistributedExecutionMap(
                String cacheName) {
            ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
            configurationBuilder.clustering()
                    .cacheMode(CacheMode.LOCAL)
                    .jmxStatistics()
                    .enable();
            cacheManager.defineConfiguration(cacheName, configurationBuilder.build());
            return new DistributedExecutionMapImpl(cacheManager.getCache(cacheName));
        }

        @Override
        public void close() throws InterruptedException {
            cacheManager.stop();
            // org.infinispan.factories.NamedExecutorsFactory.stop() calls shutdownNow() on all
            // executors, but does not awaitTermination(), so sleep a bit to allow time
            SECONDS.sleep(1);
        }

        private static String getConfigurationFilePropertyValue(File confDir,
                String jgroupsConfigurationFile) {
            File file = new File(jgroupsConfigurationFile);
            if (file.isAbsolute()) {
                return jgroupsConfigurationFile;
            }
            file = new File(confDir, jgroupsConfigurationFile);
            if (file.exists()) {
                return file.getAbsolutePath();
            }
            if (jgroupsConfigurationFile.equals("jgroups-tcp.xml")
                    || jgroupsConfigurationFile.equals("jgroups-udp.xml")) {
                return jgroupsConfigurationFile;
            }
            throw new IllegalStateException(
                    "Could not find jgroups.configurationFile: " + jgroupsConfigurationFile);
        }

        private static  V doWithSystemProperties(Map properties,
                Supplier supplier) {
            Map priorSystemProperties = new HashMap<>();
            for (Map.Entry entry : properties.entrySet()) {
                String key = entry.getKey();
                String priorValue = System.getProperty(key);
                if (priorValue != null) {
                    priorSystemProperties.put(key, priorValue);
                }
                System.setProperty(key, entry.getValue());
            }
            try {
                return supplier.get();
            } finally {
                for (String key : properties.keySet()) {
                    String priorValue = priorSystemProperties.get(key);
                    if (priorValue == null) {
                        System.clearProperty(key);
                    } else {
                        System.setProperty(key, priorValue);
                    }
                }
            }
        }

        private static Configuration createCacheConfiguration(int size) {
            ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
            // "max idle" is to keep memory down for deployments with few agents
            // "size" is to keep memory bounded for deployments with lots of agents
            configurationBuilder.clustering()
                    .cacheMode(CacheMode.INVALIDATION_ASYNC)
                    .expiration()
                    .maxIdle(30, MINUTES)
                    .memory()
                    .size(size)
                    .evictionType(EvictionType.COUNT)
                    .evictionStrategy(EvictionStrategy.REMOVE)
                    .jmxStatistics()
                    .enable();
            return configurationBuilder.build();
        }
    }

    private static class NonClusterManager extends ClusterManager {

        @Override
        public  Cache createPerAgentCache(
                String cacheName, int size, CacheLoader loader) {
            return new NonClusterCacheImpl(loader);
        }

        @Override
        public  AsyncCache createPerAgentAsyncCache(
                String cacheName, int size, AsyncCacheLoader loader) {
            return new NonClusterAsyncCacheImpl(loader);
        }

        @Override
        public  Cache createSelfBoundedCache(
                String cacheName, CacheLoader loader) {
            return new NonClusterCacheImpl(loader);
        }

        @Override
        public  DistributedExecutionMap createDistributedExecutionMap(
                String cacheName) {
            return new NonClusterDistributedExecutionMapImpl<>();
        }

        @Override
        public  LockSet createReplicatedLockSet(
                String mapName, long expirationTime, TimeUnit expirationUnit) {
            return new LockSetImpl<>(createReplicatedMap(mapName, expirationTime, expirationUnit));
        }

        @Override
        public  ConcurrentMap createReplicatedMap(
                String mapName) {
            return new ConcurrentHashMap<>();
        }

        @Override
        public  ConcurrentMap createReplicatedMap(
                String mapName, long expirationTime, TimeUnit expirationUnit) {
            return CacheBuilder.newBuilder()
                    .expireAfterWrite(expirationTime, expirationUnit)
                    .build()
                    .asMap();
        }

        @Override
        public void close() {}
    }

    private static class CacheImpl
            implements Cache {

        private final org.infinispan.Cache cache;
        private final CacheLoader loader;

        private CacheImpl(org.infinispan.Cache cache, CacheLoader loader) {
            this.cache = cache;
            this.loader = loader;
        }

        @Override
        public V get(K key) throws Exception {
            V value = cache.get(key);
            if (value == null) {
                value = loader.load(key);
                // FIXME there's a race condition if invalidation is received at this point
                cache.putForExternalRead(key, value);
            }
            return value;
        }

        @Override
        public void invalidate(K key) {
            cache.remove(key);
        }
    }

    private static class AsyncCacheImpl
            implements AsyncCache {

        private final org.infinispan.Cache cache;
        private final AsyncCacheLoader loader;
        private final Executor executor;

        private AsyncCacheImpl(org.infinispan.Cache cache, AsyncCacheLoader loader,
                Executor executor) {
            this.cache = cache;
            this.loader = loader;
            this.executor = executor;
        }

        @Override
        public ListenableFuture get(K key) throws Exception {
            V value = cache.get(key);
            if (value != null) {
                return Futures.immediateFuture(value);
            }
            ListenableFuture future = loader.load(key);
            // FIXME there's a race condition if invalidation is received at this point
            Futures.addCallback(future, new FutureCallback() {
                @Override
                public void onSuccess(V v) {
                    cache.putForExternalRead(key, v);
                }
                @Override
                public void onFailure(Throwable t) {}
            }, executor);
            return future;
        }

        @Override
        public void invalidate(K key) {
            cache.remove(key);
        }
    }

    private static class NonClusterCacheImpl
            implements Cache {

        private final ConcurrentMap cache = new ConcurrentHashMap<>();
        private final CacheLoader loader;

        private NonClusterCacheImpl(CacheLoader loader) {
            this.loader = loader;
        }

        @Override
        public V get(K key) throws Exception {
            V value = cache.get(key);
            if (value == null) {
                value = loader.load(key);
                // FIXME there's a race condition if invalidation is received at this point
                cache.put(key, value);
            }
            return value;
        }

        @Override
        public void invalidate(K key) {
            cache.remove(key);
        }
    }

    private static class NonClusterAsyncCacheImpl
            implements AsyncCache {

        private final ConcurrentMap cache = new ConcurrentHashMap<>();
        private final AsyncCacheLoader loader;

        private NonClusterAsyncCacheImpl(AsyncCacheLoader loader) {
            this.loader = loader;
        }

        @Override
        public ListenableFuture get(K key) throws Exception {
            V value = cache.get(key);
            if (value != null) {
                return Futures.immediateFuture(value);
            }
            ListenableFuture future = loader.load(key);
            // FIXME there's a race condition if invalidation is received at this point
            Futures.addCallback(future, new FutureCallback() {
                @Override
                public void onSuccess(V v) {
                    cache.put(key, v);
                }
                @Override
                public void onFailure(Throwable t) {}
                // ok to use direct executor since the cache is just a simple ConcurrentHashMap
            }, MoreExecutors.directExecutor());
            return future;
        }

        @Override
        public void invalidate(K key) {
            cache.remove(key);
        }
    }

    private static class DistributedExecutionMapImpl
            implements DistributedExecutionMap {

        private final org.infinispan.Cache cache;

        private DistributedExecutionMapImpl(org.infinispan.Cache cache) {
            this.cache = cache;
        }

        @Override
        public @Nullable V get(K key) {
            return cache.get(key);
        }

        @Override
        public void put(K key, V value) {
            cache.put(key, value);
        }

        @Override
        public void remove(K key, V value) {
            cache.remove(key, value);
        }

        @Override
        public  Optional execute(String key,
                int timeoutSeconds, SerializableFunction task) throws Exception {
            CollectingConsumer consumer = new CollectingConsumer();
            CompletableFuture future = cache.getCacheManager().executor().submitConsumer(
                    new AdapterFunction(cache.getName(), key, task), consumer);
            // TODO short-circuit after receiving one (non-empty and non-shutting-down) response,
            // instead of waiting for all responses
            future.get(timeoutSeconds, SECONDS);
            if (consumer.logStackTrace) {
                logger.warn("context for remote error(s) logged above",
                        new Exception("location stack trace"));
            }
            if (consumer.values.isEmpty()) {
                return Optional.empty();
            } else {
                // TODO first non-shutting-down response
                return Optional.of(consumer.values.remove());
            }
        }
    }

    private static class NonClusterDistributedExecutionMapImpl
            implements DistributedExecutionMap {

        private final ConcurrentMap cache = new ConcurrentHashMap<>();

        private NonClusterDistributedExecutionMapImpl() {}

        @Override
        public @Nullable V get(K key) {
            return cache.get(key);
        }

        @Override
        public void put(K key, V value) {
            cache.put(key, value);
        }

        @Override
        public void remove(K key, V value) {
            cache.remove(key, value);
        }

        @Override
        public  Optional execute(String key,
                int timeoutSeconds, SerializableFunction task) throws Exception {
            V value = cache.get(key);
            if (value == null) {
                return Optional.empty();
            }
            return Optional.of(task.apply(value));
        }
    }

    @SuppressWarnings("serial")
    private static class AdapterFunction
            implements SerializableFunction> {

        private final String cacheName;
        private final String key;
        private final SerializableFunction task;

        private AdapterFunction(String cacheName, String key, SerializableFunction task) {
            this.cacheName = cacheName;
            this.key = key;
            this.task = task;
        }

        @Override
        public Optional apply(EmbeddedCacheManager cacheManager) {
            org.infinispan.Cache cache = cacheManager.getCache(cacheName, false);
            if (cache == null) {
                return Optional.empty();
            }
            V value = cache.get(key);
            if (value == null) {
                return Optional.empty();
            }
            return Optional.ofNullable(task.apply(value));
        }
    }

    private static class CollectingConsumer
            implements TriConsumer, /*@Nullable*/ Throwable> {

        private final Queue values = new ConcurrentLinkedQueue<>();
        private volatile boolean logStackTrace;

        @Override
        public void accept(Address address, @Nullable Optional value,
                @Nullable Throwable throwable) {
            if (throwable != null) {
                logger.warn("received error from {}: {}", address, throwable.getMessage(),
                        throwable);
                logStackTrace = true;
                return;
            }
            // value is only null when throwable is not null
            if (checkNotNull(value).isPresent()) {
                values.add(value.get());
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy