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