View Javadoc
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 }