From 161f3f74e20dee765c5301b7c36b377f4030745b Mon Sep 17 00:00:00 2001 From: sergeych Date: Fri, 3 Apr 2026 23:42:43 +0300 Subject: [PATCH] fixed disabled concurrency in CLI --- examples/pi-bench.lyng | 27 +++---- lyng/src/commonMain/kotlin/Common.kt | 9 ++- .../net/sergeych/CliDispatcherJvmTest.kt | 72 +++++++++++++++++++ lynglib/build.gradle.kts | 2 +- 4 files changed, 92 insertions(+), 18 deletions(-) create mode 100644 lyng/src/jvmTest/kotlin/net/sergeych/CliDispatcherJvmTest.kt diff --git a/examples/pi-bench.lyng b/examples/pi-bench.lyng index c95ca3e..9c9d243 100644 --- a/examples/pi-bench.lyng +++ b/examples/pi-bench.lyng @@ -50,23 +50,18 @@ fn piSpigot(iThread: Int, n: Int) { println(iThread, " - done: ", s) } - var counter = 0 +val t0 = Instant() +(1..TASK_COUNT).map { n -> + val counterState = counter + val t = launch { + piSpigot(counterState, WORK_SIZE) + } + ++counter + t +}.forEach { (it as Deferred).await() } -for( repeat in 1..30) { - val t0 = Instant() - (1..TASK_COUNT).map { - val counterState = counter - val t = launch { - piSpigot(counterState, WORK_SIZE) - } - ++counter - t - }.forEach { (it as Deferred).await() } +val dt = Instant() - t0 - val dt = Instant() - t0 - - println("$repeat: all done, dt = ", dt) - delay(1000) -} \ No newline at end of file +println("all done, dt = ", dt) \ No newline at end of file diff --git a/lyng/src/commonMain/kotlin/Common.kt b/lyng/src/commonMain/kotlin/Common.kt index a2c94a9..19e9268 100644 --- a/lyng/src/commonMain/kotlin/Common.kt +++ b/lyng/src/commonMain/kotlin/Common.kt @@ -26,7 +26,9 @@ import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.arguments.optional import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import net.sergeych.lyng.EvalSession import net.sergeych.lyng.LyngVersion import net.sergeych.lyng.Script @@ -245,12 +247,17 @@ fun executeFileWithArgs(fileName: String, args: List) { suspend fun executeSource(source: Source) { val session = EvalSession(baseScopeDefer.await()) try { - session.eval(source) + evalOnCliDispatcher(session, source) } finally { session.cancelAndJoin() } } +internal suspend fun evalOnCliDispatcher(session: EvalSession, source: Source): Obj = + withContext(Dispatchers.Default) { + session.eval(source) + } + suspend fun executeFile(fileName: String) { var text = FileSystem.SYSTEM.source(fileName.toPath()).use { fileSource -> fileSource.buffer().use { bs -> diff --git a/lyng/src/jvmTest/kotlin/net/sergeych/CliDispatcherJvmTest.kt b/lyng/src/jvmTest/kotlin/net/sergeych/CliDispatcherJvmTest.kt new file mode 100644 index 0000000..e37da57 --- /dev/null +++ b/lyng/src/jvmTest/kotlin/net/sergeych/CliDispatcherJvmTest.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package net.sergeych + +import kotlinx.coroutines.runBlocking +import net.sergeych.lyng.EvalSession +import net.sergeych.lyng.Script +import net.sergeych.lyng.Source +import net.sergeych.lyng.obj.ObjList +import net.sergeych.lyng.obj.ObjString +import org.junit.Test +import kotlin.test.assertNotEquals + +class CliDispatcherJvmTest { + @Test + fun executeSourceRunsOnDefaultDispatcher() = runBlocking { + val callerThread = Thread.currentThread() + val callerThreadKey = "${System.identityHashCode(callerThread)}:${callerThread.name}" + val scope = Script.newScope().apply { + addFn("threadKey") { ObjString("${System.identityHashCode(Thread.currentThread())}:${Thread.currentThread().name}") } + addFn("threadName") { ObjString(Thread.currentThread().name) } + } + val session = EvalSession(scope) + + try { + val result = evalOnCliDispatcher( + session, + Source( + "", + """ + val task = launch { [threadKey(), threadName()] } + val child = task.await() + [threadKey(), threadName(), child] + """.trimIndent() + ) + ) as ObjList + + val topLevelThreadKey = (result.list[0] as ObjString).value + val topLevelThreadName = (result.list[1] as ObjString).value + val child = result.list[2] as ObjList + val childThreadKey = (child.list[0] as ObjString).value + val childThreadName = (child.list[1] as ObjString).value + + assertNotEquals( + callerThreadKey, + topLevelThreadKey, + "CLI top-level script body should not run on the runBlocking caller thread: $topLevelThreadName" + ) + assertNotEquals( + callerThreadKey, + childThreadKey, + "CLI launch child should not inherit the runBlocking caller thread: $childThreadName" + ) + } finally { + session.cancelAndJoin() + } + } +} diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index fe1b836..7289dac 100644 --- a/lynglib/build.gradle.kts +++ b/lynglib/build.gradle.kts @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget group = "net.sergeych" -version = "1.5.4" +version = "1.5.4-SNAPSHOT" // Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below