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 }