Home | History | Annotate | Download | only in server
      1 /*
      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 
     17 package com.android.server;
     18 
     19 import android.annotation.Nullable;
     20 import android.text.TextUtils;
     21 import android.util.ArrayMap;
     22 import android.util.ArraySet;
     23 import android.util.Slog;
     24 
     25 import com.android.internal.os.BackgroundThread;
     26 
     27 import java.io.FileDescriptor;
     28 import 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>
     60  * <p>
     61  * This class also supports a second simpler mode of operation where well-known
     62  * locks are explicitly registered and checked via indexes.
     63  */
     64 public class LockGuard {
     65     private static final String TAG = "LockGuard";
     66 
     67     /**
     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;
     78     public static final int INDEX_DPMS = 7;
     79 
     80     private static Object[] sKnownFixed = new Object[INDEX_DPMS + 1];
     81 
     82     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);
     90 
     91         /** If true, do wtf instead of a warning log. */
     92         public boolean doWtf;
     93     }
     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)) {
    123                 doLog(lock, "Calling thread " + Thread.currentThread().getName()
    124                         + " is holding " + lockToString(child) + " while trying to acquire "
    125                         + lockToString(lock));
    126                 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     /**
    147      * 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)) {
    154 
    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));
    161             }
    162         }
    163     }
    164 
    165     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 
    178     /**
    179      * Report the given lock with a well-known label.
    180      */
    181     public static Object installLock(Object lock, String label) {
    182         final LockInfo info = findOrCreateLockInfo(lock);
    183         info.label = label;
    184         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) {
    191         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) {
    198         sKnownFixed[index] = lock;
    199         final LockInfo info = findOrCreateLockInfo(lock);
    200         info.doWtf = doWtf;
    201         info.label = "Lock-" + lockToString(index);
    202         return lock;
    203     }
    204 
    205     public static Object installNewLock(int index) {
    206         return installNewLock(index, /*doWtf=*/ false);
    207     }
    208 
    209     public static Object installNewLock(int index, boolean doWtf) {
    210         final Object lock = new Object();
    211         installLock(lock, index, doWtf);
    212         return lock;
    213     }
    214 
    215     private static String lockToString(Object lock) {
    216         final LockInfo info = sKnown.get(lock);
    217         if (info != null && !TextUtils.isEmpty(info.label)) {
    218             return info.label;
    219         } else {
    220             return "0x" + Integer.toHexString(System.identityHashCode(lock));
    221         }
    222     }
    223 
    224     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";
    233             case INDEX_DPMS: return "DPMS";
    234             default: return Integer.toString(index);
    235         }
    236     }
    237 
    238     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 }
    250