Arrow: fx crash

Created on 9 Feb 2019  路  14Comments  路  Source: arrow-kt/arrow

program below is crashing with exception

Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property retVal has not been initialized
    at arrow.typeclasses.suspended.BlockingContinuation.getRetVal(MonadSyntax.kt:24)
    at arrow.typeclasses.suspended.MonadSyntax$DefaultImpls.effect(MonadSyntax.kt:12)
    at arrow.typeclasses.MonadContinuation.effect(MonadContinuations.kt:16)
    at MainKt$fetch$1.invokeSuspend(main.kt:17)
    at MainKt$fetch$1.invoke(main.kt)
    at arrow.typeclasses.Monad$fx$wrapReturn$1.invokeSuspend(Monad.kt:80)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
    at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:127)
    at arrow.typeclasses.Monad$DefaultImpls.fx(Monad.kt:81)
    at arrow.effects.extensions.IOMonad$DefaultImpls.fx(io.kt)
    at arrow.effects.extensions.io.monad.IOMonadKt$monad$1.fx(IOMonad.kt:195)
    at arrow.effects.extensions.io.monad.IOMonadKt.fx(IOMonad.kt:178)
    at MainKt.fetch(main.kt:16)
    at MainKt$main$1$1.invoke(main.kt:10)
    at MainKt$main$1$1.invoke(main.kt)
    at arrow.effects.extensions.IOUnsafeRun$DefaultImpls.runNonBlocking(io.kt:192)
    at arrow.effects.extensions.io.unsafeRun.IOUnsafeRunKt$unsafeRun$1.runNonBlocking(IOUnsafeRun.kt:38)
    at arrow.effects.extensions.io.unsafeRun.IOUnsafeRunKt.runNonBlocking(IOUnsafeRun.kt:35)
    at MainKt$main$1.invokeSuspend(main.kt:10)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
    at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:127)
    at arrow.unsafe.invoke(unsafe.kt:31)
    at MainKt.main(main.kt:9)
    at MainKt.main(main.kt)
import arrow.core.Tuple2
import arrow.effects.IO
import arrow.effects.extensions.io.monad.fx
import arrow.effects.extensions.io.unsafeRun.runNonBlocking
import arrow.unsafe
import kotlinx.coroutines.delay

fun main() {
    unsafe {
        runNonBlocking(::fetch) {
            it.fold({ it.printStackTrace() }, { println(it) })
        }
    }
}

fun fetch(): IO<Tuple2<List<Hero>, List<Comic>>> = fx {
    val heroes = effect { fetchHeroes() }
    val comics = effect { fetchComics() }

    !tupled(heroes, comics)
}

suspend fun fetchHeroes(): List<Hero> {
    delay(250)
    return heroes
}

suspend fun fetchComics(): List<Comic> {
    delay(250)
    return comics
}

val heroes = listOf(Hero(id = 1, name = "Iron Man"), Hero(id = 2, name = "Thor"))
val comics = listOf(Comic(id = 1, name = "Comic 1"), Comic(id = 2, name = "Comic 2"))

data class Hero(
    val id: Int,
    val name: String
)

data class Comic(
    val id: Int,
    val name: String
)
bug work in progress

All 14 comments

wrong fx import use import arrow.effects.extensions.io.fx.fx instead of import arrow.effects.extensions.io.monad.fx

Let's keep it open because it shouldn't crash in any case. cc @raulraja

I think it was confirmed in gitter that in the new snapshot the issue was gone. Is that not the case?

it is not the same error

WARNING!

Snapshot versions are cached and don鈥檛 refresh unless you use the gradle flag --refresh-dependencies and click on clean cache and restart in IntelliJ

I encountered the same issue:
https://kotlinlang.slack.com/archives/C5UPMM0A0/p1549634015443400?thread_ts=1549634015.443400&cid=C5UPMM0A0

This was on Feb 8. I haven't retried it with the latest version of the snapshot. I can do that later today or tomorrow.

At this moment, with the latest 0.9.0-SNAPSNOT downloaded, the issue is still there. I'll repost the code and stack trace below, where getLocation and getReverse are suspend functions as well:

Code:

private suspend fun getCity(): Either<MainViewModelError, StringResource> {
    val result: Either<DataError, StringResource> = fx {
        val (lat, long) = !!effect { locationDataSource.getLocation() }
        val nameOfCity = !!effect { geoDataSource.getReverse(lat, long) }

        nameOfCity.asResource
    }

    return result
        .mapLeft { it.asMainViewModelError }
}

