src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java
changeset 47216 71c04702a3d5
parent 36680 c9fe8f8e7e6f
child 47361 74e1913a98c0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/net/www/http/KeepAliveCache.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.net.www.http;
+
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.net.URL;
+import jdk.internal.misc.InnocuousThread;
+
+/**
+ * A class that implements a cache of idle Http connections for keep-alive
+ *
+ * @author Stephen R. Pietrowicz (NCSA)
+ * @author Dave Brown
+ */
+public class KeepAliveCache
+    extends HashMap<KeepAliveKey, ClientVector>
+    implements Runnable {
+    private static final long serialVersionUID = -2937172892064557949L;
+
+    /* maximum # keep-alive connections to maintain at once
+     * This should be 2 by the HTTP spec, but because we don't support pipe-lining
+     * a larger value is more appropriate. So we now set a default of 5, and the value
+     * refers to the number of idle connections per destination (in the cache) only.
+     * It can be reset by setting system property "http.maxConnections".
+     */
+    static final int MAX_CONNECTIONS = 5;
+    static int result = -1;
+    static int getMaxConnections() {
+        if (result == -1) {
+            result = java.security.AccessController.doPrivileged(
+                new sun.security.action.GetIntegerAction("http.maxConnections",
+                                                         MAX_CONNECTIONS))
+                .intValue();
+            if (result <= 0)
+                result = MAX_CONNECTIONS;
+        }
+            return result;
+    }
+
+    static final int LIFETIME = 5000;
+
+    private Thread keepAliveTimer = null;
+
+    /**
+     * Constructor
+     */
+    public KeepAliveCache() {}
+
+    /**
+     * Register this URL and HttpClient (that supports keep-alive) with the cache
+     * @param url  The URL contains info about the host and port
+     * @param http The HttpClient to be cached
+     */
+    public synchronized void put(final URL url, Object obj, HttpClient http) {
+        boolean startThread = (keepAliveTimer == null);
+        if (!startThread) {
+            if (!keepAliveTimer.isAlive()) {
+                startThread = true;
+            }
+        }
+        if (startThread) {
+            clear();
+            /* Unfortunately, we can't always believe the keep-alive timeout we got
+             * back from the server.  If I'm connected through a Netscape proxy
+             * to a server that sent me a keep-alive
+             * time of 15 sec, the proxy unilaterally terminates my connection
+             * The robustness to get around this is in HttpClient.parseHTTP()
+             */
+            final KeepAliveCache cache = this;
+            java.security.AccessController.doPrivileged(
+                new java.security.PrivilegedAction<>() {
+                public Void run() {
+                    keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache);
+                    keepAliveTimer.setDaemon(true);
+                    keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);
+                    keepAliveTimer.start();
+                    return null;
+                }
+            });
+        }
+
+        KeepAliveKey key = new KeepAliveKey(url, obj);
+        ClientVector v = super.get(key);
+
+        if (v == null) {
+            int keepAliveTimeout = http.getKeepAliveTimeout();
+            v = new ClientVector(keepAliveTimeout > 0?
+                                 keepAliveTimeout*1000 : LIFETIME);
+            v.put(http);
+            super.put(key, v);
+        } else {
+            v.put(http);
+        }
+    }
+
+    /* remove an obsolete HttpClient from its VectorCache */
+    public synchronized void remove (HttpClient h, Object obj) {
+        KeepAliveKey key = new KeepAliveKey(h.url, obj);
+        ClientVector v = super.get(key);
+        if (v != null) {
+            v.remove(h);
+            if (v.empty()) {
+                removeVector(key);
+            }
+        }
+    }
+
+    /* called by a clientVector thread when all its connections have timed out
+     * and that vector of connections should be removed.
+     */
+    synchronized void removeVector(KeepAliveKey k) {
+        super.remove(k);
+    }
+
+    /**
+     * Check to see if this URL has a cached HttpClient
+     */
+    public synchronized HttpClient get(URL url, Object obj) {
+
+        KeepAliveKey key = new KeepAliveKey(url, obj);
+        ClientVector v = super.get(key);
+        if (v == null) { // nothing in cache yet
+            return null;
+        }
+        return v.get();
+    }
+
+    /* Sleeps for an alloted timeout, then checks for timed out connections.
+     * Errs on the side of caution (leave connections idle for a relatively
+     * short time).
+     */
+    @Override
+    public void run() {
+        do {
+            try {
+                Thread.sleep(LIFETIME);
+            } catch (InterruptedException e) {}
+            synchronized (this) {
+                /* Remove all unused HttpClients.  Starting from the
+                 * bottom of the stack (the least-recently used first).
+                 * REMIND: It'd be nice to not remove *all* connections
+                 * that aren't presently in use.  One could have been added
+                 * a second ago that's still perfectly valid, and we're
+                 * needlessly axing it.  But it's not clear how to do this
+                 * cleanly, and doing it right may be more trouble than it's
+                 * worth.
+                 */
+
+                long currentTime = System.currentTimeMillis();
+
+                ArrayList<KeepAliveKey> keysToRemove
+                    = new ArrayList<>();
+
+                for (KeepAliveKey key : keySet()) {
+                    ClientVector v = get(key);
+                    synchronized (v) {
+                        int i;
+
+                        for (i = 0; i < v.size(); i++) {
+                            KeepAliveEntry e = v.elementAt(i);
+                            if ((currentTime - e.idleStartTime) > v.nap) {
+                                HttpClient h = e.hc;
+                                h.closeServer();
+                            } else {
+                                break;
+                            }
+                        }
+                        v.subList(0, i).clear();
+
+                        if (v.size() == 0) {
+                            keysToRemove.add(key);
+                        }
+                    }
+                }
+
+                for (KeepAliveKey key : keysToRemove) {
+                    removeVector(key);
+                }
+            }
+        } while (size() > 0);
+
+        return;
+    }
+
+    /*
+     * Do not serialize this class!
+     */
+    private void writeObject(java.io.ObjectOutputStream stream)
+    throws IOException {
+        throw new NotSerializableException();
+    }
+
+    private void readObject(java.io.ObjectInputStream stream)
+    throws IOException, ClassNotFoundException {
+        throw new NotSerializableException();
+    }
+}
+
+/* FILO order for recycling HttpClients, should run in a thread
+ * to time them out.  If > maxConns are in use, block.
+ */
+
+
+class ClientVector extends java.util.Stack<KeepAliveEntry> {
+    private static final long serialVersionUID = -8680532108106489459L;
+
+    // sleep time in milliseconds, before cache clear
+    int nap;
+
+
+
+    ClientVector (int nap) {
+        this.nap = nap;
+    }
+
+    synchronized HttpClient get() {
+        if (empty()) {
+            return null;
+        } else {
+            // Loop until we find a connection that has not timed out
+            HttpClient hc = null;
+            long currentTime = System.currentTimeMillis();
+            do {
+                KeepAliveEntry e = pop();
+                if ((currentTime - e.idleStartTime) > nap) {
+                    e.hc.closeServer();
+                } else {
+                    hc = e.hc;
+                }
+            } while ((hc== null) && (!empty()));
+            return hc;
+        }
+    }
+
+    /* return a still valid, unused HttpClient */
+    synchronized void put(HttpClient h) {
+        if (size() >= KeepAliveCache.getMaxConnections()) {
+            h.closeServer(); // otherwise the connection remains in limbo
+        } else {
+            push(new KeepAliveEntry(h, System.currentTimeMillis()));
+        }
+    }
+
+    /*
+     * Do not serialize this class!
+     */
+    private void writeObject(java.io.ObjectOutputStream stream)
+    throws IOException {
+        throw new NotSerializableException();
+    }
+
+    private void readObject(java.io.ObjectInputStream stream)
+    throws IOException, ClassNotFoundException {
+        throw new NotSerializableException();
+    }
+}
+
+
+class KeepAliveKey {
+    private String      protocol = null;
+    private String      host = null;
+    private int         port = 0;
+    private Object      obj = null; // additional key, such as socketfactory
+
+    /**
+     * Constructor
+     *
+     * @param url the URL containing the protocol, host and port information
+     */
+    public KeepAliveKey(URL url, Object obj) {
+        this.protocol = url.getProtocol();
+        this.host = url.getHost();
+        this.port = url.getPort();
+        this.obj = obj;
+    }
+
+    /**
+     * Determine whether or not two objects of this type are equal
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if ((obj instanceof KeepAliveKey) == false)
+            return false;
+        KeepAliveKey kae = (KeepAliveKey)obj;
+        return host.equals(kae.host)
+            && (port == kae.port)
+            && protocol.equals(kae.protocol)
+            && this.obj == kae.obj;
+    }
+
+    /**
+     * The hashCode() for this object is the string hashCode() of
+     * concatenation of the protocol, host name and port.
+     */
+    @Override
+    public int hashCode() {
+        String str = protocol+host+port;
+        return this.obj == null? str.hashCode() :
+            str.hashCode() + this.obj.hashCode();
+    }
+}
+
+class KeepAliveEntry {
+    HttpClient hc;
+    long idleStartTime;
+
+    KeepAliveEntry(HttpClient hc, long idleStartTime) {
+        this.hc = hc;
+        this.idleStartTime = idleStartTime;
+    }
+}