/** * Copyright 2011 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 net.sf.katta.client; import java.io.EOFException; import java.io.IOException; import java.lang.reflect.Proxy; import java.net.ConnectException; import java.net.InetSocketAddress; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.ipc.RPC; import org.apache.hadoop.ipc.VersionedProtocol; import org.apache.log4j.Logger; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; public class NodeProxyManager implements INodeProxyManager { private final static Logger LOG = Logger.getLogger(NodeProxyManager.class); private final Class<? extends VersionedProtocol> _serverClass; private final Configuration _hadoopConf; private final Map<String, VersionedProtocol> _node2ProxyMap = new ConcurrentHashMap<String, VersionedProtocol>(); private final INodeSelectionPolicy _selectionPolicy; private int _successiveProxyFailuresBeforeReestablishing = 3; private final Multiset<String> _failedNodeInteractions = HashMultiset.create(); public NodeProxyManager(Class<? extends VersionedProtocol> serverClass, Configuration hadoopConf, INodeSelectionPolicy selectionPolicy) { _serverClass = serverClass; _hadoopConf = hadoopConf; _selectionPolicy = selectionPolicy; } /** * @return how many successive proxy invocation errors must happen before the * proxy is re-established. */ public int getSuccessiveProxyFailuresBeforeReestablishing() { return _successiveProxyFailuresBeforeReestablishing; } public void setSuccessiveProxyFailuresBeforeReestablishing(int successiveProxyFailuresBeforeReestablishing) { _successiveProxyFailuresBeforeReestablishing = successiveProxyFailuresBeforeReestablishing; } public VersionedProtocol createNodeProxy(final String nodeName) throws IOException { LOG.debug("creating proxy for node: " + nodeName); String[] hostName_port = nodeName.split(":"); if (hostName_port.length != 2) { throw new RuntimeException("invalid node name format '" + nodeName + "' (It should be a host name with a port number devided by a ':')"); } final String hostName = hostName_port[0]; final String port = hostName_port[1]; final InetSocketAddress inetSocketAddress = new InetSocketAddress(hostName, Integer.parseInt(port)); VersionedProtocol proxy = RPC.getProxy(_serverClass, 0L, inetSocketAddress, _hadoopConf); LOG.debug(String.format("Created a proxy %s for %s:%s %s", Proxy.getInvocationHandler(proxy), hostName, port, inetSocketAddress)); return proxy; } @Override public VersionedProtocol getProxy(String nodeName, boolean establishIfNoExists) { VersionedProtocol versionedProtocol = _node2ProxyMap.get(nodeName); if (versionedProtocol == null && establishIfNoExists) { synchronized (nodeName.intern()) { if (!_node2ProxyMap.containsKey(nodeName)) { try { versionedProtocol = createNodeProxy(nodeName); _node2ProxyMap.put(nodeName, versionedProtocol); } catch (Exception e) { LOG.warn("Could not create proxy for node '" + nodeName + "' - " + e.getClass().getSimpleName() + ": " + e.getMessage()); } } } } return versionedProtocol; } @Override public Map<String, List<String>> createNode2ShardsMap(Collection<String> shards) throws ShardAccessException { return _selectionPolicy.createNode2ShardsMap(shards); } @SuppressWarnings("unchecked") @Override public void reportNodeCommunicationFailure(String nodeName, Throwable t) { // TODO jz: not sure if there are cases a proxy is getting invalid and // re-establishing it would fix the communication. If so, we should check // the for the exception which occurs in such cases and re-establish the // proxy. _failedNodeInteractions.add(nodeName); int failureCount = _failedNodeInteractions.count(nodeName); if (failureCount >= _successiveProxyFailuresBeforeReestablishing || exceptionContains(t, ConnectException.class, EOFException.class)) { dropNodeProxy(nodeName, failureCount); } } private boolean exceptionContains(Throwable t, Class<? extends Throwable>... exceptionClasses) { while (t != null) { for (Class<? extends Throwable> exceptionClass : exceptionClasses) { if (t.getClass().equals(exceptionClass)) { return true; } } t = t.getCause(); } return false; } private void dropNodeProxy(String nodeName, int failureCount) { synchronized (nodeName.intern()) { if (_node2ProxyMap.containsKey(nodeName)) { LOG.warn("removing proxy for node '" + nodeName + "' after " + failureCount + " proxy-invocation errors"); _failedNodeInteractions.remove(nodeName, Integer.MAX_VALUE); _selectionPolicy.removeNode(nodeName); VersionedProtocol proxy = _node2ProxyMap.remove(nodeName); RPC.stopProxy(proxy); } } } @Override public void reportNodeCommunicationSuccess(String node) { _failedNodeInteractions.remove(node, Integer.MAX_VALUE); } @Override public void shutdown() { Collection<VersionedProtocol> proxies = _node2ProxyMap.values(); for (VersionedProtocol search : proxies) { RPC.stopProxy(search); } } }