Skip to content

Commit

Permalink
Add Wasm/JS support
Browse files Browse the repository at this point in the history
  • Loading branch information
serras committed Jan 21, 2025
1 parent 1f33e45 commit a0077ec
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 1 deletion.
11 changes: 10 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
@file:OptIn(ExperimentalWasmDsl::class)

import org.jetbrains.dokka.gradle.DokkaTask
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
Expand Down Expand Up @@ -28,8 +31,12 @@ kotlin {
jvmTarget = JvmTarget.JVM_1_8
}
}
js(IR) {
js {
nodejs()
}
wasmJs {
nodejs()
d8()
}

linuxArm64()
Expand All @@ -39,6 +46,8 @@ kotlin {
macosX64()

sourceSets {
applyDefaultHierarchyTemplate()

commonMain {
dependencies {
api(libs.coroutines)
Expand Down
80 changes: 80 additions & 0 deletions src/wasmJsMain/kotlin/arrow/continuations/Enviroment.js.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package arrow.continuations

import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.startCoroutine
import kotlin.js.Promise
import kotlin.time.Duration.Companion.hours
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.promise

actual fun process(): Process = JsProcess

object JsProcess : Process {
override fun onShutdown(block: suspend () -> Unit): suspend () -> Unit {
onSigTerm { code -> exitAfter(128 + code) { block() } }
onSigInt { code -> exitAfter(128 + code) { block() } }
return { /* Nothing to unregister */ }
}

override fun onSigTerm(block: suspend (code: Int) -> Unit) = onSignal("SIGTERM") { block(15) }

override fun onSigInt(block: suspend (code: Int) -> Unit) = onSignal("SIGINT") { block(2) }

@OptIn(DelicateCoroutinesApi::class)
@Suppress("UNUSED_PARAMETER")
private fun onSignal(signal: String, block: suspend () -> Unit) {
@Suppress("UNUSED_VARIABLE")
val provide: () -> Promise<JsAny?> = { GlobalScope.promise { block() } }
js("process.on(signal, function() {\n" + " provide()\n" + "});")
}

private val jobs: MutableList<Job> = mutableListOf()

override fun runScope(context: CoroutineContext, block: suspend CoroutineScope.() -> Unit) {
val innerJob = Job()
val innerScope = CoroutineScope(innerJob)
suspend {
// An infinite heartbeat to keep main alive.
// The tick is fast enough that it isn't silently discarded, as longer ticks are,
// but slow enough that we don't interrupt often.
// https://stackoverflow.com/questions/23622051/how-to-forcibly-keep-a-node-js-process-from-terminating
val keepAlive: Job =
innerScope.launch {
while (isActive) {
// Schedule a `no-op tick` by returning to main every hour with no actual work but
// just looping.
delay(1.hours)
}
}
runCatching { block(innerScope) }.also { keepAlive.cancelAndJoin() }.getOrThrow()
}
.startCoroutine(Continuation(EmptyCoroutineContext) {})
}

override fun exit(code: Int) {
runCatching { js("process.exit(code)") }
}

override fun close() {
suspend { jobs.forEach { it.cancelAndJoin() } }
.startCoroutine(Continuation(EmptyCoroutineContext) {})
}
}

private inline fun Process.exitAfter(code: Int, block: () -> Unit): Unit =
try {
block()
exit(code)
} catch (e: Throwable) {
e.printStackTrace()
exit(-1)
}

0 comments on commit a0077ec

Please sign in to comment.