/** * 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.oncrpc; import java.net.InetAddress; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import com.google.common.annotations.VisibleForTesting; /** * This class is used for handling the duplicate <em>non-idempotenty</em> Rpc * calls. A non-idempotent request is processed as follows: * <ul> * <li>If the request is being processed for the first time, its state is * in-progress in cache.</li> * <li>If the request is retransimitted and is in-progress state, it is ignored. * </li> * <li>If the request is retransimitted and is completed, the previous response * from the cache is sent back to the client.</li> * </ul> * <br> * A request is identified by the client ID (address of the client) and * transaction ID (xid) from the Rpc call. * */ public class RpcCallCache { public static class CacheEntry { private RpcResponse response; // null if no response has been sent public CacheEntry() { response = null; } public boolean isInProgress() { return response == null; } public boolean isCompleted() { return response != null; } public RpcResponse getResponse() { return response; } public void setResponse(RpcResponse response) { this.response = response; } } /** * Call that is used to track a client in the {@link RpcCallCache} */ public static class ClientRequest { protected final InetAddress clientId; protected final int xid; public InetAddress getClientId() { return clientId; } public ClientRequest(InetAddress clientId, int xid) { this.clientId = clientId; this.xid = xid; } @Override public int hashCode() { return xid + clientId.hashCode() * 31; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || !(obj instanceof ClientRequest)) { return false; } ClientRequest other = (ClientRequest) obj; return clientId.equals(other.clientId) && (xid == other.xid); } } private final String program; private final Map<ClientRequest, CacheEntry> map; public RpcCallCache(final String program, final int maxEntries) { if (maxEntries <= 0) { throw new IllegalArgumentException("Cache size is " + maxEntries + ". Should be > 0"); } this.program = program; map = new LinkedHashMap<ClientRequest, CacheEntry>() { private static final long serialVersionUID = 1L; @Override protected boolean removeEldestEntry( java.util.Map.Entry<ClientRequest, CacheEntry> eldest) { return RpcCallCache.this.size() > maxEntries; } }; } /** Return the program name */ public String getProgram() { return program; } /** Mark a request as completed and add corresponding response to the cache */ public void callCompleted(InetAddress clientId, int xid, RpcResponse response) { ClientRequest req = new ClientRequest(clientId, xid); CacheEntry e; synchronized(map) { e = map.get(req); } e.response = response; } /** * Check the cache for an entry. If it does not exist, add the request * as in progress. */ public CacheEntry checkOrAddToCache(InetAddress clientId, int xid) { ClientRequest req = new ClientRequest(clientId, xid); CacheEntry e; synchronized(map) { e = map.get(req); if (e == null) { // Add an inprogress cache entry map.put(req, new CacheEntry()); } } return e; } /** Return number of cached entries */ public int size() { return map.size(); } /** * Iterator to the cache entries * @return iterator */ @VisibleForTesting public Iterator<Entry<ClientRequest, CacheEntry>> iterator() { return map.entrySet().iterator(); } }