blob: 5ce16c49a9457d796f4ad801b7820332cb3e3bba [file] [log] [blame]
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -07001/*
2 * Copyright (C) 2016 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 com.android.server;
18
Makoto Onukieed5b5a2018-03-13 14:22:23 -070019import android.annotation.Nullable;
20import android.text.TextUtils;
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -070021import android.util.ArrayMap;
22import android.util.ArraySet;
23import android.util.Slog;
24
Makoto Onukieed5b5a2018-03-13 14:22:23 -070025import com.android.internal.os.BackgroundThread;
26
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -070027import java.io.FileDescriptor;
28import java.io.PrintWriter;
29
30/**
31 * LockGuard is a mechanism to help detect lock inversions inside the system
32 * server. It works by requiring each lock acquisition site to follow this
33 * pattern:
34 *
35 * <pre>
36 * synchronized (LockGuard.guard(lock)) {
37 * }
38 * </pre>
39 *
40 * <pre>
41 * $ find services/ -name "*.java" -exec sed -i -r \
42 * 's/synchronized.?\((.+?)\)/synchronized \(com.android.server.LockGuard.guard\(\1\)\)/' {} \;
43 * </pre>
44 *
45 * The {@link #guard(Object)} method internally verifies that all locking is
46 * done in a consistent order, and will log if any inversion is detected. For
47 * example, if the calling thread is trying to acquire the
48 * {@code ActivityManager} lock while holding the {@code PackageManager} lock,
49 * it will yell.
50 * <p>
51 * This class requires no prior knowledge of locks or their ordering; it derives
52 * all of this data at runtime. However, this means the overhead is
53 * <em>substantial</em> and it should not be enabled by default. For example,
54 * here are some benchmarked timings:
55 * <ul>
56 * <li>An unguarded synchronized block takes 40ns.
57 * <li>A guarded synchronized block takes 50ns when disabled.
58 * <li>A guarded synchronized block takes 460ns per lock checked when enabled.
59 * </ul>
Jeff Sharkey5f3e9342017-03-13 14:53:11 -060060 * <p>
61 * This class also supports a second simpler mode of operation where well-known
62 * locks are explicitly registered and checked via indexes.
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -070063 */
64public class LockGuard {
65 private static final String TAG = "LockGuard";
66
Jeff Sharkey5f3e9342017-03-13 14:53:11 -060067 /**
68 * Well-known locks ordered by fixed index. Locks with a specific index
69 * should never be acquired while holding a lock of a lower index.
70 */
71 public static final int INDEX_APP_OPS = 0;
72 public static final int INDEX_POWER = 1;
73 public static final int INDEX_USER = 2;
74 public static final int INDEX_PACKAGES = 3;
75 public static final int INDEX_STORAGE = 4;
76 public static final int INDEX_WINDOW = 5;
77 public static final int INDEX_ACTIVITY = 6;
Makoto Onukieed5b5a2018-03-13 14:22:23 -070078 public static final int INDEX_DPMS = 7;
Jeff Sharkey5f3e9342017-03-13 14:53:11 -060079
Makoto Onukieed5b5a2018-03-13 14:22:23 -070080 private static Object[] sKnownFixed = new Object[INDEX_DPMS + 1];
Jeff Sharkey5f3e9342017-03-13 14:53:11 -060081
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -070082 private static ArrayMap<Object, LockInfo> sKnown = new ArrayMap<>(0, true);
83
84 private static class LockInfo {
85 /** Friendly label to describe this lock */
86 public String label;
87
88 /** Child locks that can be acquired while this lock is already held */
89 public ArraySet<Object> children = new ArraySet<>(0, true);
Makoto Onukieed5b5a2018-03-13 14:22:23 -070090
91 /** If true, do wtf instead of a warning log. */
92 public boolean doWtf;
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -070093 }
94
95 private static LockInfo findOrCreateLockInfo(Object lock) {
96 LockInfo info = sKnown.get(lock);
97 if (info == null) {
98 info = new LockInfo();
99 info.label = "0x" + Integer.toHexString(System.identityHashCode(lock)) + " ["
100 + new Throwable().getStackTrace()[2].toString() + "]";
101 sKnown.put(lock, info);
102 }
103 return info;
104 }
105
106 /**
107 * Check if the calling thread is holding any locks in an inverted order.
108 *
109 * @param lock The lock the calling thread is attempting to acquire.
110 */
111 public static Object guard(Object lock) {
112 // If we already hold this lock, ignore
113 if (lock == null || Thread.holdsLock(lock)) return lock;
114
115 // Check to see if we're already holding any child locks
116 boolean triggered = false;
117 final LockInfo info = findOrCreateLockInfo(lock);
118 for (int i = 0; i < info.children.size(); i++) {
119 final Object child = info.children.valueAt(i);
120 if (child == null) continue;
121
122 if (Thread.holdsLock(child)) {
Makoto Onukieed5b5a2018-03-13 14:22:23 -0700123 doLog(lock, "Calling thread " + Thread.currentThread().getName()
124 + " is holding " + lockToString(child) + " while trying to acquire "
125 + lockToString(lock));
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700126 triggered = true;
127 }
128 }
129
130 if (!triggered) {
131 // If no trouble found above, record this lock as being a valid
132 // child of all locks currently being held
133 for (int i = 0; i < sKnown.size(); i++) {
134 final Object test = sKnown.keyAt(i);
135 if (test == null || test == lock) continue;
136
137 if (Thread.holdsLock(test)) {
138 sKnown.valueAt(i).children.add(lock);
139 }
140 }
141 }
142
143 return lock;
144 }
145
146 /**
Jeff Sharkey5f3e9342017-03-13 14:53:11 -0600147 * Yell if any lower-level locks are being held by the calling thread that
148 * is about to acquire the given lock.
149 */
150 public static void guard(int index) {
151 for (int i = 0; i < index; i++) {
152 final Object lock = sKnownFixed[i];
153 if (lock != null && Thread.holdsLock(lock)) {
Makoto Onukieed5b5a2018-03-13 14:22:23 -0700154
155 // Note in this case sKnownFixed may not contain a lock at the given index,
156 // which is okay and in that case we just don't do a WTF.
157 final Object targetMayBeNull = sKnownFixed[index];
158 doLog(targetMayBeNull, "Calling thread " + Thread.currentThread().getName()
159 + " is holding " + lockToString(i) + " while trying to acquire "
160 + lockToString(index));
Jeff Sharkey5f3e9342017-03-13 14:53:11 -0600161 }
162 }
163 }
164
Makoto Onukieed5b5a2018-03-13 14:22:23 -0700165 private static void doLog(@Nullable Object lock, String message) {
166 if (lock != null && findOrCreateLockInfo(lock).doWtf) {
167
168 // Don't want to call into ActivityManager with any lock held, so let's just call it
169 // from a new thread. We don't want to use known threads (e.g. BackgroundThread) either
170 // because they may be stuck too.
171 final Throwable stackTrace = new RuntimeException(message);
172 new Thread(() -> Slog.wtf(TAG, stackTrace)).start();
173 return;
174 }
175 Slog.w(TAG, message, new Throwable());
176 }
177
Jeff Sharkey5f3e9342017-03-13 14:53:11 -0600178 /**
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700179 * Report the given lock with a well-known label.
180 */
Jeff Sharkey5f3e9342017-03-13 14:53:11 -0600181 public static Object installLock(Object lock, String label) {
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700182 final LockInfo info = findOrCreateLockInfo(lock);
183 info.label = label;
Jeff Sharkey5f3e9342017-03-13 14:53:11 -0600184 return lock;
185 }
186
187 /**
188 * Report the given lock with a well-known index.
189 */
190 public static Object installLock(Object lock, int index) {
Makoto Onukieed5b5a2018-03-13 14:22:23 -0700191 return installLock(lock, index, /*doWtf=*/ false);
192 }
193
194 /**
195 * Report the given lock with a well-known index.
196 */
197 public static Object installLock(Object lock, int index, boolean doWtf) {
Jeff Sharkey5f3e9342017-03-13 14:53:11 -0600198 sKnownFixed[index] = lock;
Makoto Onukieed5b5a2018-03-13 14:22:23 -0700199 final LockInfo info = findOrCreateLockInfo(lock);
200 info.doWtf = doWtf;
201 info.label = "Lock-" + lockToString(index);
Jeff Sharkey5f3e9342017-03-13 14:53:11 -0600202 return lock;
203 }
204
205 public static Object installNewLock(int index) {
Makoto Onukieed5b5a2018-03-13 14:22:23 -0700206 return installNewLock(index, /*doWtf=*/ false);
207 }
208
209 public static Object installNewLock(int index, boolean doWtf) {
Jeff Sharkey5f3e9342017-03-13 14:53:11 -0600210 final Object lock = new Object();
Makoto Onukieed5b5a2018-03-13 14:22:23 -0700211 installLock(lock, index, doWtf);
Jeff Sharkey5f3e9342017-03-13 14:53:11 -0600212 return lock;
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700213 }
214
215 private static String lockToString(Object lock) {
216 final LockInfo info = sKnown.get(lock);
Makoto Onukieed5b5a2018-03-13 14:22:23 -0700217 if (info != null && !TextUtils.isEmpty(info.label)) {
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700218 return info.label;
219 } else {
220 return "0x" + Integer.toHexString(System.identityHashCode(lock));
221 }
222 }
223
Jeff Sharkey5f3e9342017-03-13 14:53:11 -0600224 private static String lockToString(int index) {
225 switch (index) {
226 case INDEX_APP_OPS: return "APP_OPS";
227 case INDEX_POWER: return "POWER";
228 case INDEX_USER: return "USER";
229 case INDEX_PACKAGES: return "PACKAGES";
230 case INDEX_STORAGE: return "STORAGE";
231 case INDEX_WINDOW: return "WINDOW";
232 case INDEX_ACTIVITY: return "ACTIVITY";
Makoto Onukieed5b5a2018-03-13 14:22:23 -0700233 case INDEX_DPMS: return "DPMS";
Jeff Sharkey5f3e9342017-03-13 14:53:11 -0600234 default: return Integer.toString(index);
235 }
236 }
237
Jeff Sharkey3d1cb6a2016-02-27 21:10:34 -0700238 public static void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
239 for (int i = 0; i < sKnown.size(); i++) {
240 final Object lock = sKnown.keyAt(i);
241 final LockInfo info = sKnown.valueAt(i);
242 pw.println("Lock " + lockToString(lock) + ":");
243 for (int j = 0; j < info.children.size(); j++) {
244 pw.println(" Child " + lockToString(info.children.valueAt(j)));
245 }
246 pw.println();
247 }
248 }
249}