Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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());
}
}
}
}