1 /*
2 * Copyright 2012-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License").
5 * You may not use this file except in compliance with the License.
6 * A copy of the License is located at
7 *
8 * http://aws.amazon.com/apache2.0
9 *
10 * or in the "license" file accompanying this file. This file is distributed
11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 * express or implied. See the License for the specific language governing
13 * permissions and limitations under the License.
14 */
15 package com.amazonaws.http;
16
17 import com.amazonaws.annotation.SdkInternalApi;
18 import com.amazonaws.annotation.SdkTestInternalApi;
19
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
22 import org.apache.http.conn.HttpClientConnectionManager;
23
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.TimeUnit;
29
30 /**
31 * Daemon thread to periodically check connection pools for idle connections.
32 * <p/>
33 * Connections sitting around idle in the HTTP connection pool for too long will
34 * eventually be terminated by the AWS end of the connection, and will go into
35 * CLOSE_WAIT. If this happens, sockets will sit around in CLOSE_WAIT, still
36 * using resources on the client side to manage that socket. Many sockets stuck
37 * in CLOSE_WAIT can prevent the OS from creating new connections.
38 * <p/>
39 * This class closes idle connections before they can move into the CLOSE_WAIT
40 * state.
41 * <p/>
42 * This thread is important because by default, we disable Apache HttpClient's
43 * stale connection checking, so without this thread running in the background,
44 * cleaning up old/inactive HTTP connections, we'd see more IO exceptions when
45 * stale connections (i.e. closed on the AWS side) are left in the connection
46 * pool, and requests grab one of them to begin executing a request.
47 */
48 @SdkInternalApi
49 public final class IdleConnectionReaper extends Thread {
50
51 /**
52 * Shared log for any errors during connection reaping.
53 */
54 private static final Log LOG = LogFactory.getLog(IdleConnectionReaper.class);
55 /**
56 * The period between invocations of the idle connection reaper.
57 */
58 private static final int PERIOD_MILLISECONDS = 1000 * 60;
59
60 /**
61 * Legacy constant used when {@link #registerConnectionManager(HttpClientConnectionManager)} is called. New code paths should
62 * use {@link #registerConnectionManager(HttpClientConnectionManager, long)} and provide the max idle timeout for that
63 * particular connection manager.
64 */
65 @Deprecated
66 private static final int DEFAULT_MAX_IDLE_MILLIS = 1000 * 60;
67
68 private static final Map<HttpClientConnectionManager, Long> connectionManagers = new ConcurrentHashMap<HttpClientConnectionManager, Long>();
69 /**
70 * Singleton instance of the connection reaper.
71 */
72 private static volatile IdleConnectionReaper instance;
73 /**
74 * Set to true when shutting down the reaper; Once set to true, this
75 * flag is never set back to false.
76 */
77 private volatile boolean shuttingDown;
78
79 /**
80 * Private constructor - singleton pattern.
81 */
82 private IdleConnectionReaper() {
83 super("java-sdk-http-connection-reaper");
84 setDaemon(true);
85 }
86
87 /**
88 * Registers the given connection manager with this reaper.
89 *
90 * @return true if the connection manager has been successfully registered; false otherwise.
91 * @deprecated By {@link #registerConnectionManager(HttpClientConnectionManager, long)}.
92 */
93 @Deprecated
94 public static boolean registerConnectionManager(HttpClientConnectionManager connectionManager) {
95 return registerConnectionManager(connectionManager, DEFAULT_MAX_IDLE_MILLIS);
96 }
97
98 /**
99 * Registers the given connection manager with this reaper;
100 *
101 * @param connectionManager Connection manager to register
102 * @param maxIdleInMs Max idle connection timeout in milliseconds for this connection manager.
103 * @return true if the connection manager has been successfully registered; false otherwise.
104 */
105 public static boolean registerConnectionManager(HttpClientConnectionManager connectionManager, long maxIdleInMs) {
106 if (instance == null) {
107 synchronized (IdleConnectionReaper.class) {
108 if (instance == null) {
109 instance = new IdleConnectionReaper();
110 instance.start();
111 }
112 }
113 }
114 return connectionManagers.put(connectionManager, maxIdleInMs) == null;
115 }
116
117 /**
118 * Removes the given connection manager from this reaper,
119 * and shutting down the reaper if there is zero connection manager left.
120 *
121 * @return true if the connection manager has been successfully removed;
122 * false otherwise.
123 */
124 public static boolean removeConnectionManager(HttpClientConnectionManager connectionManager) {
125 boolean wasRemoved = connectionManagers.remove(connectionManager) != null;
126 if (connectionManagers.isEmpty()) {
127 shutdown();
128 }
129 return wasRemoved;
130 }
131
132 @SdkTestInternalApi
133 public static List<HttpClientConnectionManager> getRegisteredConnectionManagers() {
134 return new ArrayList<HttpClientConnectionManager>(connectionManagers.keySet());
135 }
136
137 /**
138 * Shuts down the thread, allowing the class and instance to be collected.
139 * <p/>
140 * Since this is a daemon thread, its running will not prevent JVM shutdown.
141 * It will, however, prevent this class from being unloaded or garbage
142 * collected, in the context of a long-running application, until it is
143 * interrupted. This method will stop the thread's execution and clear its
144 * state. Any use of a service client will cause the thread to be restarted.
145 *
146 * @return true if an actual shutdown has been made; false otherwise.
147 */
148 public static synchronized boolean shutdown() {
149 if (instance != null) {
150 instance.markShuttingDown();
151 instance.interrupt();
152 connectionManagers.clear();
153 instance = null;
154 return true;
155 }
156 return false;
157 }
158
159 /**
160 * For testing purposes.
161 * Returns the number of connection managers currently monitored by this
162 * reaper.
163 */
164 static int size() {
165 return connectionManagers.size();
166 }
167
168 private void markShuttingDown() {
169 shuttingDown = true;
170 }
171
172 @SuppressWarnings("unchecked")
173 @Override
174 public void run() {
175 while (!shuttingDown) {
176 try {
177 for (Map.Entry<HttpClientConnectionManager, Long> entry : connectionManagers.entrySet()) {
178 // When we release connections, the connection manager leaves them
179 // open so they can be reused. We want to close out any idle
180 // connections so that they don't sit around in CLOSE_WAIT.
181 try {
182 entry.getKey().closeIdleConnections(entry.getValue(), TimeUnit.MILLISECONDS);
183 } catch (Exception t) {
184 LOG.warn("Unable to close idle connections", t);
185 }
186 }
187
188 Thread.sleep(PERIOD_MILLISECONDS);
189 } catch (Throwable t) {
190 LOG.debug("Reaper thread: ", t);
191 }
192 }
193
194 LOG.debug("Shutting down reaper thread.");
195 }
196 }