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    *      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 }