/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.hadoop.hdfs; import com.google.common.annotations.VisibleForTesting; import java.util.HashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hdfs.DFSClient; import org.apache.hadoop.hdfs.DFSClient.Conf; import org.apache.hadoop.hdfs.client.ShortCircuitCache; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; /** * ClientContext contains context information for a client. * * This allows us to share caches such as the socket cache across * DFSClient instances. */ @InterfaceAudience.Private public class ClientContext { private static final Log LOG = LogFactory.getLog(ClientContext.class); /** * Global map of context names to caches contexts. */ private final static HashMap<String, ClientContext> CACHES = new HashMap<String, ClientContext>(); /** * Name of context. */ private final String name; /** * String representation of the configuration. */ private final String confString; /** * Caches short-circuit file descriptors, mmap regions. */ private final ShortCircuitCache shortCircuitCache; /** * Caches TCP and UNIX domain sockets for reuse. */ private final PeerCache peerCache; /** * Stores information about socket paths. */ private final DomainSocketFactory domainSocketFactory; /** * True if we should use the legacy BlockReaderLocal. */ private final boolean useLegacyBlockReaderLocal; /** * True if the legacy BlockReaderLocal is disabled. * * The legacy block reader local gets disabled completely whenever there is an * error or miscommunication. The new block reader local code handles this * case more gracefully inside DomainSocketFactory. */ private volatile boolean disableLegacyBlockReaderLocal = false; /** * Whether or not we complained about a DFSClient fetching a CacheContext that * didn't match its config values yet. */ private boolean printedConfWarning = false; private ClientContext(String name, Conf conf) { this.name = name; this.confString = confAsString(conf); this.shortCircuitCache = new ShortCircuitCache( conf.shortCircuitStreamsCacheSize, conf.shortCircuitStreamsCacheExpiryMs, conf.shortCircuitMmapCacheSize, conf.shortCircuitMmapCacheExpiryMs, conf.shortCircuitMmapCacheRetryTimeout, conf.shortCircuitCacheStaleThresholdMs, conf.shortCircuitSharedMemoryWatcherInterruptCheckMs); this.peerCache = new PeerCache(conf.socketCacheCapacity, conf.socketCacheExpiry); this.useLegacyBlockReaderLocal = conf.useLegacyBlockReaderLocal; this.domainSocketFactory = new DomainSocketFactory(conf); } public static String confAsString(Conf conf) { StringBuilder builder = new StringBuilder(); builder.append("shortCircuitStreamsCacheSize = "). append(conf.shortCircuitStreamsCacheSize). append(", shortCircuitStreamsCacheExpiryMs = "). append(conf.shortCircuitStreamsCacheExpiryMs). append(", shortCircuitMmapCacheSize = "). append(conf.shortCircuitMmapCacheSize). append(", shortCircuitMmapCacheExpiryMs = "). append(conf.shortCircuitMmapCacheExpiryMs). append(", shortCircuitMmapCacheRetryTimeout = "). append(conf.shortCircuitMmapCacheRetryTimeout). append(", shortCircuitCacheStaleThresholdMs = "). append(conf.shortCircuitCacheStaleThresholdMs). append(", socketCacheCapacity = "). append(conf.socketCacheCapacity). append(", socketCacheExpiry = "). append(conf.socketCacheExpiry). append(", shortCircuitLocalReads = "). append(conf.shortCircuitLocalReads). append(", useLegacyBlockReaderLocal = "). append(conf.useLegacyBlockReaderLocal). append(", domainSocketDataTraffic = "). append(conf.domainSocketDataTraffic). append(", shortCircuitSharedMemoryWatcherInterruptCheckMs = "). append(conf.shortCircuitSharedMemoryWatcherInterruptCheckMs); return builder.toString(); } public static ClientContext get(String name, Conf conf) { ClientContext context; synchronized(ClientContext.class) { context = CACHES.get(name); if (context == null) { context = new ClientContext(name, conf); CACHES.put(name, context); } else { context.printConfWarningIfNeeded(conf); } } return context; } /** * Get a client context, from a Configuration object. * * This method is less efficient than the version which takes a DFSClient#Conf * object, and should be mostly used by tests. */ @VisibleForTesting public static ClientContext getFromConf(Configuration conf) { return get(conf.get(DFSConfigKeys.DFS_CLIENT_CONTEXT, DFSConfigKeys.DFS_CLIENT_CONTEXT_DEFAULT), new DFSClient.Conf(conf)); } private void printConfWarningIfNeeded(Conf conf) { String existing = this.getConfString(); String requested = confAsString(conf); if (!existing.equals(requested)) { if (!printedConfWarning) { printedConfWarning = true; LOG.warn("Existing client context '" + name + "' does not match " + "requested configuration. Existing: " + existing + ", Requested: " + requested); } } } public String getConfString() { return confString; } public ShortCircuitCache getShortCircuitCache() { return shortCircuitCache; } public PeerCache getPeerCache() { return peerCache; } public boolean getUseLegacyBlockReaderLocal() { return useLegacyBlockReaderLocal; } public boolean getDisableLegacyBlockReaderLocal() { return disableLegacyBlockReaderLocal; } public void setDisableLegacyBlockReaderLocal() { disableLegacyBlockReaderLocal = true; } public DomainSocketFactory getDomainSocketFactory() { return domainSocketFactory; } }