Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Verified Commit c2a1e19f authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

perf: cap parallel signature and system fetch

Run signature verification and system app update fetches concurrently with conservative caps to reduce latency without saturating IO.
parent 0d3a08fe
Loading
Loading
Loading
Loading
+62 −29
Original line number Diff line number Diff line
@@ -33,6 +33,10 @@ import foundation.e.apps.data.handleNetworkResult
import foundation.e.apps.data.install.pkg.AppLoungePackageManager
import foundation.e.apps.data.system.SystemInfoProvider
import foundation.e.apps.domain.model.install.Status
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import timber.log.Timber
import java.util.Locale
import javax.inject.Inject
@@ -66,6 +70,8 @@ class SystemAppsUpdatesRepository @Inject constructor(
        private const val MILLIS_IN_SECOND = 1000L
        private const val SECONDS_IN_MINUTE = 60L
        private const val MINUTES_IN_HOUR = 60L

        private const val SYSTEM_APP_CONCURRENCY = 2
    }

    private fun getUpdatableSystemApps(): List<String> {
@@ -271,7 +277,7 @@ class SystemAppsUpdatesRepository @Inject constructor(
        }
    }

    suspend fun getSystemUpdates(): List<Application> {
    suspend fun getSystemUpdates(): List<Application> = coroutineScope {
        val updateList = mutableListOf<Application>()
        val overallStartMs = nowMs()
        val releaseType = getSystemReleaseType()
@@ -283,55 +289,82 @@ class SystemAppsUpdatesRepository @Inject constructor(
            "System updates start, totalApps=%d",
            updatableApps.size
        )
        updatableApps.forEach {

        val semaphore = Semaphore(SYSTEM_APP_CONCURRENCY)
        val fetchTasks = updatableApps.map { packageName ->
            async {
                semaphore.withPermit {
                    fetchSystemAppUpdate(packageName, releaseType, sdkLevel, device)
                }
            }
        }

        fetchTasks.forEach { deferred ->
            deferred.await()?.let { updateList.add(it) }
        }

        Timber.tag("FAHIM").i(
            "System updates done, updatable=%d, duration=%s",
            updateList.size,
            formatDuration(nowMs() - overallStartMs)
        )

        return@coroutineScope updateList
    }

    private suspend fun fetchSystemAppUpdate(
        packageName: String,
        releaseType: OsReleaseType,
        sdkLevel: Int,
        device: String,
    ): Application? {
        val perAppStartMs = nowMs()
            if (!appLoungePackageManager.isInstalled(it)) {
        val installed = appLoungePackageManager.isInstalled(packageName)
        if (!installed) {
            // Don't install for system apps which are removed (by root or otherwise)
            Timber.tag("FAHIM").i(
                "System updates skip (not installed), package=%s",
                    it
                packageName
            )
                return@forEach
        }

            val result = handleNetworkResult {
        val result = if (installed) {
            handleNetworkResult {
                getApplication(
                    it,
                    packageName,
                    releaseType,
                    sdkLevel,
                    device,
                )
            }
        } else {
            null
        }

        if (result != null) {
            Timber.tag("FAHIM").i(
                "System updates fetch done, package=%s, duration=%s, success=%s",
                it,
                packageName,
                formatDuration(nowMs() - perAppStartMs),
                result.isSuccess()
            )

            if (!result.isSuccess()) {
                Timber.e("Failed to get system app info for $it - ${result.message}")
                return@forEach
                Timber.e("Failed to get system app info for $packageName - ${result.message}")
            }
        }

            val app: Application = result.data ?: return@forEach
            val appStatus = appLoungePackageManager.getPackageStatus(it, app.latest_version_code)
            if (appStatus != Status.UPDATABLE) return@forEach

        val app: Application? = if (result?.isSuccess() == true) result.data else null
        val appStatus = app?.let { appLoungePackageManager.getPackageStatus(packageName, it.latest_version_code) }
        val updateApp = if (app != null && appStatus == Status.UPDATABLE) {
            app.run {
                applicationDataManager.updateStatus(this)
                source = Source.SYSTEM_APP
                updateList.add(this)
            }
            app
        } else {
            null
        }

        Timber.tag("FAHIM").i(
            "System updates done, updatable=%d, duration=%s",
            updateList.size,
            formatDuration(nowMs() - overallStartMs)
        )

        return updateList
        return updateApp
    }

    private fun nowMs(): Long {
+51 −31
Original line number Diff line number Diff line
@@ -37,6 +37,8 @@ import foundation.e.apps.domain.preferences.AppPreferencesRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.Locale
@@ -62,6 +64,8 @@ class UpdatesManagerImpl @Inject constructor(
        private const val MILLIS_IN_SECOND = 1000L
        private const val SECONDS_IN_MINUTE = 60L
        private const val MINUTES_IN_HOUR = 60L

        private const val SIGNATURE_VERIFY_CONCURRENCY = 3
    }

    private val userApplications: List<ApplicationInfo>
@@ -475,7 +479,7 @@ class UpdatesManagerImpl @Inject constructor(
    private suspend fun findPackagesMatchingFDroidSignatures(
        installedPackageNames: List<String>,
        cleanApkAppsByPackage: Map<String, Application?>,
    ): List<String> {
    ): List<String> = coroutineScope {
        val signatureStartMs = nowMs()
        val fDroidAppsAndSignatures = getFDroidAppsAndSignatures(
            installedPackageNames,
@@ -483,16 +487,45 @@ class UpdatesManagerImpl @Inject constructor(
        )

        val fDroidUpdatablePackageNames = mutableListOf<String>()
        fDroidAppsAndSignatures.forEach { (packageName, signature) ->
        val semaphore = Semaphore(SIGNATURE_VERIFY_CONCURRENCY)
        val verificationTasks = fDroidAppsAndSignatures.mapNotNull { (packageName, signature) ->
            if (signature.isEmpty()) {
                Timber.tag("FAHIM").i(
                    "Updates signature skipped, package=%s, reason=empty_signature",
                    packageName
                )
                return@forEach
                return@mapNotNull null
            }

            async {
                semaphore.withPermit {
                    verifyFdroidSignatureCandidate(packageName, signature)
                }
            }
        }

        verificationTasks.forEach { deferred ->
            val (packageName, verified) = deferred.await()
            if (verified) {
                fDroidUpdatablePackageNames.add(packageName)
            }
        }

        Timber.tag("FAHIM").i(
            "Updates signature matching done, input=%d, candidates=%d, matched=%d, duration=%s",
            installedPackageNames.size,
            fDroidAppsAndSignatures.size,
            fDroidUpdatablePackageNames.size,
            formatDuration(nowMs() - signatureStartMs)
        )

        return@coroutineScope fDroidUpdatablePackageNames
    }

            // For each installed app also present on F-droid, check signature of base APK.
    private suspend fun verifyFdroidSignatureCandidate(
        packageName: String,
        signature: String,
    ): Pair<String, Boolean> {
        val baseApkStartMs = nowMs()
        val baseApkPath = appLoungePackageManager.getBaseApkPath(packageName)
        val baseApkDuration = formatDuration(nowMs() - baseApkStartMs)
@@ -503,7 +536,7 @@ class UpdatesManagerImpl @Inject constructor(
            baseApkPath.isNotEmpty()
        )
        if (baseApkPath.isEmpty()) {
                return@forEach
            return Pair(packageName, false)
        }

        val verifyStartMs = nowMs()
@@ -521,20 +554,7 @@ class UpdatesManagerImpl @Inject constructor(
            formatDuration(nowMs() - verifyStartMs),
            verified
        )
            if (verified) {
                fDroidUpdatablePackageNames.add(packageName)
            }
        }

        Timber.tag("FAHIM").i(
            "Updates signature matching done, input=%d, candidates=%d, matched=%d, duration=%s",
            installedPackageNames.size,
            fDroidAppsAndSignatures.size,
            fDroidUpdatablePackageNames.size,
            formatDuration(nowMs() - signatureStartMs)
        )

        return fDroidUpdatablePackageNames
        return Pair(packageName, verified)
    }

    private suspend fun getCleanApkDetailsByPackage(