15278 detekt rule runblocking (#15942)
* For #15278: added CoroutineManager to count runBlocking calls * For #15278: Added actual detekt rule for runblocking and its config to the yaml * For #15278: Added unit test for RunblockingCounter * For #15278: renamed StrictModeStartupSuppressionCountTest.kt to PerformanceStartupTest.kt and added runBlockingCount test * Lint fix * For #15278: made runblocking a Long to prevent overflow * For #15278: fixed MozRunblocking name, description and moved RunBlockingCounter to perf package * For #15278:Renamed MozillaRunblockingCheck to MozillaRunBlockingCheck * For #15278: Added setup for unit test, since it failed without restting counter * For #15278: Fixed naming for RunBlocking lint check * For #15278: removed changes made to test to use runBlockingIncrement * For #15728: added test exclusion for runBlocking check * For #15278: changed null check and added Synchronized to count setter * For #15278: fix for nits * For #15278: added StartupExcessiveResourceUseTest to CODEOWNERS * For #15278: fixed for nits * For #15278: Moved increment function to extension function and fixed indentation * For #15278: Added tests for Atomic Integer extension and nit fix
This commit is contained in:
parent
d46fc7b142
commit
7b1af41b40
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -50,6 +50,8 @@
|
||||||
*Application.kt @mozilla-mobile/Performance
|
*Application.kt @mozilla-mobile/Performance
|
||||||
*StrictMode*kt @mozilla-mobile/Performance
|
*StrictMode*kt @mozilla-mobile/Performance
|
||||||
*ConstraintLayoutPerfDetector* @mozilla-mobile/Performance
|
*ConstraintLayoutPerfDetector* @mozilla-mobile/Performance
|
||||||
|
*MozillaRunBlockingCheck.kt @mozilla-mobile/Performance
|
||||||
|
*StartupExcessiveResourceUseTest.kt @mozilla-mobile/Performance
|
||||||
|
|
||||||
# We want to be aware of new features behind flags as well as features
|
# We want to be aware of new features behind flags as well as features
|
||||||
# about to be enabled.
|
# about to be enabled.
|
||||||
|
|
|
@ -9,13 +9,15 @@ import androidx.test.uiautomator.UiDevice
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.perf.RunBlockingCounter
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
||||||
|
|
||||||
// PLEASE CONSULT WITH PERF TEAM BEFORE CHANGING THIS VALUE.
|
// PLEASE CONSULT WITH PERF TEAM BEFORE CHANGING THIS VALUE.
|
||||||
private const val EXPECTED_SUPPRESSION_COUNT = 11
|
private const val EXPECTED_SUPPRESSION_COUNT = 11
|
||||||
|
|
||||||
private const val FAILURE_MSG = """StrictMode startup suppression count does not match expected count.
|
private const val EXPECTED_RUNBLOCKING_COUNT = 2
|
||||||
|
private const val STRICTMODE_FAILURE_MSG = """StrictMode startup suppression count does not match expected count.
|
||||||
|
|
||||||
If this PR removed code that suppressed StrictMode, great! Please decrement the suppression count.
|
If this PR removed code that suppressed StrictMode, great! Please decrement the suppression count.
|
||||||
|
|
||||||
|
@ -28,31 +30,53 @@ private const val FAILURE_MSG = """StrictMode startup suppression count does not
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
private const val RUNBLOCKING_FAILURE_MSG = """RunblockingCounter startup count does not match expected count.
|
||||||
|
|
||||||
|
If this PR removed code that removed a RunBlocking call, great! Please decrement the suppression count.
|
||||||
|
|
||||||
|
Did this PR add or call code that incremented the RunBlockingCounter?
|
||||||
|
Did you know that using runBlocking can have negative perfomance implications?
|
||||||
|
|
||||||
|
If so, please do your best to implement a solution without blocking the main thread.
|
||||||
|
Please consult the perf team if you have questions or believe that using runBlocking
|
||||||
|
is the optimal solution.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A performance test to limit the number of StrictMode suppressions on startup.
|
* A performance test to limit the number of StrictMode suppressions and number of runBlocking used
|
||||||
|
* on startup.
|
||||||
|
*
|
||||||
* This test was written by the perf team.
|
* This test was written by the perf team.
|
||||||
*
|
*
|
||||||
* StrictMode detects main thread IO, which is often indicative of a performance issue.
|
* StrictMode detects main thread IO, which is often indicative of a performance issue.
|
||||||
* It's easy to suppress StrictMode so we wrote a test to ensure we have a discussion
|
* It's easy to suppress StrictMode so we wrote a test to ensure we have a discussion
|
||||||
* if the StrictMode count changes. The perf team is code owners for this file so they
|
* if the StrictMode count changes.
|
||||||
* should be notified when the count is modified.
|
*
|
||||||
|
* RunBlocking is mostly used to return values to a thread from a coroutine. However, if that
|
||||||
|
* coroutine takes too long, it can lead that thread to block every other operations.
|
||||||
|
*
|
||||||
|
* The perf team is code owners for this file so they should be notified when the count is modified.
|
||||||
*
|
*
|
||||||
* IF YOU UPDATE THE TEST NAME, UPDATE CODE OWNERS.
|
* IF YOU UPDATE THE TEST NAME, UPDATE CODE OWNERS.
|
||||||
*/
|
*/
|
||||||
class StrictModeStartupSuppressionCountTest {
|
class StartupExcessiveResourceUseTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val activityTestRule = HomeActivityTestRule(skipOnboarding = true)
|
val activityTestRule = HomeActivityTestRule(skipOnboarding = true)
|
||||||
|
|
||||||
private val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
private val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun verifyStrictModeSuppressionCount() {
|
fun verifyRunBlockingAndStrictModeSuppresionCount() {
|
||||||
uiDevice.waitForIdle() // wait for async UI to load.
|
uiDevice.waitForIdle() // wait for async UI to load.
|
||||||
|
|
||||||
// This might cause intermittents: at an arbitrary point after start up (such as the visual
|
// This might cause intermittents: at an arbitrary point after start up (such as the visual
|
||||||
// completeness queue), we might run code on the main thread that suppresses StrictMode,
|
// completeness queue), we might run code on the main thread that suppresses StrictMode,
|
||||||
// causing this number to fluctuate depending on device speed. We'll deal with it if it occurs.
|
// causing this number to fluctuate depending on device speed. We'll deal with it if it occurs.
|
||||||
val actual = activityTestRule.activity.components.strictMode.suppressionCount.toInt()
|
val actualSuppresionCount = activityTestRule.activity.components.strictMode.suppressionCount.toInt()
|
||||||
assertEquals(FAILURE_MSG, EXPECTED_SUPPRESSION_COUNT, actual)
|
val actualRunBlocking = RunBlockingCounter.count.get()
|
||||||
|
|
||||||
|
assertEquals(STRICTMODE_FAILURE_MSG, EXPECTED_SUPPRESSION_COUNT, actualSuppresionCount)
|
||||||
|
assertEquals(RUNBLOCKING_FAILURE_MSG, EXPECTED_RUNBLOCKING_COUNT, actualRunBlocking)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,7 +19,6 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import mozilla.appservices.Megazord
|
import mozilla.appservices.Megazord
|
||||||
import mozilla.components.browser.session.Session
|
import mozilla.components.browser.session.Session
|
||||||
import mozilla.components.browser.state.action.SystemAction
|
import mozilla.components.browser.state.action.SystemAction
|
||||||
|
@ -44,6 +43,7 @@ import org.mozilla.fenix.components.metrics.MetricServiceType
|
||||||
import org.mozilla.fenix.ext.settings
|
import org.mozilla.fenix.ext.settings
|
||||||
import org.mozilla.fenix.perf.StorageStatsMetrics
|
import org.mozilla.fenix.perf.StorageStatsMetrics
|
||||||
import org.mozilla.fenix.perf.StartupTimeline
|
import org.mozilla.fenix.perf.StartupTimeline
|
||||||
|
import org.mozilla.fenix.perf.runBlockingIncrement
|
||||||
import org.mozilla.fenix.push.PushFxaIntegration
|
import org.mozilla.fenix.push.PushFxaIntegration
|
||||||
import org.mozilla.fenix.push.WebPushEngineIntegration
|
import org.mozilla.fenix.push.WebPushEngineIntegration
|
||||||
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
|
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
|
||||||
|
@ -137,7 +137,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
|
||||||
// to invoke parts of itself that require complete megazord initialization
|
// to invoke parts of itself that require complete megazord initialization
|
||||||
// before that process completes, we wait here, if necessary.
|
// before that process completes, we wait here, if necessary.
|
||||||
if (!megazordSetup.isCompleted) {
|
if (!megazordSetup.isCompleted) {
|
||||||
runBlocking { megazordSetup.await(); }
|
runBlockingIncrement { megazordSetup.await() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
|
|
||||||
package org.mozilla.fenix.components.history
|
package org.mozilla.fenix.components.history
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import mozilla.components.concept.storage.HistoryStorage
|
import mozilla.components.concept.storage.HistoryStorage
|
||||||
import mozilla.components.concept.storage.VisitInfo
|
import mozilla.components.concept.storage.VisitInfo
|
||||||
import mozilla.components.concept.storage.VisitType
|
import mozilla.components.concept.storage.VisitType
|
||||||
|
import org.mozilla.fenix.perf.runBlockingIncrement
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Interface for providing a paginated list of [VisitInfo]
|
* An Interface for providing a paginated list of [VisitInfo]
|
||||||
|
@ -32,7 +32,7 @@ fun HistoryStorage.createSynchronousPagedHistoryProvider(): PagedHistoryProvider
|
||||||
numberOfItems: Long,
|
numberOfItems: Long,
|
||||||
onComplete: (List<VisitInfo>) -> Unit
|
onComplete: (List<VisitInfo>) -> Unit
|
||||||
) {
|
) {
|
||||||
runBlocking {
|
runBlockingIncrement {
|
||||||
val history = getVisitsPaginated(
|
val history = getVisitsPaginated(
|
||||||
offset,
|
offset,
|
||||||
numberOfItems,
|
numberOfItems,
|
||||||
|
|
|
@ -12,7 +12,6 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleObserver
|
import androidx.lifecycle.LifecycleObserver
|
||||||
import androidx.lifecycle.OnLifecycleEvent
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import mozilla.components.support.utils.SafeIntent
|
import mozilla.components.support.utils.SafeIntent
|
||||||
import org.mozilla.fenix.components.metrics.Event.AppAllStartup
|
import org.mozilla.fenix.components.metrics.Event.AppAllStartup
|
||||||
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Source
|
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Source
|
||||||
|
@ -25,6 +24,7 @@ import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.ERROR
|
||||||
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.HOT
|
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.HOT
|
||||||
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.COLD
|
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.COLD
|
||||||
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.WARM
|
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.WARM
|
||||||
|
import org.mozilla.fenix.perf.runBlockingIncrement
|
||||||
import java.lang.reflect.Modifier.PRIVATE
|
import java.lang.reflect.Modifier.PRIVATE
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,7 +186,7 @@ class AppStartupTelemetry(
|
||||||
* the application potentially closes.
|
* the application potentially closes.
|
||||||
*/
|
*/
|
||||||
fun onStop() {
|
fun onStop() {
|
||||||
runBlocking {
|
runBlockingIncrement {
|
||||||
recordMetric()
|
recordMetric()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import mozilla.components.browser.search.SearchEngine
|
import mozilla.components.browser.search.SearchEngine
|
||||||
import mozilla.components.browser.search.provider.AssetsSearchEngineProvider
|
import mozilla.components.browser.search.provider.AssetsSearchEngineProvider
|
||||||
import mozilla.components.browser.search.provider.SearchEngineList
|
import mozilla.components.browser.search.provider.SearchEngineList
|
||||||
|
@ -27,6 +26,7 @@ import org.mozilla.fenix.BuildConfig
|
||||||
import org.mozilla.fenix.Config
|
import org.mozilla.fenix.Config
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.settings
|
import org.mozilla.fenix.ext.settings
|
||||||
|
import org.mozilla.fenix.perf.runBlockingIncrement
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@SuppressWarnings("TooManyFunctions")
|
@SuppressWarnings("TooManyFunctions")
|
||||||
|
@ -141,7 +141,7 @@ open class FenixSearchEngineProvider(
|
||||||
* are readily available throughout the app. Includes all installed engines, both
|
* are readily available throughout the app. Includes all installed engines, both
|
||||||
* default and custom
|
* default and custom
|
||||||
*/
|
*/
|
||||||
fun installedSearchEngines(context: Context): SearchEngineList = runBlocking {
|
fun installedSearchEngines(context: Context): SearchEngineList = runBlockingIncrement {
|
||||||
val installedIdentifiers = installedSearchEngineIdentifiers(context)
|
val installedIdentifiers = installedSearchEngineIdentifiers(context)
|
||||||
val defaultList = searchEngines.await()
|
val defaultList = searchEngines.await()
|
||||||
|
|
||||||
|
@ -161,15 +161,15 @@ open class FenixSearchEngineProvider(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allSearchEngineIdentifiers() = runBlocking {
|
fun allSearchEngineIdentifiers() = runBlockingIncrement {
|
||||||
loadedSearchEngines.await().list.map { it.identifier }
|
loadedSearchEngines.await().list.map { it.identifier }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun uninstalledSearchEngines(context: Context): SearchEngineList = runBlocking {
|
fun uninstalledSearchEngines(context: Context): SearchEngineList = runBlockingIncrement {
|
||||||
val installedIdentifiers = installedSearchEngineIdentifiers(context)
|
val installedIdentifiers = installedSearchEngineIdentifiers(context)
|
||||||
val engineList = loadedSearchEngines.await()
|
val engineList = loadedSearchEngines.await()
|
||||||
|
|
||||||
return@runBlocking engineList.copy(
|
return@runBlockingIncrement engineList.copy(
|
||||||
list = engineList.list.filterNot { installedIdentifiers.contains(it.identifier) }
|
list = engineList.list.filterNot { installedIdentifiers.contains(it.identifier) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -182,7 +182,7 @@ open class FenixSearchEngineProvider(
|
||||||
context: Context,
|
context: Context,
|
||||||
searchEngine: SearchEngine,
|
searchEngine: SearchEngine,
|
||||||
isCustom: Boolean = false
|
isCustom: Boolean = false
|
||||||
) = runBlocking {
|
) = runBlockingIncrement {
|
||||||
if (isCustom) {
|
if (isCustom) {
|
||||||
val searchUrl = searchEngine.getSearchTemplate()
|
val searchUrl = searchEngine.getSearchTemplate()
|
||||||
CustomSearchEngineStore.addSearchEngine(context, searchEngine.name, searchUrl)
|
CustomSearchEngineStore.addSearchEngine(context, searchEngine.name, searchUrl)
|
||||||
|
@ -201,7 +201,7 @@ open class FenixSearchEngineProvider(
|
||||||
context: Context,
|
context: Context,
|
||||||
searchEngine: SearchEngine,
|
searchEngine: SearchEngine,
|
||||||
isCustom: Boolean = false
|
isCustom: Boolean = false
|
||||||
) = runBlocking {
|
) = runBlockingIncrement {
|
||||||
if (isCustom) {
|
if (isCustom) {
|
||||||
CustomSearchEngineStore.removeSearchEngine(context, searchEngine.identifier)
|
CustomSearchEngineStore.removeSearchEngine(context, searchEngine.identifier)
|
||||||
reload()
|
reload()
|
||||||
|
|
|
@ -9,7 +9,6 @@ import android.content.Intent
|
||||||
import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
|
import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import mozilla.components.browser.session.Session
|
import mozilla.components.browser.session.Session
|
||||||
import mozilla.components.browser.session.SessionManager
|
import mozilla.components.browser.session.SessionManager
|
||||||
import mozilla.components.browser.state.state.CustomTabConfig
|
import mozilla.components.browser.state.state.CustomTabConfig
|
||||||
|
@ -30,6 +29,7 @@ import mozilla.components.support.utils.toSafeIntent
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.perf.runBlockingIncrement
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ class FennecWebAppIntentProcessor(
|
||||||
val url = safeIntent.dataString
|
val url = safeIntent.dataString
|
||||||
|
|
||||||
return if (!url.isNullOrEmpty() && matches(intent)) {
|
return if (!url.isNullOrEmpty() && matches(intent)) {
|
||||||
val webAppManifest = runBlocking { loadManifest(safeIntent, url) }
|
val webAppManifest = runBlockingIncrement { loadManifest(safeIntent, url) }
|
||||||
|
|
||||||
val session = Session(url, private = false, source = SessionState.Source.HOME_SCREEN)
|
val session = Session(url, private = false, source = SessionState.Source.HOME_SCREEN)
|
||||||
session.webAppManifest = webAppManifest
|
session.webAppManifest = webAppManifest
|
||||||
|
|
19
app/src/main/java/org/mozilla/fenix/ext/AtomicInteger.kt
Normal file
19
app/src/main/java/org/mozilla/fenix/ext/AtomicInteger.kt
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/* 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.ext
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increases an AtomicInteger safely.
|
||||||
|
*/
|
||||||
|
fun AtomicInteger.getAndIncrementNoOverflow() {
|
||||||
|
var prev: Int
|
||||||
|
var next: Int
|
||||||
|
do {
|
||||||
|
prev = this.get()
|
||||||
|
next = if (prev == Integer.MAX_VALUE) prev else prev + 1
|
||||||
|
} while (!this.compareAndSet(prev, next))
|
||||||
|
}
|
|
@ -11,13 +11,13 @@ import android.util.Patterns
|
||||||
import android.webkit.URLUtil
|
import android.webkit.URLUtil
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import mozilla.components.concept.fetch.Client
|
import mozilla.components.concept.fetch.Client
|
||||||
import mozilla.components.concept.fetch.Request
|
import mozilla.components.concept.fetch.Request
|
||||||
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
||||||
import mozilla.components.lib.publicsuffixlist.ext.urlToTrimmedHost
|
import mozilla.components.lib.publicsuffixlist.ext.urlToTrimmedHost
|
||||||
import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes
|
import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes
|
||||||
|
import org.mozilla.fenix.perf.runBlockingIncrement
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.IDN
|
import java.net.IDN
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
@ -92,7 +92,8 @@ private fun Uri.isIpv6(): Boolean {
|
||||||
/**
|
/**
|
||||||
* Trim a host's prefix and suffix
|
* Trim a host's prefix and suffix
|
||||||
*/
|
*/
|
||||||
fun String.urlToTrimmedHost(publicSuffixList: PublicSuffixList): String = runBlocking {
|
fun String.urlToTrimmedHost(publicSuffixList: PublicSuffixList): String =
|
||||||
|
runBlockingIncrement {
|
||||||
urlToTrimmedHost(publicSuffixList).await()
|
urlToTrimmedHost(publicSuffixList).await()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
// This class implements the alternative ways to invoke runBlocking with some
|
||||||
|
// monitoring by wrapping the raw methods. This lint check tells us not to use the raw
|
||||||
|
// methods so we suppress the check.
|
||||||
|
@file:Suppress("MozillaRunBlockingCheck")
|
||||||
|
|
||||||
|
package org.mozilla.fenix.perf
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.mozilla.fenix.ext.getAndIncrementNoOverflow
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the number of runBlocking calls made
|
||||||
|
*/
|
||||||
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||||
|
object RunBlockingCounter {
|
||||||
|
val count = AtomicInteger(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around `runBlocking`. RunBlocking seems to be a "fix-all" to return values to the thread
|
||||||
|
* where the coroutine is called. The official doc explains runBlocking: "Runs a new coroutine and
|
||||||
|
* blocks the current thread interruptibly until its completion`. This can block our main thread
|
||||||
|
* which could lead to significant jank. This wrapper aims to count the number of runBlocking call
|
||||||
|
* to try to limit them as much as possible to encourage alternatives solutions whenever this function
|
||||||
|
* might be needed.
|
||||||
|
*/
|
||||||
|
fun <T> runBlockingIncrement(
|
||||||
|
context: CoroutineContext? = null,
|
||||||
|
action: suspend CoroutineScope.() -> T
|
||||||
|
): T {
|
||||||
|
RunBlockingCounter.count.getAndIncrementNoOverflow()
|
||||||
|
return if (context != null) {
|
||||||
|
runBlocking(context) { action() }
|
||||||
|
} else {
|
||||||
|
runBlocking { action() }
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,6 @@ import kotlinx.android.synthetic.main.search_engine_radio_button.view.*
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import mozilla.components.browser.search.SearchEngine
|
import mozilla.components.browser.search.SearchEngine
|
||||||
import org.mozilla.fenix.BrowserDirection
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
@ -37,6 +36,7 @@ import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.requireComponents
|
import org.mozilla.fenix.ext.requireComponents
|
||||||
import org.mozilla.fenix.ext.showToolbar
|
import org.mozilla.fenix.ext.showToolbar
|
||||||
|
import org.mozilla.fenix.perf.runBlockingIncrement
|
||||||
import org.mozilla.fenix.settings.SupportUtils
|
import org.mozilla.fenix.settings.SupportUtils
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine),
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
|
|
||||||
availableEngines = runBlocking {
|
availableEngines = runBlockingIncrement {
|
||||||
requireContext()
|
requireContext()
|
||||||
.components
|
.components
|
||||||
.search
|
.search
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
package org.mozilla.fenix
|
package org.mozilla.fenix
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import mozilla.components.support.migration.FennecMigrator
|
import mozilla.components.support.migration.FennecMigrator
|
||||||
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
|
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
|
||||||
import org.mozilla.fenix.migration.MigrationTelemetryListener
|
import org.mozilla.fenix.migration.MigrationTelemetryListener
|
||||||
|
import org.mozilla.fenix.perf.runBlockingIncrement
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An application class which knows how to migrate Fennec data.
|
* An application class which knows how to migrate Fennec data.
|
||||||
|
@ -81,7 +81,7 @@ class MigratingFenixApplication : FenixApplication() {
|
||||||
.migrateSettings()
|
.migrateSettings()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
runBlocking {
|
runBlockingIncrement {
|
||||||
migrator.migrateAsync(components.migrationStore).await()
|
migrator.migrateAsync(components.migrationStore).await()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
40
app/src/test/java/org/mozilla/fenix/ext/AtomicIntegerTest.kt
Normal file
40
app/src/test/java/org/mozilla/fenix/ext/AtomicIntegerTest.kt
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/* 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.ext
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Test
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
class AtomicIntegerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Safely increment an AtomicInteger from different coroutines`() {
|
||||||
|
val integer = AtomicInteger(0)
|
||||||
|
runBlocking {
|
||||||
|
for (i in 1..2) {
|
||||||
|
launch(Dispatchers.Default) {
|
||||||
|
integer.getAndIncrementNoOverflow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(integer.get(), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Incrementing the AtomicInteger should not overflow`() {
|
||||||
|
val integer = AtomicInteger(Integer.MAX_VALUE)
|
||||||
|
integer.getAndIncrementNoOverflow()
|
||||||
|
assertEquals(integer.get(), Integer.MAX_VALUE)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* 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 org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class RunBlockingCounterTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
RunBlockingCounter.count.set(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN we call our custom runBlocking method with counter THEN the latter should increase`() {
|
||||||
|
assertEquals(0, RunBlockingCounter.count.get())
|
||||||
|
runBlockingIncrement {}
|
||||||
|
assertEquals(1, RunBlockingCounter.count.get())
|
||||||
|
}
|
||||||
|
}
|
|
@ -117,6 +117,9 @@ mozilla-detekt-rules:
|
||||||
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
|
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
|
||||||
MozillaCorrectUnitTestRunner:
|
MozillaCorrectUnitTestRunner:
|
||||||
active: true
|
active: true
|
||||||
|
MozillaRunBlockingCheck:
|
||||||
|
active: true
|
||||||
|
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
|
||||||
|
|
||||||
empty-blocks:
|
empty-blocks:
|
||||||
active: true
|
active: true
|
||||||
|
|
|
@ -16,7 +16,8 @@ class CustomRulesetProvider : RuleSetProvider {
|
||||||
listOf(
|
listOf(
|
||||||
MozillaBannedPropertyAccess(config),
|
MozillaBannedPropertyAccess(config),
|
||||||
MozillaStrictModeSuppression(config),
|
MozillaStrictModeSuppression(config),
|
||||||
MozillaCorrectUnitTestRunner(config)
|
MozillaCorrectUnitTestRunner(config),
|
||||||
|
MozillaRunBlockingCheck(config)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/* 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.detektrules
|
||||||
|
|
||||||
|
import io.gitlab.arturbosch.detekt.api.*
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
|
||||||
|
import kotlin.math.exp
|
||||||
|
|
||||||
|
private const val VIOLATION_MSG = "Please use `org.mozilla.fenix.perf.runBlockingImplement` instead" +
|
||||||
|
"because it allows us to monitor the code for performance regressions."
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A check to prevent us from working around mechanisms we implemented in
|
||||||
|
* @see org.mozilla.fenix.perf.RunBlockingCounter.runBlockingIncrement to count how many runBlocking
|
||||||
|
* are used.
|
||||||
|
*
|
||||||
|
* IF YOU UPDATE THIS FILE NAME, UPDATE CODE OWNERS.
|
||||||
|
*/
|
||||||
|
class MozillaRunBlockingCheck(config: Config) : Rule(config) {
|
||||||
|
|
||||||
|
override val issue = Issue(
|
||||||
|
"MozillaRunBlockingCheck",
|
||||||
|
Severity.Performance,
|
||||||
|
"Prevents us from working around mechanisms we implemented to count how many " +
|
||||||
|
"runBlocking are used",
|
||||||
|
Debt.TWENTY_MINS
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun visitImportDirective(importDirective: KtImportDirective) {
|
||||||
|
if (importDirective.importPath?.toString() == "kotlinx.coroutines.runBlocking") {
|
||||||
|
report(CodeSmell(issue, Entity.from(importDirective), VIOLATION_MSG))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user