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