Path: blob/master/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java
41161 views
/*1* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.net.www.http;2627import java.io.IOException;28import java.io.NotSerializableException;29import java.io.ObjectInputStream;30import java.io.ObjectOutputStream;31import java.net.URL;32import java.security.AccessController;33import java.security.PrivilegedAction;34import java.util.ArrayDeque;35import java.util.ArrayList;36import java.util.HashMap;37import java.util.List;38import java.util.concurrent.locks.Lock;39import java.util.concurrent.locks.ReentrantLock;4041import jdk.internal.misc.InnocuousThread;42import sun.security.action.GetIntegerAction;4344/**45* A class that implements a cache of idle Http connections for keep-alive46*47* @author Stephen R. Pietrowicz (NCSA)48* @author Dave Brown49*/50public class KeepAliveCache51extends HashMap<KeepAliveKey, ClientVector>52implements Runnable {53@java.io.Serial54private static final long serialVersionUID = -2937172892064557949L;5556/* maximum # keep-alive connections to maintain at once57* This should be 2 by the HTTP spec, but because we don't support pipe-lining58* a larger value is more appropriate. So we now set a default of 5, and the value59* refers to the number of idle connections per destination (in the cache) only.60* It can be reset by setting system property "http.maxConnections".61*/62static final int MAX_CONNECTIONS = 5;63static int result = -1;64@SuppressWarnings("removal")65static int getMaxConnections() {66if (result == -1) {67result = AccessController.doPrivileged(68new GetIntegerAction("http.maxConnections", MAX_CONNECTIONS))69.intValue();70if (result <= 0) {71result = MAX_CONNECTIONS;72}73}74return result;75}7677static final int LIFETIME = 5000;7879// This class is never serialized (see writeObject/readObject).80private final ReentrantLock cacheLock = new ReentrantLock();81private Thread keepAliveTimer = null;8283/**84* Constructor85*/86public KeepAliveCache() {}8788/**89* Register this URL and HttpClient (that supports keep-alive) with the cache90* @param url The URL contains info about the host and port91* @param http The HttpClient to be cached92*/93@SuppressWarnings("removal")94public void put(final URL url, Object obj, HttpClient http) {95cacheLock.lock();96try {97boolean startThread = (keepAliveTimer == null);98if (!startThread) {99if (!keepAliveTimer.isAlive()) {100startThread = true;101}102}103if (startThread) {104clear();105/* Unfortunately, we can't always believe the keep-alive timeout we got106* back from the server. If I'm connected through a Netscape proxy107* to a server that sent me a keep-alive108* time of 15 sec, the proxy unilaterally terminates my connection109* The robustness to get around this is in HttpClient.parseHTTP()110*/111final KeepAliveCache cache = this;112AccessController.doPrivileged(new PrivilegedAction<>() {113public Void run() {114keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache);115keepAliveTimer.setDaemon(true);116keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);117keepAliveTimer.start();118return null;119}120});121}122123KeepAliveKey key = new KeepAliveKey(url, obj);124ClientVector v = super.get(key);125126if (v == null) {127int keepAliveTimeout = http.getKeepAliveTimeout();128v = new ClientVector(keepAliveTimeout > 0 ?129keepAliveTimeout * 1000 : LIFETIME);130v.put(http);131super.put(key, v);132} else {133v.put(http);134}135} finally {136cacheLock.unlock();137}138}139140/* remove an obsolete HttpClient from its VectorCache */141public void remove(HttpClient h, Object obj) {142cacheLock.lock();143try {144KeepAliveKey key = new KeepAliveKey(h.url, obj);145ClientVector v = super.get(key);146if (v != null) {147v.remove(h);148if (v.isEmpty()) {149removeVector(key);150}151}152} finally {153cacheLock.unlock();154}155}156157/* called by a clientVector thread when all its connections have timed out158* and that vector of connections should be removed.159*/160private void removeVector(KeepAliveKey k) {161assert cacheLock.isHeldByCurrentThread();162super.remove(k);163}164165/**166* Check to see if this URL has a cached HttpClient167*/168public HttpClient get(URL url, Object obj) {169cacheLock.lock();170try {171KeepAliveKey key = new KeepAliveKey(url, obj);172ClientVector v = super.get(key);173if (v == null) { // nothing in cache yet174return null;175}176return v.get();177} finally {178cacheLock.unlock();179}180}181182/* Sleeps for an alloted timeout, then checks for timed out connections.183* Errs on the side of caution (leave connections idle for a relatively184* short time).185*/186@Override187public void run() {188do {189try {190Thread.sleep(LIFETIME);191} catch (InterruptedException e) {}192193// Remove all outdated HttpClients.194cacheLock.lock();195try {196long currentTime = System.currentTimeMillis();197List<KeepAliveKey> keysToRemove = new ArrayList<>();198199for (KeepAliveKey key : keySet()) {200ClientVector v = get(key);201v.lock();202try {203KeepAliveEntry e = v.peek();204while (e != null) {205if ((currentTime - e.idleStartTime) > v.nap) {206v.poll();207e.hc.closeServer();208} else {209break;210}211e = v.peek();212}213214if (v.isEmpty()) {215keysToRemove.add(key);216}217} finally {218v.unlock();219}220}221222for (KeepAliveKey key : keysToRemove) {223removeVector(key);224}225} finally {226cacheLock.unlock();227}228} while (!isEmpty());229}230231/*232* Do not serialize this class!233*/234@java.io.Serial235private void writeObject(ObjectOutputStream stream) throws IOException {236throw new NotSerializableException();237}238239@java.io.Serial240private void readObject(ObjectInputStream stream)241throws IOException, ClassNotFoundException242{243throw new NotSerializableException();244}245}246247/* FILO order for recycling HttpClients, should run in a thread248* to time them out. If > maxConns are in use, block.249*/250class ClientVector extends ArrayDeque<KeepAliveEntry> {251@java.io.Serial252private static final long serialVersionUID = -8680532108106489459L;253private final ReentrantLock lock = new ReentrantLock();254255// sleep time in milliseconds, before cache clear256int nap;257258ClientVector(int nap) {259this.nap = nap;260}261262HttpClient get() {263lock();264try {265if (isEmpty()) {266return null;267}268269// Loop until we find a connection that has not timed out270HttpClient hc = null;271long currentTime = System.currentTimeMillis();272do {273KeepAliveEntry e = pop();274if ((currentTime - e.idleStartTime) > nap) {275e.hc.closeServer();276} else {277hc = e.hc;278}279} while ((hc == null) && (!isEmpty()));280return hc;281} finally {282unlock();283}284}285286/* return a still valid, unused HttpClient */287void put(HttpClient h) {288lock();289try {290if (size() >= KeepAliveCache.getMaxConnections()) {291h.closeServer(); // otherwise the connection remains in limbo292} else {293push(new KeepAliveEntry(h, System.currentTimeMillis()));294}295} finally {296unlock();297}298}299300/* remove an HttpClient */301boolean remove(HttpClient h) {302lock();303try {304for (KeepAliveEntry curr : this) {305if (curr.hc == h) {306return super.remove(curr);307}308}309return false;310} finally {311unlock();312}313}314315final void lock() {316lock.lock();317}318319final void unlock() {320lock.unlock();321}322323/*324* Do not serialize this class!325*/326@java.io.Serial327private void writeObject(ObjectOutputStream stream) throws IOException {328throw new NotSerializableException();329}330331@java.io.Serial332private void readObject(ObjectInputStream stream)333throws IOException, ClassNotFoundException334{335throw new NotSerializableException();336}337}338339class KeepAliveKey {340private String protocol = null;341private String host = null;342private int port = 0;343private Object obj = null; // additional key, such as socketfactory344345/**346* Constructor347*348* @param url the URL containing the protocol, host and port information349*/350public KeepAliveKey(URL url, Object obj) {351this.protocol = url.getProtocol();352this.host = url.getHost();353this.port = url.getPort();354this.obj = obj;355}356357/**358* Determine whether or not two objects of this type are equal359*/360@Override361public boolean equals(Object obj) {362if ((obj instanceof KeepAliveKey) == false)363return false;364KeepAliveKey kae = (KeepAliveKey)obj;365return host.equals(kae.host)366&& (port == kae.port)367&& protocol.equals(kae.protocol)368&& this.obj == kae.obj;369}370371/**372* The hashCode() for this object is the string hashCode() of373* concatenation of the protocol, host name and port.374*/375@Override376public int hashCode() {377String str = protocol+host+port;378return this.obj == null? str.hashCode() :379str.hashCode() + this.obj.hashCode();380}381}382383class KeepAliveEntry {384HttpClient hc;385long idleStartTime;386387KeepAliveEntry(HttpClient hc, long idleStartTime) {388this.hc = hc;389this.idleStartTime = idleStartTime;390}391}392393394