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

Commit 7c9269fe authored by Jonathan Klee's avatar Jonathan Klee
Browse files

fix: isolate source home loading

Use supervisorScope to not cancel all children when a child gets cancelled.
parent 7bb7a8cf
Loading
Loading
Loading
Loading
Loading
+30 −3
Original line number Diff line number Diff line
@@ -35,8 +35,11 @@ import foundation.e.apps.data.enums.Source
import foundation.e.apps.data.enums.Status
import foundation.e.apps.data.handleNetworkResult
import foundation.e.apps.data.install.models.AppInstall
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.isActive
import kotlinx.coroutines.supervisorScope
import javax.inject.Inject
import javax.inject.Singleton

@@ -76,7 +79,7 @@ class ApplicationRepository @Inject constructor(
                )
            }

            coroutineScope {
            supervisorScope {
                val resultsBySource = mutableMapOf<Source, ResultSupreme<List<Home>>>()
                val sources = stores.getStores().keys
                val deferredResults = sources.associateWith { source ->
@@ -84,7 +87,14 @@ class ApplicationRepository @Inject constructor(
                }

                sources.forEach { source ->
                    val result = deferredResults.getValue(source).await()
                    val result = try {
                        deferredResults.getValue(source).await()
                    } catch (exception: CancellationException) {
                        if (!currentCoroutineContext().isActive) {
                            throw exception
                        }
                        createSourceFailureResult(source, exception)
                    }
                    resultsBySource[source] = result
                    emitResult(result, resultsBySource)
                }
@@ -123,6 +133,23 @@ class ApplicationRepository @Inject constructor(
        }
    }

    private fun createSourceFailureResult(
        source: Source,
        exception: Exception,
    ): ResultSupreme<List<Home>> {
        val status = ResultStatus.UNKNOWN.apply {
            message = exception.message.orEmpty()
        }

        setHomeErrorMessage(status, source)
        return ResultSupreme.create(
            status = status,
            data = emptyList(),
            message = status.message,
            exception = exception,
        )
    }

    private fun mergeResults(results: Collection<ResultSupreme<List<Home>>>): List<Home> {
        val merged = results.flatMap { it.data.orEmpty() }.toMutableList()
        merged.sortBy {
+28 −0
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
@@ -191,6 +192,33 @@ class ApplicationRepositoryHomeTest {
            .containsExactly(ApplicationRepository.APP_TYPE_ANY)
    }

    @Test
    fun fetchHomeScreenDataKeepsOtherSourcesWhenOneSourceIsCancelled() = runTest {
        val cancelledStore = object : StoreRepository {
            override suspend fun getHomeScreenData(list: MutableList<Home>): List<Home> {
                throw CancellationException("request cancelled")
            }
            override suspend fun getAppDetails(packageName: String) = Application()
            override suspend fun getSearchResults(pattern: String) = emptyList<Application>()
            override suspend fun getSearchSuggestions(pattern: String) = emptyList<SearchSuggestion>()
        }
        val repositories = linkedMapOf(
            Source.PLAY_STORE to fakeStore(playStoreHome),
            Source.OPEN_SOURCE to cancelledStore,
        )
        every { stores.getStores() } returns repositories
        every { stores.getStore(Source.PLAY_STORE) } returns repositories.getValue(Source.PLAY_STORE)
        every { stores.getStore(Source.OPEN_SOURCE) } returns cancelledStore

        val emissions = applicationRepository.getHomeScreenData().asFlow().take(2).toList()

        val result = emissions.last()
        assertThat(result.isUnknownError()).isTrue()
        assertThat(result.message).contains("request cancelled")
        assertThat(result.data?.map { it.source })
            .containsExactly(ApplicationRepository.APP_TYPE_ANY)
    }

    @Test
    fun getSelectedAppTypesReturnsEnabledStores() {
        every { stores.isStoreEnabled(Source.PLAY_STORE) } returns true