Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IsolateState can not work with coroutines MutableStateFlow: kotlin.native.concurrent.FreezingException: freezing of Ok(result=kotlinx.coroutines.flow.StateFlowImpl@80804878) has failed, first blocker is kotlinx.coroutines.flow.StateFlowImpl@80804878 #71

Closed
xiaobailong24 opened this issue Nov 9, 2021 · 9 comments

Comments

@xiaobailong24
Copy link

xiaobailong24 commented Nov 9, 2021

org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1-native-mt

iosTest sorceset:

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlin.coroutines.CoroutineContext
import kotlin.test.Test

// https://github.com/Kotlin/kotlinx.coroutines/issues/1996
//iOS code:
val testCoroutineContext: CoroutineContext =
    newSingleThreadContext("testRunner")

fun runBlockingTest(block: suspend CoroutineScope.() -> Unit) =
    runBlocking(testCoroutineContext) { this.block() }

class IsoStateTest {

    private val flowState =
        IsolateState {
            MutableStateFlow("")
        }

    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun test() = runBlockingTest {
        println("runBlockingTest start")
        val job = launch {
            flowState
                .access { it }
                .asStateFlow().collect {
                    run()
                }
        }

        flowState.access { it.value = "123" }

//        job.cancel()
        println("runBlockingTest end")
    }

    private suspend fun run() {
        println("collect:${flowState.access { it.value }}")
    }
}

got error:

runBlockingTest start
runBlockingTest end

kotlin.native.concurrent.FreezingException: freezing of Ok(result=kotlinx.coroutines.flow.StateFlowImpl@80804878) has failed, first blocker is kotlinx.coroutines.flow.StateFlowImpl@80804878
kotlin.native.concurrent.FreezingException: freezing of Ok(result=kotlinx.coroutines.flow.StateFlowImpl@80804878) has failed, first blocker is kotlinx.coroutines.flow.StateFlowImpl@80804878
	at kotlin.Throwable#<init>(/Users/teamcity1/teamcity_work/7c4fade2802b7783/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:23)
	at kotlin.Exception#<init>(/Users/teamcity1/teamcity_work/7c4fade2802b7783/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:23)
	at kotlin.RuntimeException#<init>(/Users/teamcity1/teamcity_work/7c4fade2802b7783/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:34)
	at kotlin.native.concurrent.FreezingException#<init>(/Users/teamcity1/teamcity_work/7c4fade2802b7783/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Freezing.kt:15)
	at <global>.ThrowFreezingException(/Users/teamcity1/teamcity_work/7c4fade2802b7783/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Internal.kt:88)
	at <global>.FreezeSubgraph(Unknown Source)
	at <global>.Kotlin_Worker_freezeInternal(Unknown Source)
	at kotlin.native.concurrent#freeze__at__0:0(/Users/teamcity1/teamcity_work/7c4fade2802b7783/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Freezing.kt:33)
	at co.touchlab.stately.isolate.BackgroundStateRunner.stateRun$lambda-1#internal(co.touchlab.stately.isolate/BackgroundStateRunner.kt:15)
	at <global>._ZN6Worker19processQueueElementEb(Unknown Source)
	at <global>._ZN12_GLOBAL__N_113workerRoutineEPv(Unknown Source)
	at <global>._pthread_start(Unknown Source)
	at <global>.thread_start(Unknown Source)

@xiaobailong24
Copy link
Author

@kpgalligan
Copy link
Contributor

This code directly violates the basic concept of IsolateState.

flowState
      .access { it }

The thing that's kept isolated, MutableStateFlow(""), has ensureNeverFrozen() called on it when you init the IsolateState instance (https://github.com/touchlab/Stately/blob/main/stately-isolate/src/nativeCommonMain/kotlin/co/touchlab/stately/isolate/Platform.kt#L15).

The exception is happening because when you try to return the MutableStateFlow instance in the above code, it needs to be frozen as per K/N memory model rules, but you can't (because ensureNeverFrozen() ).

As for what you should do instead, I'm not sure. Flows are freezable, generally speaking. The bug you linked is old and fixed.

@xiaobailong24
Copy link
Author

@kpgalligan Thanks for your replay. But how to use MutableStateFlow with IsolateState please?

@xiaobailong24
Copy link
Author

xiaobailong24 commented Nov 10, 2021

Use Result wrapper the flow to solve the poblem:

 private val flowState =
        IsolateState {
            Result.success(MutableStateFlow("1"))
        }


    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun test() = runBlockingTest {
        println("runBlockingTest start")
        val job = launch {
            flowState
                .access {
                    it.getOrNull()!!
                }
                .asStateFlow()
                .collect {
                    run()
                }
        }

        flowState.access { it.getOrNull()?.value = "123" }

//        job.cancel()
        println("runBlockingTest end")
    }

    private suspend fun run() {
        println("collect:${flowState.access { it.getOrNull()?.value }}")
    }

@kpgalligan
Copy link
Contributor

Why are you using IsolateState at all in that case? The MutableStateFlow instance will be frozen. Try this:

private val flowState = MutableStateFlow("1")

    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    fun test() = runBlockingTest {
        println("runBlockingTest start")
        val job = launch {
            flowState
                .asStateFlow()
                .collect {
                    run()
                }
        }

        flowState.value = "123"

//        job.cancel()
        println("runBlockingTest end")
    }

    private suspend fun run() {
        println("collect:${flowState.value}")
    }

@xiaobailong24
Copy link
Author

xiaobailong24 commented Nov 11, 2021

Because the MutableStateFlow instance may be accessed to get or set new value in multi-thread.

@xiaobailong24
Copy link
Author

@kpgalligan
access() block param will block main thread in iOS platform?

fun test() {
        val map = IsolateState{ mutableMapOf<Int,String>()}
        map.access { _map->
            // will block main thread?
            repeat(100_1000){
                _map[it] = "$it"
            }
        }
    }

@kpgalligan
Copy link
Contributor

Yes, until your loop is done. IsolateState isn't async and access isn't a suspend.

@xiaobailong24
Copy link
Author

Is there a workaround to handle time-consuming task and don't block main thread?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants