]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/util/LeakDetector.java
Importing upstream Jetty jetty-9.2.1.v20140609
[gigi.git] / lib / jetty / org / eclipse / jetty / util / LeakDetector.java
1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2014 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
35  * and a method to released it back to the pool.
36  * <p>
37  * To detect if client code acquires a resource but never releases it,
38  * the resource pool can be modified to use a {@link LeakDetector}.
39  * The modified resource pool should call {@link #acquired(Object)} every time
40  * the method to acquire a resource is called, and {@link #released(Object)}
41  * every time the method to release the resource is called.
42  * {@link LeakDetector} keeps track of these resources and invokes method
43  * {@link #leaked(org.eclipse.jetty.util.LeakDetector.LeakInfo)} when it detects that a resource
44  * has been leaked (that is, acquired but never released).
45  * <p>
46  * To detect whether client code releases a resource without having
47  * acquired it, the resource pool can be modified to check the return value
48  * of {@link #released(Object)}: if false, it means that the resource was
49  * not acquired.
50  * <p>
51  * IMPLEMENTATION NOTES
52  * <p>
53  * This class relies on {@link System#identityHashCode(Object)} to create
54  * a unique id for each resource passed to {@link #acquired(Object)} and
55  * {@link #released(Object)}. {@link System#identityHashCode(Object)} does
56  * not guarantee that it will not generate the same number for different
57  * objects, but in practice the chance of collision is rare.
58  * <p>
59  * {@link LeakDetector} uses {@link PhantomReference}s to detect leaks.
60  * {@link PhantomReference}s are enqueued in their {@link ReferenceQueue}
61  * <em>after</em> they have been garbage collected (differently from
62  * {@link WeakReference}s that are enqueued <em>before</em>).
63  * Since the resource is now garbage collected, {@link LeakDetector} checks
64  * whether it has been released and if not, it reports a leak.
65  * Using {@link PhantomReference}s is better than overriding {@link #finalize()}
66  * and works also in those cases where {@link #finalize()} is not
67  * overridable.
68  *
69  * @param <T> the resource type.
70  */
71 public class LeakDetector<T> extends AbstractLifeCycle implements Runnable
72 {
73     private static final Logger LOG = Log.getLogger(LeakDetector.class);
74
75     private final ReferenceQueue<T> queue = new ReferenceQueue<>();
76     private final ConcurrentMap<String, LeakInfo> resources = new ConcurrentHashMap<>();
77     private Thread thread;
78
79     /**
80      * Tracks the resource as been acquired.
81      *
82      * @param resource the resource that has been acquired
83      * @return whether the resource has been tracked
84      * @see #released(Object)
85      */
86     public boolean acquired(T resource)
87     {
88         String id = id(resource);
89         return resources.putIfAbsent(id, new LeakInfo(resource, id)) == null;
90     }
91
92     /**
93      * Tracks the resource as been released.
94      *
95      * @param resource the resource that has been released
96      * @return whether the resource has been acquired
97      * @see #acquired(Object)
98      */
99     public boolean released(T resource)
100     {
101         String id = id(resource);
102         return resources.remove(id) != null;
103     }
104
105     /**
106      * Generates a unique ID for the given resource.
107      *
108      * @param resource the resource to generate the unique ID for
109      * @return the unique ID of the given resource
110      */
111     protected String id(T resource)
112     {
113         return String.valueOf(System.identityHashCode(resource));
114     }
115
116     @Override
117     protected void doStart() throws Exception
118     {
119         super.doStart();
120         thread = new Thread(this, getClass().getSimpleName());
121         thread.setDaemon(true);
122         thread.start();
123     }
124
125     @Override
126     protected void doStop() throws Exception
127     {
128         thread.interrupt();
129         super.doStop();
130     }
131
132     @Override
133     public void run()
134     {
135         try
136         {
137             while (isRunning())
138             {
139                 @SuppressWarnings("unchecked")
140                 LeakInfo leakInfo = (LeakInfo)queue.remove();
141                 LOG.debug("Resource GC'ed: {}", leakInfo);
142                 if (resources.remove(leakInfo.id) != null)
143                     leaked(leakInfo);
144             }
145         }
146         catch (InterruptedException x)
147         {
148             // Exit
149         }
150     }
151
152     /**
153      * Callback method invoked by {@link LeakDetector} when it detects that a resource has been leaked.
154      *
155      * @param leakInfo the information about the leak
156      */
157     protected void leaked(LeakInfo leakInfo)
158     {
159         LOG.warn("Resource leaked: " + leakInfo.description, leakInfo.stackFrames);
160     }
161
162     /**
163      * Information about the leak of a resource.
164      */
165     public class LeakInfo extends PhantomReference<T>
166     {
167         private final String id;
168         private final String description;
169         private final Throwable stackFrames;
170
171         private LeakInfo(T referent, String id)
172         {
173             super(referent, queue);
174             this.id = id;
175             this.description = referent.toString();
176             this.stackFrames = new Throwable();
177         }
178
179         /**
180          * @return the resource description as provided by the resource's {@link Object#toString()} method.
181          */
182         public String getResourceDescription()
183         {
184             return description;
185         }
186
187         /**
188          * @return a Throwable instance that contains the stack frames at the time of resource acquisition.
189          */
190         public Throwable getStackFrames()
191         {
192             return stackFrames;
193         }
194
195         @Override
196         public String toString()
197         {
198             return description;
199         }
200     }
201 }