main

square/leakcanary

Last updated at: 29/12/2023 09:51

RetryingHeapAnalyzer.kt

TLDR

The file RetryingHeapAnalyzer.kt is a Kotlin file that contains the implementation of the RetryingHeapAnalyzer class. This class is responsible for wrapping the InstrumentationHeapAnalyzer and retrying the analysis once if it fails.

Methods

analyze

The analyze method takes a heapDumpFile as a parameter and performs the analysis using the heapAnalyzer. It first creates a copy of the heapDumpFile in case of failure followed by success. It then sleeps for 2 seconds to allow the hprof to flush to the file system. After that, it calls the analyze method of heapAnalyzer to perform the analysis. If the analysis fails, it logs the failure and retries after sleeping for 10 seconds. If the second analysis succeeds, it returns the analysis result with additional metadata. If the second analysis also fails, it returns the failure result. If the analysis succeeds, it deletes the heap dump copy and returns the analysis result.

Class

RetryingHeapAnalyzer

The RetryingHeapAnalyzer class wraps the InstrumentationHeapAnalyzer and provides a mechanism to retry the analysis once if it fails. It has one constructor that takes an instance of InstrumentationHeapAnalyzer as a parameter. It contains the analyze method that performs the analysis and handles the retry logic.

package leakcanary.internal

import android.os.SystemClock
import android.util.Log
import java.io.File
import shark.HeapAnalysis
import shark.HeapAnalysisFailure
import shark.HeapAnalysisSuccess
import shark.SharkLog

/**
 * Wraps [InstrumentationHeapAnalyzer] and retries the analysis once if it fails.
 */
internal class RetryingHeapAnalyzer(
  private val heapAnalyzer: InstrumentationHeapAnalyzer
) {

  fun analyze(heapDumpFile: File): HeapAnalysis {
    // A copy that will be used in case of failure followed by success, to see if the file has changed.
    val heapDumpCopyFile = File(heapDumpFile.parent, "copy-${heapDumpFile.name}")
    heapDumpFile.copyTo(heapDumpCopyFile)
    // Giving an extra 2 seconds to flush the hprof to the file system. We've seen several cases
    // of corrupted hprof files and assume this could be a timing issue.
    SystemClock.sleep(2000)

    val heapAnalysis = heapAnalyzer.analyze(heapDumpFile)

    return if (heapAnalysis is HeapAnalysisFailure) {
      // Experience has shown that trying again often just works. Not sure why.
      SharkLog.d(heapAnalysis.exception) {
        "Heap Analysis failed, retrying in 10s in case the heap dump was not fully baked yet. " +
          "Copy of original heap dump available at ${heapDumpCopyFile.absolutePath}"
      }
      SystemClock.sleep(10000)
      heapAnalyzer.analyze(heapDumpFile).let {
        when (it) {
          is HeapAnalysisSuccess -> it.copy(
            metadata = it.metadata + mapOf(
              "previousFailureHeapDumpCopy" to heapDumpCopyFile.absolutePath,
              "previousFailureStacktrace" to Log.getStackTraceString(heapAnalysis.exception)
            )
          )
          is HeapAnalysisFailure -> it
        }
      }
    } else {
      // We don't need the copy after all.
      heapDumpCopyFile.delete()
      heapAnalysis
    }
  }
}