EvictionTimer.java

  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. import java.lang.ref.WeakReference;
  19. import java.security.AccessController;
  20. import java.security.PrivilegedAction;
  21. import java.time.Duration;
  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.  * Provides a shared idle object eviction timer for all pools.
  30.  * <p>
  31.  * This class is currently implemented using {@link ScheduledThreadPoolExecutor}. This implementation may change in any
  32.  * future release. This class keeps track of how many pools are using it. If no pools are using the timer, it is
  33.  * cancelled. This prevents a thread being left running which, in application server environments, can lead to memory
  34.  * leads and/or prevent applications from shutting down or reloading cleanly.
  35.  * </p>
  36.  * <p>
  37.  * This class has package scope to prevent its inclusion in the pool public API. The class declaration below should
  38.  * *not* be changed to public.
  39.  * </p>
  40.  * <p>
  41.  * This class is intended to be thread-safe.
  42.  * </p>
  43.  *
  44.  * @since 2.0
  45.  */
  46. final class EvictionTimer {

  47.     /**
  48.      * Thread factory that creates a daemon thread, with the context class loader from this class.
  49.      */
  50.     private static final class EvictorThreadFactory implements ThreadFactory {

  51.         @Override
  52.         public Thread newThread(final Runnable runnable) {
  53.             final Thread thread = new Thread(null, runnable, "commons-pool-evictor");
  54.             thread.setDaemon(true); // POOL-363 - Required for applications using Runtime.addShutdownHook().
  55.             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
  56.                 thread.setContextClassLoader(EvictorThreadFactory.class.getClassLoader());
  57.                 return null;
  58.             });

  59.             return thread;
  60.         }
  61.     }

  62.     /**
  63.      * Task that removes references to abandoned tasks and shuts
  64.      * down the executor if there are no live tasks left.
  65.      */
  66.     private static final class Reaper implements Runnable {
  67.         @Override
  68.         public void run() {
  69.             synchronized (EvictionTimer.class) {
  70.                 for (final Entry<WeakReference<BaseGenericObjectPool<?>.Evictor>, WeakRunner<BaseGenericObjectPool<?>.Evictor>> entry : TASK_MAP
  71.                         .entrySet()) {
  72.                     if (entry.getKey().get() == null) {
  73.                         executor.remove(entry.getValue());
  74.                         TASK_MAP.remove(entry.getKey());
  75.                     }
  76.                 }
  77.                 if (TASK_MAP.isEmpty() && executor != null) {
  78.                     executor.shutdown();
  79.                     executor.setCorePoolSize(0);
  80.                     executor = null;
  81.                 }
  82.             }
  83.         }
  84.     }

  85.     /**
  86.      * Runnable that runs the referent of a weak reference. When the referent is no
  87.      * no longer reachable, run is no-op.
  88.      * @param <R> The kind of Runnable.
  89.      */
  90.     private static final class WeakRunner<R extends Runnable> implements Runnable {

  91.         private final WeakReference<R> ref;

  92.         /**
  93.          * Constructs a new instance to track the given reference.
  94.          *
  95.          * @param ref the reference to track.
  96.          */
  97.         private WeakRunner(final WeakReference<R> ref) {
  98.            this.ref = ref;
  99.         }

  100.         @Override
  101.         public void run() {
  102.             final Runnable task = ref.get();
  103.             if (task != null) {
  104.                 task.run();
  105.             } else {
  106.                 executor.remove(this);
  107.                 TASK_MAP.remove(ref);
  108.             }
  109.         }
  110.     }

  111.     /** Executor instance */
  112.     private static ScheduledThreadPoolExecutor executor; //@GuardedBy("EvictionTimer.class")

  113.     /** Keys are weak references to tasks, values are runners managed by executor. */
  114.     private static final HashMap<
  115.         WeakReference<BaseGenericObjectPool<?>.Evictor>,
  116.         WeakRunner<BaseGenericObjectPool<?>.Evictor>> TASK_MAP = new HashMap<>(); // @GuardedBy("EvictionTimer.class")

  117.     /**
  118.      * Removes the specified eviction task from the timer.
  119.      *
  120.      * @param evictor   Task to be cancelled.
  121.      * @param timeout   If the associated executor is no longer required, how
  122.      *                  long should this thread wait for the executor to
  123.      *                  terminate?
  124.      * @param restarting The state of the evictor.
  125.      */
  126.     static synchronized void cancel(final BaseGenericObjectPool<?>.Evictor evictor, final Duration timeout,
  127.             final boolean restarting) {
  128.         if (evictor != null) {
  129.             evictor.cancel();
  130.             remove(evictor);
  131.         }
  132.         if (!restarting && executor != null && TASK_MAP.isEmpty()) {
  133.             executor.shutdown();
  134.             try {
  135.                 executor.awaitTermination(timeout.toMillis(), TimeUnit.MILLISECONDS);
  136.             } catch (final InterruptedException e) {
  137.                 // Swallow
  138.                 // Significant API changes would be required to propagate this
  139.             }
  140.             executor.setCorePoolSize(0);
  141.             executor = null;
  142.         }
  143.     }

  144.     /**
  145.      * For testing only.
  146.      *
  147.      * @return The executor.
  148.      */
  149.     static ScheduledThreadPoolExecutor getExecutor() {
  150.         return executor;
  151.     }

  152.     /**
  153.      * @return the number of eviction tasks under management.
  154.      */
  155.     static synchronized int getNumTasks() {
  156.         return TASK_MAP.size();
  157.     }

  158.     /**
  159.      * Gets the task map. Keys are weak references to tasks, values are runners managed by executor.
  160.      *
  161.      * @return the task map.
  162.      */
  163.     static HashMap<WeakReference<BaseGenericObjectPool<?>.Evictor>, WeakRunner<BaseGenericObjectPool<?>.Evictor>> getTaskMap() {
  164.         return TASK_MAP;
  165.     }

  166.     /**
  167.      * Removes evictor from the task set and executor.
  168.      * Only called when holding the class lock.
  169.      *
  170.      * @param evictor Eviction task to remove
  171.      */
  172.     private static void remove(final BaseGenericObjectPool<?>.Evictor evictor) {
  173.         for (final Entry<WeakReference<BaseGenericObjectPool<?>.Evictor>, WeakRunner<BaseGenericObjectPool<?>.Evictor>> entry : TASK_MAP.entrySet()) {
  174.             if (entry.getKey().get() == evictor) {
  175.                 executor.remove(entry.getValue());
  176.                 TASK_MAP.remove(entry.getKey());
  177.                 break;
  178.             }
  179.         }
  180.     }

  181.     /**
  182.      * Adds the specified eviction task to the timer. Tasks that are added with
  183.      * a call to this method *must* call {@link
  184.      * #cancel(BaseGenericObjectPool.Evictor, Duration, boolean)}
  185.      * to cancel the task to prevent memory and/or thread leaks in application
  186.      * server environments.
  187.      *
  188.      * @param task      Task to be scheduled.
  189.      * @param delay     Delay in milliseconds before task is executed.
  190.      * @param period    Time in milliseconds between executions.
  191.      */
  192.     static synchronized void schedule(
  193.             final BaseGenericObjectPool<?>.Evictor task, final Duration delay, final Duration period) {
  194.         if (null == executor) {
  195.             executor = new ScheduledThreadPoolExecutor(1, new EvictorThreadFactory());
  196.             executor.setRemoveOnCancelPolicy(true);
  197.             executor.scheduleAtFixedRate(new Reaper(), delay.toMillis(), period.toMillis(), TimeUnit.MILLISECONDS);
  198.         }
  199.         final WeakReference<BaseGenericObjectPool<?>.Evictor> ref = new WeakReference<>(task);
  200.         final WeakRunner<BaseGenericObjectPool<?>.Evictor> runner = new WeakRunner<>(ref);
  201.         final ScheduledFuture<?> scheduledFuture = executor.scheduleWithFixedDelay(runner, delay.toMillis(),
  202.                 period.toMillis(), TimeUnit.MILLISECONDS);
  203.         task.setScheduledFuture(scheduledFuture);
  204.         TASK_MAP.put(ref, runner);
  205.     }

  206.     /** Prevents instantiation */
  207.     private EvictionTimer() {
  208.         // Hide the default constructor
  209.     }

  210.     /**
  211.      * @since 2.4.3
  212.      */
  213.     @Override
  214.     public String toString() {
  215.         final StringBuilder builder = new StringBuilder();
  216.         builder.append("EvictionTimer []");
  217.         return builder.toString();
  218.     }

  219. }