Commit a86b4ef1 authored by Michael Comella's avatar Michael Comella Committed by Michael Comella
Browse files

For 17920: add ThreadPenaltyDeathWithIgnoresListener, tests, helpers.

parent e9e06761
Loading
Loading
Loading
Loading
+73 −0
Original line number Diff line number Diff line
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.perf

import android.os.Build
import android.os.StrictMode
import android.os.strictmode.Violation
import androidx.annotation.RequiresApi
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.utils.ManufacturerCodes

private const val FCQN_EDM_STORAGE_PROVIDER_BASE = "com.android.server.enterprise.storage.EdmStorageProviderBase"

/**
 * A [StrictMode.OnThreadViolationListener] that recreates
 * [StrictMode.ThreadPolicy.Builder.penaltyDeath] but will ignore some violations. For example,
 * sometimes OEMs will add code that violates StrictMode so we can ignore them here instead of
 * cluttering up our code with resetAfter.
 *
 * This class can only be used with Android P+ so we'd have to implement workarounds if the
 * violations we want to ignore affect older devices.
 */
@RequiresApi(Build.VERSION_CODES.P)
class ThreadPenaltyDeathWithIgnoresListener(
    private val logger: Logger = Performance.logger
) : StrictMode.OnThreadViolationListener {

    override fun onThreadViolation(violation: Violation?) {
        if (violation == null) return

        // Unfortunately, this method gets called many (~5+) times with the same violation so we end
        // up logging/throwing redundantly.
        if (shouldViolationBeIgnored(violation)) {
            logger.debug("Ignoring StrictMode ThreadPolicy violation", violation)
        } else {
            penaltyDeath(violation)
        }
    }

    @Suppress("TooGenericExceptionThrown") // we throw what StrictMode's penaltyDeath throws.
    private fun penaltyDeath(violation: Violation) {
        throw RuntimeException("StrictMode ThreadPolicy violation", violation)
    }

    private fun shouldViolationBeIgnored(violation: Violation): Boolean =
        isSamsungLgEdmStorageProviderStartupViolation(violation)

    private fun isSamsungLgEdmStorageProviderStartupViolation(violation: Violation): Boolean {
        // Root issue: https://github.com/mozilla-mobile/fenix/issues/17920
        //
        // This fix may address the issues seen in this bug:
        // https://github.com/mozilla-mobile/fenix/issues/15430
        // So we might be able to back out the changes made there. However, I don't have a device to
        // test so I didn't bother.
        //
        // This issue occurs on the Galaxy S10e, Galaxy A50, Note 10, and LG G7 FIT but not the S7:
        // I'm guessing it's just a problem on recent Samsungs and LGs so it's okay being in this P+
        // listener.
        if (!ManufacturerCodes.isSamsung && !ManufacturerCodes.isLG) {
            return false
        }

        // To ignore this warning, we can inspect the stack trace. There are no parts of the
        // violation stack trace that are clearly unique to this violation but
        // EdmStorageProviderBase doesn't appear in Android code search so we match against it.
        // This class may be used in other violations that we're capable of fixing but this
        // code may ignore them. I think it's okay - we keep this code simple and if it was a serious
        // issue, we'd catch it on other manufacturers.
        return violation.stackTrace.any { it.className == FCQN_EDM_STORAGE_PROVIDER_BASE }
    }
}
+39 −0
Original line number Diff line number Diff line
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.helpers

/**
 * A collection of test helper functions for manipulating stack traces.
 */
object StackTraces {

    /**
     * Gets a stack trace from logcat output. To use this, you should remove the name of the
     * Exception or "Caused by" lines causing the problem and only use the stack trace lines below
     * it. See src/test/resources/EdmStorageProviderBaseLogcat.txt for an example.
     */
    fun getStackTraceFromLogcat(logcatResourcePath: String): Array<StackTraceElement> {
        val logcat = javaClass.classLoader!!.getResource(logcatResourcePath).readText()
        val lines = logcat.split('\n').filter(String::isNotBlank)
        return lines.map(::logcatLineToStackTraceElement).toTypedArray()
    }

