Home | History | Annotate | Download | only in system
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package dalvik.system;
     18 
     19 /**
     20  * CloseGuard is a mechanism for flagging implicit finalizer cleanup of
     21  * resources that should have been cleaned up by explicit close
     22  * methods (aka "explicit termination methods" in Effective Java).
     23  * <p>
     24  * A simple example: <pre>   {@code
     25  *   class Foo {
     26  *
     27  *       {@literal @}ReachabilitySensitive
     28  *       private final CloseGuard guard = CloseGuard.get();
     29  *
     30  *       ...
     31  *
     32  *       public Foo() {
     33  *           ...;
     34  *           guard.open("cleanup");
     35  *       }
     36  *
     37  *       public void cleanup() {
     38  *          guard.close();
     39  *          ...;
     40  *       }
     41  *
     42  *       protected void finalize() throws Throwable {
     43  *           try {
     44  *               // Note that guard could be null if the constructor threw.
     45  *               if (guard != null) {
     46  *                   guard.warnIfOpen();
     47  *               }
     48  *               cleanup();
     49  *           } finally {
     50  *               super.finalize();
     51  *           }
     52  *       }
     53  *   }
     54  * }</pre>
     55  *
     56  * In usage where the resource to be explicitly cleaned up is
     57  * allocated after object construction, CloseGuard protection can
     58  * be deferred. For example: <pre>   {@code
     59  *   class Bar {
     60  *
     61  *       {@literal @}ReachabilitySensitive
     62  *       private final CloseGuard guard = CloseGuard.get();
     63  *
     64  *       ...
     65  *
     66  *       public Bar() {
     67  *           ...;
     68  *       }
     69  *
     70  *       public void connect() {
     71  *          ...;
     72  *          guard.open("cleanup");
     73  *       }
     74  *
     75  *       public void cleanup() {
     76  *          guard.close();
     77  *          ...;
     78  *       }
     79  *
     80  *       protected void finalize() throws Throwable {
     81  *           try {
     82  *               // Note that guard could be null if the constructor threw.
     83  *               if (guard != null) {
     84  *                   guard.warnIfOpen();
     85  *               }
     86  *               cleanup();
     87  *           } finally {
     88  *               super.finalize();
     89  *           }
     90  *       }
     91  *   }
     92  * }</pre>
     93  *
     94  * When used in a constructor, calls to {@code open} should occur at
     95  * the end of the constructor since an exception that would cause
     96  * abrupt termination of the constructor will mean that the user will
     97  * not have a reference to the object to cleanup explicitly. When used
     98  * in a method, the call to {@code open} should occur just after
     99  * resource acquisition.
    100  *
    101  * The @ReachabilitySensitive annotation ensures that finalize() cannot be
    102  * called during the explicit call to cleanup(), prior to the guard.close call.
    103  * There is an extremely small chance that, for code that neglects to call
    104  * cleanup(), finalize() and thus cleanup() will be called while a method on
    105  * the object is still active, but the "this" reference is no longer required.
    106  * If missing cleanup() calls are expected, additional @ReachabilitySensitive
    107  * annotations or reachabilityFence() calls may be required.
    108  *
    109  * @hide
    110  */
    111 public final class CloseGuard {
    112 
    113     /**
    114      * True if collection of call-site information (the expensive operation
    115      * here)  and tracking via a Tracker (see below) are enabled.
    116      * Enabled by default so we can diagnose issues early in VM startup.
    117      * Note, however, that Android disables this early in its startup,
    118      * but enables it with DropBoxing for system apps on debug builds.
    119      */
    120     private static volatile boolean stackAndTrackingEnabled = true;
    121 
    122     /**
    123      * Hook for customizing how CloseGuard issues are reported.
    124      * Bypassed if stackAndTrackingEnabled was false when open was called.
    125      */
    126     private static volatile Reporter reporter = new DefaultReporter();
    127 
    128     /**
    129      * Hook for customizing how CloseGuard issues are tracked.
    130      */
    131     private static volatile Tracker currentTracker = null; // Disabled by default.
    132 
    133     /**
    134      * Returns a CloseGuard instance. {@code #open(String)} can be used to set
    135      * up the instance to warn on failure to close.
    136      */
    137     public static CloseGuard get() {
    138         return new CloseGuard();
    139     }
    140 
    141     /**
    142      * Enables/disables stack capture and tracking. A call stack is captured
    143      * during open(), and open/close events are reported to the Tracker, only
    144      * if enabled is true. If a stack trace was captured, the {@link
    145      * #getReporter() reporter} is informed of unclosed resources; otherwise a
    146      * one-line warning is logged.
    147      */
    148     public static void setEnabled(boolean enabled) {
    149         CloseGuard.stackAndTrackingEnabled = enabled;
    150     }
    151 
    152     /**
    153      * True if CloseGuard stack capture and tracking are enabled.
    154      */
    155     public static boolean isEnabled() {
    156         return stackAndTrackingEnabled;
    157     }
    158 
    159     /**
    160      * Used to replace default Reporter used to warn of CloseGuard
    161      * violations when stack tracking is enabled. Must be non-null.
    162      */
    163     public static void setReporter(Reporter rep) {
    164         if (rep == null) {
    165             throw new NullPointerException("reporter == null");
    166         }
    167         CloseGuard.reporter = rep;
    168     }
    169 
    170     /**
    171      * Returns non-null CloseGuard.Reporter.
    172      */
    173     public static Reporter getReporter() {
    174         return reporter;
    175     }
    176 
    177     /**
    178      * Sets the {@link Tracker} that is notified when resources are allocated and released.
    179      * The Tracker is invoked only if CloseGuard {@link #isEnabled()} held when {@link #open()}
    180      * was called. A null argument disables tracking.
    181      *
    182      * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
    183      * MUST NOT be used for any other purposes.
    184      */
    185     public static void setTracker(Tracker tracker) {
    186         currentTracker = tracker;
    187     }
    188 
    189     /**
    190      * Returns {@link #setTracker(Tracker) last Tracker that was set}, or null to indicate
    191      * there is none.
    192      *
    193      * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
    194      * MUST NOT be used for any other purposes.
    195      */
    196     public static Tracker getTracker() {
    197         return currentTracker;
    198     }
    199 
    200     private CloseGuard() {}
    201 
    202     /**
    203      * {@code open} initializes the instance with a warning that the caller
    204      * should have explicitly called the {@code closer} method instead of
    205      * relying on finalization.
    206      *
    207      * @param closer non-null name of explicit termination method. Printed by warnIfOpen.
    208      * @throws NullPointerException if closer is null.
    209      */
    210     public void open(String closer) {
    211         // always perform the check for valid API usage...
    212         if (closer == null) {
    213             throw new NullPointerException("closer == null");
    214         }
    215         // ...but avoid allocating an allocation stack if "disabled"
    216         if (!stackAndTrackingEnabled) {
    217             closerNameOrAllocationInfo = closer;
    218             return;
    219         }
    220         String message = "Explicit termination method '" + closer + "' not called";
    221         Throwable stack = new Throwable(message);
    222         closerNameOrAllocationInfo = stack;
    223         Tracker tracker = currentTracker;
    224         if (tracker != null) {
    225             tracker.open(stack);
    226         }
    227     }
    228 
    229     // We keep either an allocation stack containing the closer String or, when
    230     // in disabled state, just the closer String.
    231     // We keep them in a single field only to minimize overhead.
    232     private Object /* String or Throwable */ closerNameOrAllocationInfo;
    233 
    234     /**
    235      * Marks this CloseGuard instance as closed to avoid warnings on
    236      * finalization.
    237      */
    238     public void close() {
    239         Tracker tracker = currentTracker;
    240         if (tracker != null && closerNameOrAllocationInfo instanceof Throwable) {
    241             // Invoke tracker on close only if we invoked it on open. Tracker may have changed.
    242             tracker.close((Throwable) closerNameOrAllocationInfo);
    243         }
    244         closerNameOrAllocationInfo = null;
    245     }
    246 
    247     /**
    248      * Logs a warning if the caller did not properly cleanup by calling an
    249      * explicit close method before finalization. If CloseGuard was enabled
    250      * when the CloseGuard was created, passes the stacktrace associated with
    251      * the allocation to the current reporter. If it was not enabled, it just
    252      * directly logs a brief message.
    253      */
    254     public void warnIfOpen() {
    255         if (closerNameOrAllocationInfo != null) {
    256             if (closerNameOrAllocationInfo instanceof String) {
    257                 System.logW("A resource failed to call "
    258                         + (String) closerNameOrAllocationInfo + ". ");
    259             } else {
    260                 String message =
    261                         "A resource was acquired at attached stack trace but never released. ";
    262                 message += "See java.io.Closeable for information on avoiding resource leaks.";
    263                 Throwable stack = (Throwable) closerNameOrAllocationInfo;
    264                 reporter.report(message, stack);
    265             }
    266         }
    267     }
    268 
    269     /**
    270      * Interface to allow customization of tracking behaviour.
    271      *
    272      * <p>This is only intended for use by {@code dalvik.system.CloseGuardSupport} class and so
    273      * MUST NOT be used for any other purposes.
    274      */
    275     public interface Tracker {
    276         void open(Throwable allocationSite);
    277         void close(Throwable allocationSite);
    278     }
    279 
    280     /**
    281      * Interface to allow customization of reporting behavior.
    282      */
    283     public interface Reporter {
    284         void report (String message, Throwable allocationSite);
    285     }
    286 
    287     /**
    288      * Default Reporter which reports CloseGuard violations to the log.
    289      */
    290     private static final class DefaultReporter implements Reporter {
    291         @Override public void report (String message, Throwable allocationSite) {
    292             System.logW(message, allocationSite);
    293         }
    294     }
    295 }
    296