Home | History | Annotate | Download | only in inspector
      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.bugreport.inspector;
     18 
     19 import com.android.bugreport.anr.Anr;
     20 import com.android.bugreport.stacks.ProcessSnapshot;
     21 import com.android.bugreport.stacks.JavaStackFrameSnapshot;
     22 import com.android.bugreport.stacks.LockSnapshot;
     23 import com.android.bugreport.stacks.StackFrameSnapshot;
     24 import com.android.bugreport.stacks.ThreadSnapshot;
     25 import com.android.bugreport.stacks.VmTraces;
     26 
     27 import java.util.Map;
     28 import java.util.Set;
     29 import java.util.TreeSet;
     30 import java.util.HashMap;
     31 
     32 /**
     33  * Class to inspect an Anr object and determine which, if any threads are
     34  * in a cycle of lcoks and binder transactions.
     35  */
     36 public class DeadlockDetector {
     37 
     38     /**
     39      * Entry in our growing list of involved threads.
     40      */
     41     private static class ThreadRecord implements Comparable<ThreadRecord> {
     42         public ProcessSnapshot process;
     43         public ThreadSnapshot thread;
     44 
     45         public ThreadRecord(ProcessSnapshot process, ThreadSnapshot thread) {
     46             this.process = process;
     47             this.thread = thread;
     48         }
     49 
     50         public boolean equals(ThreadRecord that) {
     51             return this.process == that.process
     52                     && this.thread == that.thread;
     53         }
     54 
     55         @Override
     56         public int hashCode() {
     57             int hash = 7;
     58             hash = hash * 31 + this.process.hashCode();
     59             hash = hash * 31 + this.thread.hashCode();
     60             return hash;
     61         }
     62 
     63         public int compareTo(ThreadRecord that) {
     64             int cmp = this.process.compareTo(that.process);
     65             if (cmp != 0) {
     66                 return cmp;
     67             }
     68             return this.thread.compareTo(that.thread);
     69         }
     70     }
     71 
     72     /**
     73      * Entry in our growing list of involved threads.
     74      */
     75     private static class LockRecord implements Comparable<LockRecord> {
     76         public ProcessSnapshot process;
     77         public LockSnapshot lock;
     78 
     79         public LockRecord(ProcessSnapshot process, LockSnapshot lock) {
     80             this.process = process;
     81             this.lock = lock;
     82         }
     83 
     84         public boolean equals(LockRecord that) {
     85             return this.process == that.process
     86                     && (this.lock.address == that.lock.address
     87                             || (this.lock.address != null && that.lock.address != null
     88                                 && this.lock.address.equals(that.lock.address)));
     89         }
     90 
     91         @Override
     92         public int hashCode() {
     93             int hash = 7;
     94             hash = hash * 31 + this.process.hashCode();
     95             if (this.lock.address != null) {
     96                 hash = hash * 31 + this.lock.address.hashCode();
     97             }
     98             return hash;
     99         }
    100 
    101         public int compareTo(LockRecord that) {
    102             int cmp = this.process.compareTo(that.process);
    103             if (cmp != 0) {
    104                 return cmp;
    105             }
    106             if (this.lock.address == that.lock.address) {
    107                 return 0;
    108             } else if (this.lock.address == null) {
    109                 return -1;
    110             } else if (that.lock.address == null) {
    111                 return 1;
    112             } else {
    113                 return this.lock.address.compareTo(that.lock.address);
    114             }
    115         }
    116     }
    117 
    118     /**
    119      * Detect any thread cycles that are affecting the main thread of the given pid.
    120      */
    121     public static Set<ProcessSnapshot> detectDeadlocks(VmTraces vmTraces, int pid) {
    122         final boolean dump = false;
    123 
    124         final TreeSet<ThreadRecord> involvedThreads = new TreeSet<ThreadRecord>();
    125 
    126         final TreeSet<LockRecord> locksToVisit = new TreeSet<LockRecord>();
    127         final TreeSet<LockRecord> locksVisited = new TreeSet<LockRecord>();
    128 
    129         // Seed the traversal with the locks held by the main thread.
    130         final ProcessSnapshot offendingProcess = vmTraces.getProcess(pid);
    131         if (offendingProcess == null) {
    132             return new TreeSet<ProcessSnapshot>();
    133         }
    134         final ThreadSnapshot offendingThread = offendingProcess.getThread("main");
    135         if (offendingThread == null) {
    136             return new TreeSet<ProcessSnapshot>();
    137         }
    138         addLockRecordsForThread(locksToVisit, locksVisited, offendingProcess, offendingThread);
    139 
    140         if (dump) {
    141             if (offendingThread.outboundBinderPackage != null
    142                     || offendingThread.outboundBinderClass != null
    143                     || offendingThread.inboundBinderMethod != null) {
    144                 System.out.println("Offending thread:");
    145                 System.out.print("  pid=" + offendingProcess.pid + " \"" + offendingThread.name
    146                         + "\" (tid=" + offendingThread.tid + ")");
    147                 if (offendingThread.outboundBinderClass != null) {
    148                     System.out.print(" outbound=" + offendingThread.outboundBinderPackage + "."
    149                             + offendingThread.outboundBinderClass
    150                             + "." + offendingThread.outboundBinderMethod);
    151                 }
    152                 if (offendingThread.inboundBinderClass != null) {
    153                     System.out.print(" inbound=" + offendingThread.inboundBinderPackage + "."
    154                             + offendingThread.inboundBinderClass
    155                             + "." + offendingThread.inboundBinderMethod);
    156                 }
    157                 System.out.println();
    158             }
    159         }
    160 
    161         if (locksToVisit.size() == 0) {
    162             // There weren't any locks. Just stop here.
    163             return new TreeSet<ProcessSnapshot>();
    164         }
    165 
    166         involvedThreads.add(new ThreadRecord(offendingProcess, offendingThread));
    167 
    168         // Terminate when we stop finding new locks to look at. We will terminate
    169         // eventually because there are a finite number of locks in the system.
    170         while (locksToVisit.size() > 0) {
    171             final LockRecord lr = locksToVisit.pollFirst();
    172 
    173             // Don't allow ourselves to re-add this lock
    174             locksVisited.add(lr);
    175 
    176             // Find all the threads holding this lock.
    177             for (ThreadSnapshot thread: lr.process.threads) {
    178                 final Map<String,LockSnapshot> locks = thread.locks;
    179                 if (locks.containsKey(lr.lock.address)) {
    180                     if (dump) {
    181                         System.out.println("Thread " + thread.tid
    182                                 + " contains lock " + lr.lock.address);
    183                     }
    184                     // This thread is holding the lock (or trying to).
    185                     // Enqeue its other locks that we haven't already done.
    186                     addLockRecordsForThread(locksToVisit, locksVisited, lr.process, thread);
    187                     involvedThreads.add(new ThreadRecord(lr.process, thread));
    188                 }
    189             }
    190         }
    191 
    192         final HashMap<Integer,ProcessSnapshot> results = new HashMap<Integer,ProcessSnapshot>();
    193 
    194         // Add the process / thread pairs into the results
    195         if (dump) System.out.println("Involved threads:");
    196         for (ThreadRecord tr: involvedThreads) {
    197             if (dump) {
    198                 System.out.print("  pid=" + tr.process.pid + " \"" + tr.thread.name
    199                         + "\" (tid=" + tr.thread.tid + ")");
    200                 if (tr.thread.outboundBinderClass != null) {
    201                     System.out.print(" outbound=" + tr.thread.outboundBinderPackage + "."
    202                             + tr.thread.outboundBinderClass + "." + tr.thread.outboundBinderMethod);
    203                 }
    204                 if (tr.thread.inboundBinderClass != null) {
    205                     System.out.print(" inbound=" + tr.thread.inboundBinderPackage + "."
    206                             + tr.thread.inboundBinderClass + "." + tr.thread.inboundBinderMethod);
    207                 }
    208                 System.out.println();
    209             }
    210 
    211             ProcessSnapshot cloneProcess = results.get(tr.process.pid);
    212             if (cloneProcess == null) {
    213                 cloneProcess = tr.process.clone();
    214                 cloneProcess.threads.clear();
    215                 results.put(tr.process.pid, cloneProcess);
    216             }
    217             cloneProcess.threads.add(tr.thread);
    218         }
    219         if (dump) {
    220             System.out.println("Involved locks:");
    221             for (LockRecord lr: locksVisited) {
    222                 System.out.println("  pid=" + lr.process.pid + " " + lr.lock.packageName
    223                         + "." + lr.lock.className + " - " + lr.lock.address);
    224             }
    225         }
    226 
    227         return new TreeSet<ProcessSnapshot>(results.values());
    228     }
    229 
    230     /**
    231      * Add the LockRecords for the locks held by the thread to toVisit, unless
    232      * they're already in alreadyVisited.
    233      */
    234     private static void addLockRecordsForThread(TreeSet<LockRecord> toVisit,
    235             TreeSet<LockRecord> alreadyVisited, ProcessSnapshot process, ThreadSnapshot thread) {
    236         for (LockSnapshot lock: thread.locks.values()) {
    237             final LockRecord next = new LockRecord(process, lock);
    238             if (!alreadyVisited.contains(next)) {
    239                 toVisit.add(next);
    240             }
    241         }
    242     }
    243 }
    244