/* * 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 com.facebook.presto.execution.scheduler; import com.facebook.presto.spi.HostAddress; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import io.airlift.log.Logger; import io.airlift.units.Duration; import java.util.concurrent.ExecutorService; import static com.facebook.presto.execution.scheduler.NetworkLocation.ROOT_LOCATION; import static com.google.common.cache.CacheLoader.asyncReloading; import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static java.util.Objects.requireNonNull; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; public class NetworkLocationCache { private static final Duration NEGATIVE_CACHE_DURATION = new Duration(10, MINUTES); private static final Logger log = Logger.get(NetworkLocationCache.class); private final ExecutorService executor = newCachedThreadPool(daemonThreadsNamed("network-location-%s")); private final NetworkTopology networkTopology; private final LoadingCache<HostAddress, NetworkLocation> cache; private final Cache<HostAddress, Boolean> negativeCache; public NetworkLocationCache(NetworkTopology networkTopology) { this.networkTopology = requireNonNull(networkTopology, "networkTopology is null"); this.cache = CacheBuilder.newBuilder() .expireAfterWrite(1, DAYS) .refreshAfterWrite(12, HOURS) .build(asyncReloading(new CacheLoader<HostAddress, NetworkLocation>() { @Override public NetworkLocation load(HostAddress host) throws Exception { return locate(host); } }, executor)); this.negativeCache = CacheBuilder.newBuilder() .expireAfterWrite(NEGATIVE_CACHE_DURATION.toMillis(), MILLISECONDS) .build(); } public void stop() { executor.shutdownNow(); } public NetworkLocation get(HostAddress host) { NetworkLocation location = cache.getIfPresent(host); if ((location == null) && (negativeCache.getIfPresent(host) == null)) { // Store a value in the cache, so that refresh() is done asynchronously cache.put(host, ROOT_LOCATION); cache.refresh(host); } // Return the root location for anything we couldn't locate return location == null ? ROOT_LOCATION : location; } private NetworkLocation locate(HostAddress host) { try { return networkTopology.locate(host); } catch (RuntimeException e) { negativeCache.put(host, true); log.warn(e, "Unable to determine location of %s. Will attempt again in %s", host, NEGATIVE_CACHE_DURATION); // no one will see the exception thrown here throw e; } } }