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