View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.util;
20  
21  import java.lang.ref.PhantomReference;
22  import java.lang.ref.ReferenceQueue;
23  import java.lang.ref.WeakReference;
24  import java.util.concurrent.ConcurrentHashMap;
25  import java.util.concurrent.ConcurrentMap;
26  
27  import org.eclipse.jetty.util.component.AbstractLifeCycle;
28  import org.eclipse.jetty.util.log.Log;
29  import org.eclipse.jetty.util.log.Logger;
30  
31  /**
32   * A facility to detect improper usage of resource pools.
33   * <p>
34   * Resource pools usually have a method to acquire a pooled resource and a method to released it back to the pool.
35   * <p>
36   * To detect if client code acquires a resource but never releases it, the resource pool can be modified to use a
37   * {@link LeakDetector}. The modified resource pool should call {@link #acquired(Object)} every time the method to
38   * acquire a resource is called, and {@link #released(Object)} every time the method to release the resource is called.
39   * {@link LeakDetector} keeps track of these resources and invokes method
40   * {@link #leaked(org.eclipse.jetty.util.LeakDetector.LeakInfo)} when it detects that a resource has been leaked (that
41   * is, acquired but never released).
42   * <p>
43   * To detect whether client code releases a resource without having acquired it, the resource pool can be modified to
44   * check the return value of {@link #released(Object)}: if false, it means that the resource was not acquired.
45   * <p>
46   * IMPLEMENTATION NOTES
47   * <p>
48   * This class relies on {@link System#identityHashCode(Object)} to create a unique id for each resource passed to
49   * {@link #acquired(Object)} and {@link #released(Object)}. {@link System#identityHashCode(Object)} does not guarantee
50   * that it will not generate the same number for different objects, but in practice the chance of collision is rare.
51   * <p>
52   * {@link LeakDetector} uses {@link PhantomReference}s to detect leaks. {@link PhantomReference}s are enqueued in their
53   * {@link ReferenceQueue} <em>after</em> they have been garbage collected (differently from {@link WeakReference}s that
54   * are enqueued <em>before</em>). Since the resource is now garbage collected, {@link LeakDetector} checks whether it
55   * has been released and if not, it reports a leak. Using {@link PhantomReference}s is better than overriding
56   * {@link #finalize()} and works also in those cases where {@link #finalize()} is not overridable.
57   *
58   * @param <T> the resource type.
59   */
60  public class LeakDetector<T> extends AbstractLifeCycle implements Runnable
61  {
62      private static final Logger LOG = Log.getLogger(LeakDetector.class);
63  
64      private final ReferenceQueue<T> queue = new ReferenceQueue<>();
65      private final ConcurrentMap<String, LeakInfo> resources = new ConcurrentHashMap<>();
66      private Thread thread;
67  
68      /**
69       * Tracks the resource as been acquired.
70       *
71       * @param resource the resource that has been acquired
72       * @return true whether the resource has been acquired normally, false if the resource has detected a leak (meaning
73       *         that another acquire occurred before a release of the same resource)
74       * @see #released(Object)
75       */
76      public boolean acquired(T resource)
77      {
78          String id = id(resource);
79          LeakInfo info = resources.putIfAbsent(id, new LeakInfo(resource,id));
80          if (info != null)
81          {
82              // Leak detected, prior acquire exists (not released) or id clash.
83              return false;
84          }
85          // Normal behavior.
86          return true;
87      }
88  
89      /**
90       * Tracks the resource as been released.
91       *
92       * @param resource the resource that has been released
93       * @return true whether the resource has been released normally (based on a previous acquire). false if the resource
94       *         has been released without a prior acquire (such as a double release scenario)
95       * @see #acquired(Object)
96       */
97      public boolean released(T resource)
98      {
99          String id = id(resource);
100         LeakInfo info = resources.remove(id);
101         if (info != null)
102         {
103             // Normal behavior.
104             return true;
105         }
106 
107         // Leak detected (released without acquire).
108         return false;
109     }
110 
111     /**
112      * Generates a unique ID for the given resource.
113      *
114      * @param resource the resource to generate the unique ID for
115      * @return the unique ID of the given resource
116      */
117     public String id(T resource)
118     {
119         return String.valueOf(System.identityHashCode(resource));
120     }
121 
122     @Override
123     protected void doStart() throws Exception
124     {
125         super.doStart();
126         thread = new Thread(this,getClass().getSimpleName());
127         thread.setDaemon(true);
128         thread.start();
129     }
130 
131     @Override
132     protected void doStop() throws Exception
133     {
134         super.doStop();
135         thread.interrupt();
136     }
137 
138     @Override
139     public void run()
140     {
141         try
142         {
143             while (isRunning())
144             {
145                 @SuppressWarnings("unchecked")
146                 LeakInfo leakInfo = (LeakInfo)queue.remove();
147                 if (LOG.isDebugEnabled())
148                     LOG.debug("Resource GC'ed: {}",leakInfo);
149                 if (resources.remove(leakInfo.id) != null)
150                     leaked(leakInfo);
151             }
152         }
153         catch (InterruptedException x)
154         {
155             // Exit
156         }
157     }
158 
159     /**
160      * Callback method invoked by {@link LeakDetector} when it detects that a resource has been leaked.
161      *
162      * @param leakInfo the information about the leak
163      */
164     protected void leaked(LeakInfo leakInfo)
165     {
166         LOG.warn("Resource leaked: " + leakInfo.description,leakInfo.stackFrames);
167     }
168 
169     /**
170      * Information about the leak of a resource.
171      */
172     public class LeakInfo extends PhantomReference<T>
173     {
174         private final String id;
175         private final String description;
176         private final Throwable stackFrames;
177 
178         private LeakInfo(T referent, String id)
179         {
180             super(referent,queue);
181             this.id = id;
182             this.description = referent.toString();
183             this.stackFrames = new Throwable();
184         }
185 
186         /**
187          * @return the resource description as provided by the resource's {@link Object#toString()} method.
188          */
189         public String getResourceDescription()
190         {
191             return description;
192         }
193 
194         /**
195          * @return a Throwable instance that contains the stack frames at the time of resource acquisition.
196          */
197         public Throwable getStackFrames()
198         {
199             return stackFrames;
200         }
201 
202         @Override
203         public String toString()
204         {
205             return description;
206         }
207     }
208 }