CloseGuard.java revision b3f32d4d15177a0d2c064a8116d5cf5d07a217a9
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
17package dalvik.system;
18
19import java.util.logging.Level;
20import java.util.logging.Logger;
21
22/**
23 * CloseGuard is a mechanism for flagging implicit finalizer cleanup of
24 * resources that should have been cleaned up by explicit close
25 * methods (aka "explicit termination methods" in Effective Java).
26 * <p>
27 * A simple example: <pre>   {@code
28 *   class Foo {
29 *
30 *       private final CloseGuard guard = CloseGuard.get();
31 *
32 *       ...
33 *
34 *       public Foo() {
35 *           ...;
36 *           guard.open("cleanup");
37 *       }
38 *
39 *       public void cleanup() {
40 *          guard.close();
41 *          ...;
42 *       }
43 *
44 *       protected void finalize() throws Throwable {
45 *           try {
46 *               if (guard != null) {
47 *                   guard.warnIfOpen();
48 *               }
49 *               cleanup();
50 *           } finally {
51 *               super.finalize();
52 *           }
53 *       }
54 *   }
55 * }</pre>
56 *
57 * In usage where the resource to be explicitly cleaned up are
58 * allocated after object construction, CloseGuard can protection can
59 * be deferred. For example: <pre>   {@code
60 *   class Bar {
61 *
62 *       private final CloseGuard guard = CloseGuard.getUnopened();
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 *               if (guard != null) {
83 *                   guard.warnIfOpen();
84 *               }
85 *               cleanup();
86 *           } finally {
87 *               super.finalize();
88 *           }
89 *       }
90 *   }
91 * }</pre>
92 *
93 * When used in a constructor calls to {@code open} should occur at
94 * the end of the constructor since an exception that would cause
95 * abrupt termination of the constructor will mean that the user will
96 * not have a reference to the object to cleanup explicitly. When used
97 * in a method, the call to {@code open} should occur just after
98 * resource acquisition.
99 *
100 * <p>
101 *
102 * Note that the null check on {@code guard} in the finalizer is to
103 * cover cases where a constructor throws an exception causing the
104 * {@code guard} to be uninitialized.
105 *
106 * @hide
107 */
108public final class CloseGuard {
109
110    /**
111     * Instance used when CloseGuard is disabled to avoid allocation.
112     */
113    private static final CloseGuard NOOP = new CloseGuard();
114
115    /**
116     * Enabled by default so we can catch issues early in VM startup
117     */
118    private static boolean ENABLED = true;
119
120    /**
121     * Returns a CloseGuard instance. If CloseGuard is enabled, {@code
122     * #open(String)} can be used to set up the instance to warn on
123     * failure to close. If CloseGuard is disabled, a non-null no-op
124     * instance is returned.
125     */
126    public static CloseGuard get() {
127        if (!ENABLED) {
128            return NOOP;
129        }
130        return new CloseGuard();
131    }
132
133    /**
134     * Used to enable or disable CloseGuard. Note that CloseGuard only
135     * warns if it is enabled for both allocation and finalization.
136     */
137    public static void setEnabled(boolean enabled) {
138        ENABLED = enabled;
139    }
140
141    private CloseGuard() {}
142
143    /**
144     * If CloseGuard is enabled, {@code open} initializes the instance
145     * with a warning that the caller should have explicitly called the
146     * {@code closer} method instead of relying on finalization.
147     *
148     * @param closer non-null name of explicit termination method
149     * @throws NullPointerException if closer is null, regardless of
150     * whether or not CloseGuard is enabled
151     */
152    public void open(String closer) {
153        // always perform the check for valid API usage...
154        if (closer == null) {
155            throw new NullPointerException("closer == null");
156        }
157        // ...but avoid allocating an allocationSite if disabled
158        if (this == NOOP || !ENABLED) {
159            return;
160        }
161        String message = "Explicit termination method '" + closer + "' not called";
162        allocationSite = new Throwable(message);
163    }
164
165    private Throwable allocationSite;
166
167    /**
168     * Marks this CloseGuard instance as closed to avoid warnings on
169     * finalization.
170     */
171    public void close() {
172        allocationSite = null;
173    }
174
175    /**
176     * If CloseGuard is enabled, logs a warning if the caller did not
177     * properly cleanup by calling an explicit close method
178     * before finalization. If CloseGuard is disable, no action is
179     * performed.
180     */
181    public void warnIfOpen() {
182        if (allocationSite == null || !ENABLED) {
183            return;
184        }
185
186        String message =
187                ("A resource was acquired at attached stack trace but never released. "
188                 + "See java.io.Closeable for information on avoiding resource leaks.");
189
190        Logger.getLogger(CloseGuard.class.getName())
191                .log(Level.WARNING, message, allocationSite);
192    }
193}
194