I'm working on something where I wanted to use the...
# arrow
d
I'm working on something where I wanted to use the
raceN
combinator and I've just hit a
NoClassDefFound
error. I was using Arrow 1.2.4 with kotlinx.coroutines 1.6.4 and looking at the implementation of
race
, it seems it's using the experimental select feature which is throwing a ClassNotFound for some
SelectImplementation
that it didn't find. Upgrading to kotlinx.coroutines 1.8.0 solved this for me. Is this expected? Worth enforcing in the library dependencies or at least mentioning in the docs?
a
thanks for catching this one; I personally was not aware about the change of implementation in
kotlinx.coroutines
, and since the automatic dependency update was green, we proceeded with it
do you mind sharing the entire message + stack trace, to see whether there's some way to make it also work with older versions of kotlinx.coroutines, or we need to just require 1.8.0 from now on
d
can replicate with very simple code:
Copy code
suspend fun main() {
    suspend fun weirdRace() {
        val race = raceN (
            { delay(500); 42 },
            { delay(700); 43 },
        )
        println(race)
    }

    weirdRace()
}
trace:
Copy code
Exception in thread "main" java.lang.NoClassDefFoundError: kotlinx/coroutines/selects/SelectImplementation
	at com.rockthejvm.weird.ParallelOpsKt$main$weirdcom.rockthejvm.weird$$inlined$com.rockthejvm.weirdN$1.invokeSuspend(com.rockthejvm.weird2.kt:112)
	at com.rockthejvm.weird.ParallelOpsKt$main$weirdcom.rockthejvm.weird$$inlined$com.rockthejvm.weirdN$1.invoke(com.rockthejvm.weird2.kt)
	at com.rockthejvm.weird.ParallelOpsKt$main$weirdcom.rockthejvm.weird$$inlined$com.rockthejvm.weirdN$1.invoke(com.rockthejvm.weird2.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:89)
	at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:264)
	at com.rockthejvm.weird.ParallelOpsKt.main$weirdcom.rockthejvm.weird(ParallelOps.kt:157)
	at com.rockthejvm.weird.ParallelOpsKt.main(ParallelOps.kt:123)
	at com.rockthejvm.weird.ParallelOpsKt$main$2.invoke(ParallelOps.kt)
	at com.rockthejvm.weird.ParallelOpsKt$main$2.invoke(ParallelOps.kt)
	at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$1.invokeSuspend(IntrinsicsJvm.kt:270)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlin.coroutines.ContinuationKt.startCoroutine(Continuation.kt:115)
	at kotlin.coroutines.jvm.internal.RunSuspendKt.runSuspend(RunSuspend.kt:19)
	at com.rockthejvm.weird.ParallelOpsKt.main(ParallelOps.kt)
Caused by: java.lang.ClassNotFoundException: kotlinx.coroutines.selects.SelectImplementation
thank you color 1
s
That's problematic!! 😕 So something broke in 1.7.x to 1.8.x from KotlinX I guess. We can do 1.2.5 witg 1.8.x but not ideal... 🙁
a
I think it's more than something changed between 1.6.x and 1.7.x, and inlining is surfacing this
d
replacing the race with its impl seems to work
Copy code
suspend fun weirdRace() {
    val race =
        coroutineScope {
            val a = async(this.coroutineContext) { delay(500); 42 }
            val b = async(this.coroutineContext) { delay(700); 43 }
            select<Either<Int, Int>> {
                a.onAwait.invoke { Either.Left(it) }
                b.onAwait.invoke { Either.Right(it) }
            }.also {
                when (it) {
                    is Either.Left -> b.cancelAndJoin()
                    is Either.Right -> a.cancelAndJoin()
                }
            }
        }
    println(race)
}
s
Ye, that's fore sure a binary breaking change somewhere in KotlinX version.
d
worth raising (pun 😄) in #coroutines?
quickly skimming GitHub diffs, coroutines 1.8.0 made only small changes in
select
since 1.7.0, and indeed 1.7.0 seems to be enough (just tested)
a
as I said above, the problem is in the 1.6.x -> 1.7.0 transition (since it broke with 1.6.4 as dependency, but we compile against 1.7.x) in fact, in 1.6.4 the implementation didn't use `SelectImplementation`, but it's there in the 1.7.0 implementatio
s
It seems the question is, do we release 1.2.5 with new KotlinX version? Or do we just skip it since we want to move Arrow 2.0 to main, release SNAPSHOT/ALPHA/RC and move it out asap with K2?
@Daniel Ciocirlan I don't think this is worth reporting @ KotlinX. I'm pretty sure they're aware of this breaking change.
a
I'm not sure what releasing 1.2.5 would buy us. We already mark the coroutines dependency using `api` instead of `implementation`, and depend on 1.8.0, but Gradle will happily accept a lower version if specified directly on the build file
s
Oh, of course. Yes, ignore that remark. Monday after holidays. There is not enough coffee.
2
a
note that some of these errors are bound to happen, as both coroutines and Arrow use
inline
, which assumes that your implementation doesn't change between versions (in fact, I'm not happy that
select
is marked as
inline
in the coroutines library to being with) should we introduce a warning in Arrow's website? Something similar to:
The Arrow team follows an eager dependency update policy. That means that Arrow libraries always depend on the most up-to-date version of the dependencies at the moment of release. In most cases it's fine to use older version of the dependencies, but in rare cases the conflict leads to NoClassDefFoundError. In that case, please try to update your dependencies to a more recent version, or open an issue.
although I'm not sure which is the best page in the docs to have it
d
I think the best low-effort-high-visibility solution would be to add something like this ^ to the docs, IMO the best place is right where the Maven dependencies are introduced in Quickstart
a
d
Love the fast response @Alejandro Serrano.Mena, this builds massive confidence in your users, I can guarantee that 🙌
🥰 3
🙌 1
❤️ 1
s
Publishing website 💪