/* * Copyright Terracotta, Inc. * * 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.ehcache.impl.internal.store.tiering; import org.ehcache.Cache; import org.ehcache.config.ResourcePools; import org.ehcache.config.ResourceType; import org.ehcache.core.CacheConfigurationChangeListener; import org.ehcache.core.collections.ConcurrentWeakIdentityHashMap; import org.ehcache.core.spi.function.BiFunction; import org.ehcache.core.spi.function.Function; import org.ehcache.core.spi.function.NullaryFunction; import org.ehcache.core.spi.store.Store; import org.ehcache.core.spi.store.StoreAccessException; import org.ehcache.core.spi.store.events.StoreEventSource; import org.ehcache.core.spi.store.tiering.AuthoritativeTier; import org.ehcache.core.spi.store.tiering.CachingTier; import org.ehcache.spi.service.Service; import org.ehcache.spi.service.ServiceConfiguration; import org.ehcache.spi.service.ServiceDependencies; import org.ehcache.spi.service.ServiceProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terracotta.statistics.StatisticsManager; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicReference; /** * A {@link Store} implementation supporting a tiered caching model. */ public class TieredStore<K, V> implements Store<K, V> { private static final Logger LOG = LoggerFactory.getLogger(TieredStore.class); private final AtomicReference<CachingTier<K, V>> cachingTierRef; private final CachingTier<K, V> noopCachingTier; private final CachingTier<K, V> realCachingTier; private final AuthoritativeTier<K, V> authoritativeTier; public TieredStore(CachingTier<K, V> cachingTier, AuthoritativeTier<K, V> authoritativeTier) { this.cachingTierRef = new AtomicReference<CachingTier<K, V>>(cachingTier); this.authoritativeTier = authoritativeTier; this.realCachingTier = cachingTier; this.noopCachingTier = new NoopCachingTier<K, V>(authoritativeTier); this.realCachingTier.setInvalidationListener(new CachingTier.InvalidationListener<K, V>() { @Override public void onInvalidation(K key, ValueHolder<V> valueHolder) { TieredStore.this.authoritativeTier.flush(key, valueHolder); } }); this.authoritativeTier.setInvalidationValve(new AuthoritativeTier.InvalidationValve() { @Override public void invalidateAll() throws StoreAccessException { invalidateAllInternal(); } @Override public void invalidateAllWithHash(long hash) throws StoreAccessException { cachingTier().invalidateAllWithHash(hash); } }); StatisticsManager.associate(cachingTier).withParent(this); StatisticsManager.associate(authoritativeTier).withParent(this); } @Override public ValueHolder<V> get(final K key) throws StoreAccessException { try { return cachingTier().getOrComputeIfAbsent(key, new Function<K, ValueHolder<V>>() { @Override public ValueHolder<V> apply(K key) { try { return authoritativeTier.getAndFault(key); } catch (StoreAccessException cae) { throw new ComputationException(cae); } } }); } catch (ComputationException ce) { throw ce.getStoreAccessException(); } } static class ComputationException extends RuntimeException { public ComputationException(StoreAccessException cause) { super(cause); } public StoreAccessException getStoreAccessException() { return (StoreAccessException) getCause(); } @Override public synchronized Throwable fillInStackTrace() { return this; } } @Override public boolean containsKey(K key) throws StoreAccessException { return authoritativeTier.containsKey(key); } @Override public PutStatus put(final K key, final V value) throws StoreAccessException { try { return authoritativeTier.put(key, value); } finally { cachingTier().invalidate(key); } } @Override public ValueHolder<V> putIfAbsent(K key, V value) throws StoreAccessException { try { return authoritativeTier.putIfAbsent(key, value); } finally { cachingTier().invalidate(key); } } @Override public boolean remove(K key) throws StoreAccessException { try { return authoritativeTier.remove(key); } finally { cachingTier().invalidate(key); } } @Override public RemoveStatus remove(K key, V value) throws StoreAccessException { try { return authoritativeTier.remove(key, value); } finally { cachingTier().invalidate(key); } } @Override public ValueHolder<V> replace(K key, V value) throws StoreAccessException { try { return authoritativeTier.replace(key, value); } finally { cachingTier().invalidate(key); } } @Override public ReplaceStatus replace(K key, V oldValue, V newValue) throws StoreAccessException { try { return authoritativeTier.replace(key, oldValue, newValue); } finally { cachingTier().invalidate(key); } } @Override public void clear() throws StoreAccessException { swapCachingTiers(); try { authoritativeTier.clear(); } finally { try { realCachingTier.clear(); } finally { swapBackCachingTiers(); } } } private void invalidateAllInternal() throws StoreAccessException { swapCachingTiers(); try { realCachingTier.invalidateAll(); } finally { swapBackCachingTiers(); } } private void swapCachingTiers() { boolean interrupted = false; while(!cachingTierRef.compareAndSet(realCachingTier, noopCachingTier)) { synchronized (noopCachingTier) { if(cachingTierRef.get() == noopCachingTier) { try { noopCachingTier.wait(); } catch (InterruptedException e) { interrupted = true; } } } } if(interrupted) { Thread.currentThread().interrupt(); } } private void swapBackCachingTiers() { if(!cachingTierRef.compareAndSet(noopCachingTier, realCachingTier)) { throw new AssertionError("Something bad happened"); } synchronized (noopCachingTier) { noopCachingTier.notify(); } } @Override public StoreEventSource<K, V> getStoreEventSource() { return authoritativeTier.getStoreEventSource(); } @Override public Iterator<Cache.Entry<K, ValueHolder<V>>> iterator() { return authoritativeTier.iterator(); } @Override public ValueHolder<V> compute(final K key, final BiFunction<? super K, ? super V, ? extends V> mappingFunction) throws StoreAccessException { try { return authoritativeTier.compute(key, mappingFunction); } finally { cachingTier().invalidate(key); } } @Override public ValueHolder<V> compute(final K key, final BiFunction<? super K, ? super V, ? extends V> mappingFunction, final NullaryFunction<Boolean> replaceEqual) throws StoreAccessException { try { return authoritativeTier.compute(key, mappingFunction, replaceEqual); } finally { cachingTier().invalidate(key); } } public ValueHolder<V> computeIfAbsent(final K key, final Function<? super K, ? extends V> mappingFunction) throws StoreAccessException { try { return cachingTier().getOrComputeIfAbsent(key, new Function<K, ValueHolder<V>>() { @Override public ValueHolder<V> apply(K k) { try { return authoritativeTier.computeIfAbsentAndFault(k, mappingFunction); } catch (StoreAccessException cae) { throw new ComputationException(cae); } } }); } catch (ComputationException ce) { throw ce.getStoreAccessException(); } } @Override public Map<K, ValueHolder<V>> bulkCompute(Set<? extends K> keys, Function<Iterable<? extends Map.Entry<? extends K, ? extends V>>, Iterable<? extends Map.Entry<? extends K, ? extends V>>> remappingFunction) throws StoreAccessException { try { return authoritativeTier.bulkCompute(keys, remappingFunction); } finally { for (K key : keys) { cachingTier().invalidate(key); } } } @Override public Map<K, ValueHolder<V>> bulkCompute(Set<? extends K> keys, Function<Iterable<? extends Map.Entry<? extends K, ? extends V>>, Iterable<? extends Map.Entry<? extends K, ? extends V>>> remappingFunction, NullaryFunction<Boolean> replaceEqual) throws StoreAccessException { try { return authoritativeTier.bulkCompute(keys, remappingFunction, replaceEqual); } finally { for (K key : keys) { cachingTier().invalidate(key); } } } @Override public Map<K, ValueHolder<V>> bulkComputeIfAbsent(Set<? extends K> keys, Function<Iterable<? extends K>, Iterable<? extends Map.Entry<? extends K, ? extends V>>> mappingFunction) throws StoreAccessException { try { return authoritativeTier.bulkComputeIfAbsent(keys, mappingFunction); } finally { for (K key : keys) { cachingTier().invalidate(key); } } } @Override public List<CacheConfigurationChangeListener> getConfigurationChangeListeners() { List<CacheConfigurationChangeListener> configurationChangeListenerList = new ArrayList<CacheConfigurationChangeListener>(); configurationChangeListenerList.addAll(realCachingTier.getConfigurationChangeListeners()); configurationChangeListenerList.addAll(authoritativeTier.getConfigurationChangeListeners()); return configurationChangeListenerList; } private CachingTier<K, V> cachingTier() { return cachingTierRef.get(); } @ServiceDependencies({CachingTier.Provider.class, AuthoritativeTier.Provider.class}) public static class Provider implements Store.Provider { private volatile ServiceProvider<Service> serviceProvider; private final ConcurrentMap<Store<?, ?>, Map.Entry<CachingTier.Provider, AuthoritativeTier.Provider>> providersMap = new ConcurrentWeakIdentityHashMap<Store<?, ?>, Map.Entry<CachingTier.Provider, AuthoritativeTier.Provider>>(); @Override public int rank(final Set<ResourceType<?>> resourceTypes, final Collection<ServiceConfiguration<?>> serviceConfigs) { if (resourceTypes.size() == 1) { return 0; } ResourceType<?> authorityResource = getAuthorityResource(resourceTypes); int authorityRank = 0; Collection<AuthoritativeTier.Provider> authorityProviders = serviceProvider.getServicesOfType(AuthoritativeTier.Provider.class); for (AuthoritativeTier.Provider authorityProvider : authorityProviders) { int newRank = authorityProvider.rankAuthority(authorityResource, serviceConfigs); if (newRank > authorityRank) { authorityRank = newRank; } } if (authorityRank == 0) { return 0; } Set<ResourceType<?>> cachingResources = new HashSet<ResourceType<?>>(); cachingResources.addAll(resourceTypes); cachingResources.remove(authorityResource); int cachingTierRank = 0; Collection<CachingTier.Provider> cachingTierProviders = serviceProvider.getServicesOfType(CachingTier.Provider.class); for (CachingTier.Provider cachingTierProvider : cachingTierProviders) { int newRank = cachingTierProvider.rankCachingTier(cachingResources, serviceConfigs); if (newRank > cachingTierRank) { cachingTierRank = newRank; } } if (cachingTierRank == 0) { return 0; } return authorityRank + cachingTierRank; } private ResourceType<?> getAuthorityResource(Set<ResourceType<?>> resourceTypes) { ResourceType<?> authorityResource = null; for (ResourceType<?> resourceType : resourceTypes) { if (authorityResource == null || authorityResource.getTierHeight() > resourceType.getTierHeight()) { authorityResource = resourceType; } } return authorityResource; } @Override public <K, V> Store<K, V> createStore(Configuration<K, V> storeConfig, ServiceConfiguration<?>... serviceConfigs) { final List<ServiceConfiguration<?>> enhancedServiceConfigs = new ArrayList<ServiceConfiguration<?>>(Arrays.asList(serviceConfigs)); final ResourcePools resourcePools = storeConfig.getResourcePools(); if (rank(resourcePools.getResourceTypeSet(), enhancedServiceConfigs) == 0) { throw new IllegalArgumentException("TieredStore.Provider does not support configured resource types " + resourcePools.getResourceTypeSet()); } ResourceType<?> authorityResource = getAuthorityResource(resourcePools.getResourceTypeSet()); AuthoritativeTier.Provider authoritativeTierProvider = getAuthoritativeTierProvider(authorityResource, enhancedServiceConfigs); Set<ResourceType<?>> cachingResources = new HashSet<ResourceType<?>>(); cachingResources.addAll(resourcePools.getResourceTypeSet()); cachingResources.remove(authorityResource); CachingTier.Provider cachingTierProvider = getCachingTierProvider(cachingResources, enhancedServiceConfigs); final ServiceConfiguration<?>[] configurations = enhancedServiceConfigs.toArray(new ServiceConfiguration<?>[enhancedServiceConfigs.size()]); CachingTier<K, V> cachingTier = cachingTierProvider.createCachingTier(storeConfig, configurations); AuthoritativeTier<K, V> authoritativeTier = authoritativeTierProvider.createAuthoritativeTier(storeConfig, configurations); TieredStore<K, V> store = new TieredStore<K, V>(cachingTier, authoritativeTier); registerStore(store, cachingTierProvider, authoritativeTierProvider); return store; } private CachingTier.Provider getCachingTierProvider(Set<ResourceType<?>> cachingResources, List<ServiceConfiguration<?>> enhancedServiceConfigs) { CachingTier.Provider cachingTierProvider = null; Collection<CachingTier.Provider> cachingTierProviders = serviceProvider.getServicesOfType(CachingTier.Provider.class); for (CachingTier.Provider provider : cachingTierProviders) { if (provider.rankCachingTier(cachingResources, enhancedServiceConfigs) != 0) { cachingTierProvider = provider; break; } } if (cachingTierProvider == null) { throw new AssertionError("No CachingTier.Provider found although ranking found one for " + cachingResources); } return cachingTierProvider; } private AuthoritativeTier.Provider getAuthoritativeTierProvider(ResourceType<?> authorityResource, List<ServiceConfiguration<?>> enhancedServiceConfigs) { AuthoritativeTier.Provider authoritativeTierProvider = null; Collection<AuthoritativeTier.Provider> authorityProviders = serviceProvider.getServicesOfType(AuthoritativeTier.Provider.class); for (AuthoritativeTier.Provider provider : authorityProviders) { if (provider.rankAuthority(authorityResource, enhancedServiceConfigs) != 0) { authoritativeTierProvider = provider; break; } } if (authoritativeTierProvider == null) { throw new AssertionError("No AuthoritativeTier.Provider found although ranking found one for " + authorityResource); } return authoritativeTierProvider; } <K, V> void registerStore(final TieredStore<K, V> store, final CachingTier.Provider cachingTierProvider, final AuthoritativeTier.Provider authoritativeTierProvider) { if(providersMap.putIfAbsent(store, new AbstractMap.SimpleEntry<CachingTier.Provider, AuthoritativeTier.Provider>(cachingTierProvider, authoritativeTierProvider)) != null) { throw new IllegalStateException("Instance of the Store already registered!"); } } @Override public void releaseStore(Store<?, ?> resource) { Map.Entry<CachingTier.Provider, AuthoritativeTier.Provider> entry = providersMap.get(resource); if (entry == null) { throw new IllegalArgumentException("Given store is not managed by this provider : " + resource); } TieredStore tieredStore = (TieredStore) resource; entry.getKey().releaseCachingTier(tieredStore.realCachingTier); entry.getValue().releaseAuthoritativeTier(tieredStore.authoritativeTier); } @Override public void initStore(Store<?, ?> resource) { Map.Entry<CachingTier.Provider, AuthoritativeTier.Provider> entry = providersMap.get(resource); if (entry == null) { throw new IllegalArgumentException("Given store is not managed by this provider : " + resource); } TieredStore tieredStore = (TieredStore) resource; entry.getKey().initCachingTier(tieredStore.realCachingTier); entry.getValue().initAuthoritativeTier(tieredStore.authoritativeTier); } @Override public void start(ServiceProvider<Service> serviceProvider) { this.serviceProvider = serviceProvider; } @Override public void stop() { this.serviceProvider = null; providersMap.clear(); } } private static class NoopCachingTier<K, V> implements CachingTier<K, V> { private final AuthoritativeTier<K, V> authoritativeTier; public NoopCachingTier(final AuthoritativeTier<K, V> authoritativeTier) { this.authoritativeTier = authoritativeTier; } @Override public ValueHolder<V> getOrComputeIfAbsent(final K key, final Function<K, ValueHolder<V>> source) throws StoreAccessException { final ValueHolder<V> apply = source.apply(key); authoritativeTier.flush(key, apply); return apply; } @Override public void invalidate(final K key) throws StoreAccessException { // noop } @Override public void invalidateAll() { // noop } @Override public void clear() throws StoreAccessException { // noop } @Override public void setInvalidationListener(final InvalidationListener<K, V> invalidationListener) { // noop } @Override public void invalidateAllWithHash(long hash) throws StoreAccessException { // noop } @Override public List<CacheConfigurationChangeListener> getConfigurationChangeListeners() { return null; } } }