1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.pool2.impl;
18
19 import java.lang.ref.WeakReference;
20 import java.security.AccessController;
21 import java.security.PrivilegedAction;
22 import java.time.Duration;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.Map.Entry;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.ScheduledThreadPoolExecutor;
28 import java.util.concurrent.ThreadFactory;
29 import java.util.concurrent.TimeUnit;
30
31 /**
32 * Provides a shared idle object eviction timer for all pools.
33 * <p>
34 * This class is currently implemented using {@link ScheduledThreadPoolExecutor}. This implementation may change in any
35 * future release. This class keeps track of how many pools are using it. If no pools are using the timer, it is
36 * canceled. This prevents a thread being left running which, in application server environments, can lead to memory
37 * leads and/or prevent applications from shutting down or reloading cleanly.
38 * </p>
39 * <p>
40 * This class has package scope to prevent its inclusion in the pool public API. The class declaration below should
41 * *not* be changed to public.
42 * </p>
43 * <p>
44 * This class is intended to be thread-safe.
45 * </p>
46 *
47 * @since 2.0
48 */
49 final class EvictionTimer {
50
51 /**
52 * Thread factory that creates a daemon thread, with the context class loader from this class.
53 */
54 private static final class EvictorThreadFactory implements ThreadFactory {
55
56 @Override
57 public Thread newThread(final Runnable runnable) {
58 final Thread thread = new Thread(null, runnable, "commons-pool-evictor");
59 thread.setDaemon(true); // POOL-363 - Required for applications using Runtime.addShutdownHook().
60 AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
61 thread.setContextClassLoader(EvictorThreadFactory.class.getClassLoader());
62 return null;
63 });
64
65 return thread;
66 }
67 }
68
69 /**
70 * Task that removes references to abandoned tasks and shuts
71 * down the executor if there are no live tasks left.
72 */
73 private static final class Reaper implements Runnable {
74 @Override
75 public void run() {
76 synchronized (EvictionTimer.class) {
77 /*
78 * Need to use iterator over TASK_MAP so entries can be removed when iterating without triggering a
79 * ConcurrentModificationException.
80 */
81 final Iterator<Entry<WeakReference<BaseGenericObjectPool<?>.Evictor>, WeakRunner<BaseGenericObjectPool<?>.Evictor>>> iterator =
82 TASK_MAP.entrySet().iterator();
83 while (iterator.hasNext()) {
84 final Entry<WeakReference<BaseGenericObjectPool<?>.Evictor>, WeakRunner<BaseGenericObjectPool<?>.Evictor>> entry = iterator.next();
85 if (entry.getKey().get() == null) {
86 executor.remove(entry.getValue());
87 iterator.remove();
88 }
89 }
90 if (TASK_MAP.isEmpty() && executor != null) {
91 executor.shutdown();
92 executor.setCorePoolSize(0);
93 executor = null;
94 }
95 }
96 }
97 }
98
99 /**
100 * Runnable that runs the referent of a weak reference. When the referent is no
101 * no longer reachable, run is no-op.
102 * @param <R> The kind of Runnable.
103 */
104 private static final class WeakRunner<R extends Runnable> implements Runnable {
105
106 private final WeakReference<R> ref;
107
108 /**
109 * Constructs a new instance to track the given reference.
110 *
111 * @param ref the reference to track.
112 */
113 private WeakRunner(final WeakReference<R> ref) {
114 this.ref = ref;
115 }
116
117 @Override
118 public void run() {
119 final Runnable task = ref.get();
120 if (task != null) {
121 task.run();
122 } else {
123 synchronized (EvictionTimer.class) {
124 executor.remove(this);
125 TASK_MAP.remove(ref);
126 }
127 }
128 }
129 }
130
131 /** Executor instance */
132 private static ScheduledThreadPoolExecutor executor; //@GuardedBy("EvictionTimer.class")
133
134 /** Keys are weak references to tasks, values are runners managed by executor. */
135 private static final HashMap<
136 WeakReference<BaseGenericObjectPool<?>.Evictor>,
137 WeakRunner<BaseGenericObjectPool<?>.Evictor>> TASK_MAP = new HashMap<>(); // @GuardedBy("EvictionTimer.class")
138
139 /**
140 * Removes the specified eviction task from the timer.
141 *
142 * @param evictor Task to be canceled.
143 * @param timeout If the associated executor is no longer required, how
144 * long should this thread wait for the executor to
145 * terminate?
146 * @param restarting The state of the evictor.
147 */
148 static synchronized void cancel(final BaseGenericObjectPool<?>.Evictor evictor, final Duration timeout,
149 final boolean restarting) {
150 if (evictor != null) {
151 evictor.cancel();
152 remove(evictor);
153 }
154 if (!restarting && executor != null && TASK_MAP.isEmpty()) {
155 executor.shutdown();
156 try {
157 executor.awaitTermination(timeout.toMillis(), TimeUnit.MILLISECONDS);
158 } catch (final InterruptedException e) {
159 // Swallow
160 // Significant API changes would be required to propagate this
161 }
162 executor.setCorePoolSize(0);
163 executor = null;
164 }
165 }
166
167 /**
168 * For testing only.
169 *
170 * @return The executor.
171 */
172 static ScheduledThreadPoolExecutor getExecutor() {
173 return executor;
174 }
175
176 /**
177 * @return the number of eviction tasks under management.
178 */
179 static synchronized int getNumTasks() {
180 return TASK_MAP.size();
181 }
182
183 /**
184 * Gets the task map. Keys are weak references to tasks, values are runners managed by executor.
185 *
186 * @return the task map.
187 */
188 static HashMap<WeakReference<BaseGenericObjectPool<?>.Evictor>, WeakRunner<BaseGenericObjectPool<?>.Evictor>> getTaskMap() {
189 return TASK_MAP;
190 }
191
192 /**
193 * Removes evictor from the task set and executor.
194 * Only called when holding the class lock.
195 *
196 * @param evictor Eviction task to remove
197 */
198 private static void remove(final BaseGenericObjectPool<?>.Evictor evictor) {
199 for (final Entry<WeakReference<BaseGenericObjectPool<?>.Evictor>, WeakRunner<BaseGenericObjectPool<?>.Evictor>> entry : TASK_MAP.entrySet()) {
200 if (entry.getKey().get() == evictor) {
201 executor.remove(entry.getValue());
202 TASK_MAP.remove(entry.getKey());
203 break;
204 }
205 }
206 }
207
208 /**
209 * Adds the specified eviction task to the timer. Tasks that are added with
210 * a call to this method *must* call {@link
211 * #cancel(BaseGenericObjectPool.Evictor, Duration, boolean)}
212 * to cancel the task to prevent memory and/or thread leaks in application
213 * server environments.
214 *
215 * @param task Task to be scheduled.
216 * @param delay Duration before task is executed.
217 * @param period Duration between executions.
218 */
219 static synchronized void schedule(
220 final BaseGenericObjectPool<?>.Evictor task, final Duration delay, final Duration period) {
221 if (null == executor) {
222 executor = new ScheduledThreadPoolExecutor(1, new EvictorThreadFactory());
223 executor.setRemoveOnCancelPolicy(true);
224 executor.scheduleAtFixedRate(new Reaper(), delay.toMillis(), period.toMillis(), TimeUnit.MILLISECONDS);
225 }
226 final WeakReference<BaseGenericObjectPool<?>.Evictor> ref = new WeakReference<>(task);
227 final WeakRunner<BaseGenericObjectPool<?>.Evictor> runner = new WeakRunner<>(ref);
228 final ScheduledFuture<?> scheduledFuture = executor.scheduleWithFixedDelay(runner, delay.toMillis(),
229 period.toMillis(), TimeUnit.MILLISECONDS);
230 task.setScheduledFuture(scheduledFuture);
231 TASK_MAP.put(ref, runner);
232 }
233
234 /** Prevents instantiation */
235 private EvictionTimer() {
236 // Hide the default constructor
237 }
238
239 /**
240 * @since 2.4.3
241 */
242 @Override
243 public String toString() {
244 final StringBuilder builder = new StringBuilder();
245 builder.append("EvictionTimer []");
246 return builder.toString();
247 }
248
249 }