1 package leakcanary.internal
2 
3 import android.os.SystemClock
4 import android.util.Log
5 import java.io.File
6 import shark.HeapAnalysis
7 import shark.HeapAnalysisFailure
8 import shark.HeapAnalysisSuccess
9 import shark.SharkLog
10 
11 /**
12  * Wraps [InstrumentationHeapAnalyzer] and retries the analysis once if it fails.
13  */
14 internal class RetryingHeapAnalyzer(
15   private val heapAnalyzer: InstrumentationHeapAnalyzer
16 ) {
17 
analyzenull18   fun analyze(heapDumpFile: File): HeapAnalysis {
19     // A copy that will be used in case of failure followed by success, to see if the file has changed.
20     val heapDumpCopyFile = File(heapDumpFile.parent, "copy-${heapDumpFile.name}")
21     heapDumpFile.copyTo(heapDumpCopyFile)
22     // Giving an extra 2 seconds to flush the hprof to the file system. We've seen several cases
23     // of corrupted hprof files and assume this could be a timing issue.
24     SystemClock.sleep(2000)
25 
26     val heapAnalysis = heapAnalyzer.analyze(heapDumpFile)
27 
28     return if (heapAnalysis is HeapAnalysisFailure) {
29       // Experience has shown that trying again often just works. Not sure why.
30       SharkLog.d(heapAnalysis.exception) {
31         "Heap Analysis failed, retrying in 10s in case the heap dump was not fully baked yet. " +
32           "Copy of original heap dump available at ${heapDumpCopyFile.absolutePath}"
33       }
34       SystemClock.sleep(10000)
35       heapAnalyzer.analyze(heapDumpFile).let {
36         when (it) {
37           is HeapAnalysisSuccess -> it.copy(
38             metadata = it.metadata + mapOf(
39               "previousFailureHeapDumpCopy" to heapDumpCopyFile.absolutePath,
40               "previousFailureStacktrace" to Log.getStackTraceString(heapAnalysis.exception)
41             )
42           )
43           is HeapAnalysisFailure -> it
44         }
45       }
46     } else {
47       // We don't need the copy after all.
48       heapDumpCopyFile.delete()
49       heapAnalysis
50     }
51   }
52 }
53