node.jsgradlekotlin-coroutineskotlin-multiplatformkotlin-js

Kotlin/JS app doesn't terminate after library usage


I'm currently working on a Kotlin multi-platform project also targeting NodeJs. Building and executing tests of the project with Gradle so far worked well and without problems.

Lately I tried to use the snarkjs package as a dependency for my jsMain source set. But somehow this messed up my build and i couldn't figure out what's the problem with this specific package. Importing the package is no problem, however as soon as I use any function from this package my application or unit tests won't terminate anymore.
It's extra confusing since importing and using other packages worked well so far and the Gradle prompt shows that all tests are successful, but somehow the task doesn't terminate despite no errors can be found in the logs.

So in the (sub-) project I defined the following build.gradle.kts that also contains the dependency on snarkjs for my js sources.

import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi

group = /*...*/
version = "0.1.0"

plugins {
    kotlin("multiplatform")
    kotlin("plugin.serialization")
    kotlin("plugin.noarg")
    id("dev.adamko.dokkatoo-html")
}

kotlin {
    @OptIn(ExperimentalKotlinGradlePluginApi::class)
    compilerOptions {
        freeCompilerArgs.add("-Xexpect-actual-classes")
    }

    noArg{
        annotation(/*...*/)
    }

    js(IR) {
        useCommonJs()
        nodejs {
            testTask { }
        }
        binaries.executable()
    }

    sourceSets {
        all {
            languageSettings.optIn("kotlin.uuid.ExperimentalUuidApi")
        }
        commonMain.dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
            implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
            implementation(project.dependencies.platform("org.kotlincrypto.hash:bom:0.5.1"))
            implementation("org.kotlincrypto.hash:md")
            implementation("com.ionspin.kotlin:bignum:0.3.10")
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
        }
        commonTest.dependencies {
            implementation(kotlin("test"))
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0")
        }
        jsMain.dependencies {
            implementation(npm("snarkjs", ">=0.7.4"))
            implementation(kotlinWrappers.node)
        }
        jsTest.dependencies { }
        dependencies { }
    }
}

dokkatoo {
    dokkatooSourceSets.configureEach {
        externalDocumentationLinks {
            val `kotlinx-serialization` by creating {
                url("https://kotlinlang.org/api/kotlinx.serialization/")
            }
            val `kotlinx-datetime` by creating {
                url("https://kotlinlang.org/api/kotlinx-datetime/")
            }
        }
    }
}

In my jsMain/kotlin src folder I than import the package in a snarkjs.kt file:

@file:JsModule("snarkjs")
@file:JsQualifier("groth16")

package snarkjs.groth16
import kotlin.js.Promise

external fun fullProve(_input: dynamic, wasmFile: String, zkeyFileName: String, logger: dynamic): Promise<Any>

And try to use it in a unit test like this:

package /*...*/

import kotlinx.coroutines.test.runTest
import snarkjs.groth16.*
import kotlin.test.Ignore
import kotlin.test.Test

class SnarkJSServiceTest {

  @Test
  fun awaitProof() = runTest {
    val result = fullProve(
      JSON.parse("{\"a\": 2, \"b\": 4}"),
      "kotlin/test_circuit_js/test_circuit.wasm",
      "kotlin/test_circuit.zkey",
      null
    ).await()
    println(JSON.stringify(result))
  }
}

And this is where things become wild. While the test function is executed successful and a valid result is printed to the terminal the ./gradlew :check task won't show any error or failed test, yet won't terminate either.

Executing the command in the Terminal will generate the following output (wallet is the name of the gradle submodule). The Tests terminate after 6s, after that gradle is stuck on 85% for ever. the --debug log shows no errors or warnings.

./gradlew :wallet:check
Type-safe project accessors is an incubating feature.
<===========--> 85% EXECUTING [9s]
> :wallet:jsNodeTest > 63 tests completed, 2 skipped

If I annotate the unit test shown above with @Ignore all the command will terminate without any problems.

The import of the function into Kotlin/JS works since the unit test will print correct results. I'm also pretty sure that my project setup works too, since using the node:fs package worked without any problems. So is there anything I overlooked? I would be very grateful for help from anyone who has ever had similar problems.

Cheers ;)


Solution

  • After taking a closer look to the snarkjs package I came across the following issue snarkjs #152 and noticed that it's indeed a bug of the package and not of Gradle, NodeJS, Kotlin/JS or whatsoever.

    The solution is to terminate a global handle, created in the background but not closed by the library by adding we following line of code where ever needed:

    js.globals.globalThis.curve_bn128.terminate()