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.util.ArrayMap;
     20 import android.util.ArraySet;
     21 import android.util.Slog;
     22 
     23 import java.io.FileDescriptor;
     24 import java.io.PrintWriter;
     25 
     26 /**
     27  * LockGuard is a mechanism to help detect lock inversions inside the system
     28  * server. It works by requiring each lock acquisition site to follow this
     29  * pattern:
     30  *
     31  * <pre>
     32  * synchronized (LockGuard.guard(lock)) {
     33  * }
     34  * </pre>
     35  *
     36  * <pre>
     37  * $ find services/ -name "*.java" -exec sed -i -r \
     38  *     's/synchronized.?\((.+?)\)/synchronized \(com.android.server.LockGuard.guard\(\1\)\)/' {} \;
     39  * </pre>
     40  *
     41  * The {@link #guard(Object)} method internally verifies that all locking is
     42  * done in a consistent order, and will log if any inversion is detected. For
     43  * example, if the calling thread is trying to acquire the
     44  * {@code ActivityManager} lock while holding the {@code PackageManager} lock,
     45  * it will yell.
     46  * <p>
     47  * This class requires no prior knowledge of locks or their ordering; it derives
     48  * all of this data at runtime. However, this means the overhead is
     49  * <em>substantial</em> and it should not be enabled by default. For example,
     50  * here are some benchmarked timings:
     51  * <ul>
     52  * <li>An unguarded synchronized block takes 40ns.
     53  * <li>A guarded synchronized block takes 50ns when disabled.
     54  * <li>A guarded synchronized block takes 460ns per lock checked when enabled.
     55  * </ul>
     56  */
     57 public class LockGuard {
     58     private static final String TAG = "LockGuard";
     59 
     60     private static ArrayMap<Object, LockInfo> sKnown = new ArrayMap<>(0, true);
     61 
     62     private static class LockInfo {
     63         /** Friendly label to describe this lock */
     64         public String label;
     65 
     66         /** Child locks that can be acquired while this lock is already held */
     67         public ArraySet<Object> children = new ArraySet<>(0, true);
     68     }
     69 
     70     private static LockInfo findOrCreateLockInfo(Object lock) {
     71         LockInfo info = sKnown.get(lock);
     72         if (info == null) {
     73             info = new LockInfo();
     74             info.label = "0x" + Integer.toHexString(System.identityHashCode(lock)) + " ["
     75                     + new Throwable().getStackTrace()[2].toString() + "]";
     76             sKnown.put(lock, info);
     77         }
     78         return info;
     79     }
     80 
     81     /**
     82      * Check if the calling thread is holding any locks in an inverted order.
     83      *
     84      * @param lock The lock the calling thread is attempting to acquire.
     85      */
     86     public static Object guard(Object lock) {
     87         // If we already hold this lock, ignore
     88         if (lock == null || Thread.holdsLock(lock)) return lock;
     89 
     90         // Check to see if we're already holding any child locks
     91         boolean triggered = false;
     92         final LockInfo info = findOrCreateLockInfo(lock);
     93         for (int i = 0; i < info.children.size(); i++) {
     94             final Object child = info.children.valueAt(i);
     95             if (child == null) continue;
     96 
     97             if (Thread.holdsLock(child)) {
     98                 Slog.w(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding "
     99                       + lockToString(child) + " while trying to acquire "
    100                       + lockToString(lock), new Throwable());
    101                 triggered = true;
    102             }
    103         }
    104 
    105         if (!triggered) {
    106             // If no trouble found above, record this lock as being a valid
    107             // child of all locks currently being held
    108             for (int i = 0; i < sKnown.size(); i++) {
    109                 final Object test = sKnown.keyAt(i);
    110                 if (test == null || test == lock) continue;
    111 
    112                 if (Thread.holdsLock(test)) {
    113                     sKnown.valueAt(i).children.add(lock);
    114                 }
    115             }
    116         }
    117 
    118         return lock;
    119     }
    120 
    121     /**
    122      * Report the given lock with a well-known label.
    123      */
    124     public static void installLock(Object lock, String label) {
    125         final LockInfo info = findOrCreateLockInfo(lock);
    126         info.label = label;
    127     }
    128 
    129     private static String lockToString(Object lock) {
    130         final LockInfo info = sKnown.get(lock);
    131         if (info != null) {
    132             return info.label;
    133         } else {
    134             return "0x" + Integer.toHexString(System.identityHashCode(lock));
    135         }
    136     }
    137 
    138     public static void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    139         for (int i = 0; i < sKnown.size(); i++) {
    140             final Object lock = sKnown.keyAt(i);
    141             final LockInfo info = sKnown.valueAt(i);
    142             pw.println("Lock " + lockToString(lock) + ":");
    143             for (int j = 0; j < info.children.size(); j++) {
    144                 pw.println("  Child " + lockToString(info.children.valueAt(j)));
    145             }
    146             pw.println();
    147         }
    148     }
    149 }
    150