Stack trace caused when calling this function:

     Caused by: kotlin.UninitializedPropertyAccessException: lateinit property retVal has not been initialized
        at arrow.typeclasses.suspended.BlockingContinuation.getRetVal(MonadSyntax.kt:24)
        at arrow.typeclasses.suspended.MonadSyntax$DefaultImpls.effect(MonadSyntax.kt:12)
        at arrow.typeclasses.MonadContinuation.effect(MonadContinuations.kt:16)
        at io.intrepid.mvvmfp.ui.main.MainViewModel$getCity$result$1.invokeSuspend(MainViewModel.kt:78)
        at io.intrepid.mvvmfp.ui.main.MainViewModel$getCity$result$1.invoke(Unknown Source:10)
        at arrow.typeclasses.Monad$fx$wrapReturn$1.invokeSuspend(Monad.kt:80)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
        at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:128)
        at arrow.typeclasses.Monad$DefaultImpls.fx(Monad.kt:81)
        at arrow.core.extensions.EitherMonad$DefaultImpls.fx(Unknown Source:8)
        at arrow.core.extensions.either.monad.EitherMonadKt$monad$1.fx(EitherMonad.kt:194)
        at arrow.typeclasses.suspended.monad.Fx$DefaultImpls.fx(Fx.kt:10)
        at arrow.core.extensions.EitherFx$DefaultImpls.fx(Unknown Source:8)
        at arrow.core.extensions.either.fx.EitherFxKt$fx$1.fx(EitherFx.kt:35)
        at arrow.core.extensions.either.fx.EitherFxKt.fx(EitherFx.kt:22)
        at io.intrepid.mvvmfp.ui.main.MainViewModel.getCity(MainViewModel.kt:77)
        ...

Another small reproduction code sample:

suspend fun getValue1() : Either<Throwable, Int> {
    delay(100) // <-- This make the kotlin.UninitializedPropertyAccessException happen
    return 5.just()
}

suspend fun getValue2() : Either<Throwable, Int> {
    return 10.just()
}

fun main() {
    val result = fx<Throwable, Int> {
        val v1 = effect { getValue1() }
        val v2 = effect { getValue2() }

        !!v1 + !!v2
    }
    println(result)
}

If you change delay(100) to delay(1), the crash sometimes happens. Often "Right(b=15)" is printed, though....
If you remove the delay(100) entirely, the crash does not happen and "Right(b=15)" is printed.

I've ran into a similar problem when working with coroutines and this post helped a lot.
Maybe it can help to fix this issue.

Can you please try to do the same with fx fixed to IO or Observable instead of Either?

Ok, I see what is going on here. This is using the master snapshot which contains effect in MonadSyntaxand a BlockingContinuation. This is is why you can use effect on either and that is incorrect. Will submit a fix before 0.9.0 and refer to this issue. effect can't be used on Either and won't be available simply because Either is eager and can't suspend side effects.

Additionally the only way to implement this on either is to actually block the main thread on that delay suspension which is something we are not gonna do.

The following program shows how it works if you use the IO fx which is a suspend capable monad.

package arrow.effects.zio

import arrow.core.Either
import arrow.core.extensions.either.applicative.just
import arrow.core.extensions.either.applicative.map
import arrow.effects.IO
import arrow.effects.extensions.io.fx.fx
import arrow.effects.extensions.io.unsafeRun.runBlocking
import arrow.unsafe
import kotlinx.coroutines.delay

suspend fun getValue1(): Either<Throwable, Int> {
  delay(100) // <-- Either can't suspend anything because it's an eager data type, so you can't use the `either fx`
  return 5.just()
}

suspend fun getValue2(): Either<Throwable, Int> {
  return 10.just()
}

val program: IO<Either<Throwable, Int>> =
  fx {
    val v1 = !effect { getValue1() }
    val v2 = !effect { getValue2() }
    val result = map(v1, v2) { it.a + it.b }
    !effect { println(result) }
    result
  }

fun main() {
  unsafe { runBlocking { program } } //Right(b=15)
}

To conclude:

  • effect won't be available in data types that can't suspend side effects
  • We need extensions for Concurrent for EitherT, Kleisli and in general all transformers data types that delegate to the inner monad. You could have used the EitherT fx here if we had those instances but currently it's fx only reaches MonadError which is also unable to suspend effects.
  • BlockingContinuation goes away

Will keep this open to reflect those change but if anyone wants to take a shot at it you just need to remove effect from MonadSyntax and get rid of BlockingContinuation. Would love the help as most of the current maintainer team are currently focused on bigger tasks also related to Fx.

/cc @javipacheco I don't need you to check this on Android anymore, thanks anyway 馃槜

working on this now.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

gortiz picture gortiz  路  3Comments

ovu picture ovu  路  4Comments

pakoito picture pakoito  路  3Comments

JorgeCastilloPrz picture JorgeCastilloPrz  路  3Comments

raulraja picture raulraja  路  4Comments