//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

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.slf4j.Logger;
import org.slf4j.LoggerFactory;

A facility to detect improper usage of resource pools.

Resource pools usually have a method to acquire a pooled resource and a method to released it back to the pool.

To detect if client code acquires a resource but never releases it, the resource pool can be modified to use a LeakDetector. The modified resource pool should call acquired(Object) every time the method to acquire a resource is called, and released(Object) every time the method to release the resource is called. LeakDetector keeps track of these resources and invokes method leaked(LeakInfo) when it detects that a resource has been leaked (that is, acquired but never released).

To detect whether client code releases a resource without having acquired it, the resource pool can be modified to check the return value of released(Object): if false, it means that the resource was not acquired.

IMPLEMENTATION NOTES

This class relies on System.identityHashCode(Object) to create a unique id for each resource passed to acquired(Object) and released(Object). 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.

LeakDetector uses PhantomReferences to detect leaks. PhantomReferences are enqueued in their ReferenceQueue after they have been garbage collected (differently from WeakReferences that are enqueued before). Since the resource is now garbage collected, LeakDetector checks whether it has been released and if not, it reports a leak. Using PhantomReferences is better than overriding Object.finalize() and works also in those cases where Object.finalize() is not overridable.

Type parameters:
  • <T> – the resource type.
/** * 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 = LoggerFactory.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.
Params:
  • resource – the resource that has been acquired
See Also:
Returns: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)
/** * 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.
Params:
  • resource – the resource that has been released
See Also:
Returns: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)
/** * 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.
Params:
  • resource – the resource to generate the unique ID for
Returns:the unique ID of the given resource
/** * 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 LeakDetector when it detects that a resource has been leaked.
Params:
  • leakInfo – the information about the leak
/** * 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.
/** * 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(); }
Returns:the resource description as provided by the resource's Object.toString() method.
/** * @return the resource description as provided by the resource's {@link Object#toString()} method. */
public String getResourceDescription() { return description; }
Returns:a Throwable instance that contains the stack frames at the time of resource acquisition.
/** * @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; } } }