arrow-kt / arrow

Λrrow - Functional companion to Kotlin's Standard Library
http://arrow-kt.io
Other
6.18k stars 452 forks source link

fx crash #1295

Closed marenovakovic closed 5 years ago

marenovakovic commented 5 years ago

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
)
marenovakovic commented 5 years ago

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

pakoito commented 5 years ago

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

raulraja commented 5 years ago

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

marenovakovic commented 5 years ago

it is not the same error

pakoito commented 5 years ago

WARNING!

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

streetsofboston commented 5 years ago

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.

streetsofboston commented 5 years ago

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)
        ...
streetsofboston commented 5 years ago

Could it be related to https://github.com/arrow-kt/arrow/issues/1277 ?

streetsofboston commented 5 years ago

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.

alissonfpmorais commented 5 years ago

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.

pakoito commented 5 years ago

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

raulraja commented 5 years ago

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:

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.

raulraja commented 5 years ago

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

raulraja commented 5 years ago

working on this now.