blob: e1c7ec01049c35dd71cdfb48709dea156a3f3269 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* A facility to detect improper usage of resource pools.
* <p>
* Resource pools usually have a method to acquire a pooled resource and a method to released it back to the pool.
* <p>
* To detect if client code acquires a resource but never releases it, the resource pool can be modified to use a
* {@link LeakDetector}. The modified resource pool should call {@link #acquired(Object)} every time the method to
* acquire a resource is called, and {@link #released(Object)} every time the method to release the resource is called.
* {@link LeakDetector} keeps track of these resources and invokes method
* {@link #leaked(org.eclipse.jetty.util.LeakDetector.LeakInfo)} when it detects that a resource has been leaked (that
* is, acquired but never released).
* <p>
* To detect whether client code releases a resource without having acquired it, the resource pool can be modified to
* check the return value of {@link #released(Object)}: if false, it means that the resource was not acquired.
* <p>
* IMPLEMENTATION NOTES
* <p>
* This class relies on {@link System#identityHashCode(Object)} to create a unique id for each resource passed to
* {@link #acquired(Object)} and {@link #released(Object)}. {@link System#identityHashCode(Object)} does not guarantee
* that it will not generate the same number for different objects, but in practice the chance of collision is rare.
* <p>
* {@link LeakDetector} uses {@link PhantomReference}s to detect leaks. {@link PhantomReference}s are enqueued in their
* {@link ReferenceQueue} <em>after</em> they have been garbage collected (differently from {@link WeakReference}s that
* are enqueued <em>before</em>). Since the resource is now garbage collected, {@link LeakDetector} checks whether it
* has been released and if not, it reports a leak. Using {@link PhantomReference}s is better than overriding
* {@link #finalize()} and works also in those cases where {@link #finalize()} is not overridable.
*
* @param <T> the resource type.
*/
public class LeakDetector<T> extends AbstractLifeCycle implements Runnable
{
private static final Logger LOG = Log.getLogger(LeakDetector.class);
private final ReferenceQueue<T> queue = new ReferenceQueue<>();
private final ConcurrentMap<String, LeakInfo> resources = new ConcurrentHashMap<>();
private Thread thread;
/**
* Tracks the resource as been acquired.
*
* @param resource the resource that has been acquired
* @return true whether the resource has been acquired normally, false if the resource has detected a leak (meaning
* that another acquire occurred before a release of the same resource)
* @see #released(Object)
*/
public boolean acquired(T resource)
{
String id = id(resource);
LeakInfo info = resources.putIfAbsent(id, new LeakInfo(resource,id));
if (info != null)
{
// Leak detected, prior acquire exists (not released) or id clash.
return false;
}
// Normal behavior.
return true;
}
/**
* Tracks the resource as been released.
*
* @param resource the resource that has been released
* @return true whether the resource has been released normally (based on a previous acquire). false if the resource
* has been released without a prior acquire (such as a double release scenario)
* @see #acquired(Object)
*/
public boolean released(T resource)
{
String id = id(resource);
LeakInfo info = resources.remove(id);
if (info != null)
{
// Normal behavior.
return true;
}
// Leak detected (released without acquire).
return false;
}
/**
* Generates a unique ID for the given resource.
*
* @param resource the resource to generate the unique ID for
* @return the unique ID of the given resource
*/
public String id(T resource)
{
return String.valueOf(System.identityHashCode(resource));
}
@Override
protected void doStart() throws Exception
{
super.doStart();
thread = new Thread(this,getClass().getSimpleName());
thread.setDaemon(true);
thread.start();
}
@Override
protected void doStop() throws Exception
{
super.doStop();
thread.interrupt();
}
@Override
public void run()
{
try
{
while (isRunning())
{
@SuppressWarnings("unchecked")
LeakInfo leakInfo = (LeakInfo)queue.remove();
if (LOG.isDebugEnabled())
LOG.debug("Resource GC'ed: {}",leakInfo);
if (resources.remove(leakInfo.id) != null)
leaked(leakInfo);
}
}
catch (InterruptedException x)
{
// Exit
}
}
/**
* Callback method invoked by {@link LeakDetector} when it detects that a resource has been leaked.
*
* @param leakInfo the information about the leak
*/
protected void leaked(LeakInfo leakInfo)
{
LOG.warn("Resource leaked: " + leakInfo.description,leakInfo.stackFrames);
}
/**
* Information about the leak of a resource.
*/
public class LeakInfo extends PhantomReference<T>
{
private final String id;
private final String description;
private final Throwable stackFrames;
private LeakInfo(T referent, String id)
{
super(referent,queue);
this.id = id;
this.description = referent.toString();
this.stackFrames = new Throwable();
}
/**
* @return the resource description as provided by the resource's {@link Object#toString()} method.
*/
public String getResourceDescription()
{
return description;
}
/**
* @return a Throwable instance that contains the stack frames at the time of resource acquisition.
*/
public Throwable getStackFrames()
{
return stackFrames;
}
@Override
public String toString()
{
return description;
}
}
}