    private fun logcatLineToStackTraceElement(line: String): StackTraceElement {
        // Expected format:
        // 02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1556)

        // Expected: android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk
        val methodInfo = line.substringBefore('(').substringAfterLast(' ')
        val methodName = methodInfo.substringAfterLast('.')
        val declaringClass = methodInfo.substringBeforeLast('.')

        // Expected: StrictMode.java:1556
        val fileInfo = line.substringAfter('(').substringBefore(')')
        val fileName = fileInfo.substringBefore(':')
        val lineNumber = fileInfo.substringAfter(':').toInt()

        return StackTraceElement(declaringClass, methodName, fileName, lineNumber)
    }
}
+78 −0
Original line number Diff line number Diff line
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.perf

import android.os.strictmode.Violation
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.mockkObject
import io.mockk.unmockkAll
import io.mockk.verify
import mozilla.components.support.base.log.logger.Logger
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.helpers.StackTraces
import org.mozilla.fenix.utils.ManufacturerCodes

class ThreadPenaltyDeathWithIgnoresListenerTest {

    @RelaxedMockK private lateinit var logger: Logger
    private lateinit var listener: ThreadPenaltyDeathWithIgnoresListener
    @MockK private lateinit var violation: Violation
    private lateinit var stackTrace: Array<StackTraceElement>

    @Before
    fun setUp() {
        MockKAnnotations.init(this)

        listener = ThreadPenaltyDeathWithIgnoresListener(logger)

        stackTrace = emptyArray()
        every { violation.stackTrace } answers { stackTrace }
    }

    @After
    fun tearDown() {
        unmockkAll()
    }

    @Test(expected = RuntimeException::class)
    fun `WHEN provided an arbitrary violation that does not conflict with ignores THEN we throw an exception`() {
        stackTrace = Exception().stackTrace
        listener.onThreadViolation(violation)
    }

    @Test
    fun `GIVEN we're on a Samsung WHEN provided the EdmStorageProvider violation THEN it will be ignored and logged`() {
        mockkObject(ManufacturerCodes)
        every { ManufacturerCodes.isSamsung } returns true

        every { violation.stackTrace } returns getEdmStorageProviderStackTrace()
        listener.onThreadViolation(violation)

        verify { logger.debug("Ignoring StrictMode ThreadPolicy violation", violation) }
    }

    @Test(expected = RuntimeException::class)
    fun `GIVEN we're not on a Samsung or LG WHEN provided the EdmStorageProvider violation THEN we throw an exception`() {
        mockkObject(ManufacturerCodes)
        every { ManufacturerCodes.isSamsung } returns false
        every { ManufacturerCodes.isLG } returns false

        every { violation.stackTrace } returns getEdmStorageProviderStackTrace()
        listener.onThreadViolation(violation)
    }

    @Test
    fun `WHEN violation is null THEN we don't throw an exception`() {
        listener.onThreadViolation(null)
    }

    private fun getEdmStorageProviderStackTrace() =
        StackTraces.getStackTraceFromLogcat("EdmStorageProviderBaseLogcat.txt")
}
+20 −0
Original line number Diff line number Diff line
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1556)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at android.database.sqlite.SQLiteConnection.applyBlockGuardPolicy(SQLiteConnection.java:1524)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at android.database.sqlite.SQLiteConnection.executeForCursorWindow(SQLiteConnection.java:1193)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:838)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:165)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:152)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:232)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:271)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at com.android.server.enterprise.storage.EdmStorageProviderBase.getValues(EdmStorageProviderBase.java:388)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at com.android.server.enterprise.storage.EdmStorageProviderBase.getValuesList(EdmStorageProviderBase.java:1104)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at com.android.server.enterprise.storage.EdmStorageProvider.getValuesList(EdmStorageProvider.java:300)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at com.android.server.enterprise.application.ApplicationPolicy.getApplicationStateEnabledAsUser(ApplicationPolicy.java:3745)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at com.android.server.enterprise.application.ApplicationPolicy.getApplicationStateEnabledAsUser(ApplicationPolicy.java:3714)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at com.android.server.pm.PackageManagerService.setEnabledSetting(PackageManagerService.java:26031)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at com.android.server.pm.PackageManagerService.setComponentEnabledSetting(PackageManagerService.java:26007)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:3774)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at com.android.server.pm.PackageManagerService.onTransact(PackageManagerService.java:5145)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at android.os.Binder.execTransactInternal(Binder.java:1056)
02-08 10:56:02.185 21990 21990 E AndroidRuntime: 	at android.os.Binder.execTransact(Binder.java:1029)