Compare commits
No commits in common. "9b90fe370b51c88a7a08eb8fb146da61fae2b302" and "ef95ed440571d831120952c8c5f9de750206a56b" have entirely different histories.
9b90fe370b
...
ef95ed4405
@ -18,7 +18,6 @@
|
|||||||
- Avoid creating suspend lambdas for compiler runtime statements. Prefer explicit `object : Statement()` with `override suspend fun execute(...)`.
|
- Avoid creating suspend lambdas for compiler runtime statements. Prefer explicit `object : Statement()` with `override suspend fun execute(...)`.
|
||||||
- Do not use `statement { ... }` or other inline suspend lambdas in compiler hot paths (e.g., parsing/var declarations, initializer thunks).
|
- Do not use `statement { ... }` or other inline suspend lambdas in compiler hot paths (e.g., parsing/var declarations, initializer thunks).
|
||||||
- If you need a wrapper for delegated properties, check for `getValue` explicitly and return a concrete `Statement` object when missing; avoid `onNotFoundResult` lambdas.
|
- If you need a wrapper for delegated properties, check for `getValue` explicitly and return a concrete `Statement` object when missing; avoid `onNotFoundResult` lambdas.
|
||||||
- For any code in `commonMain`, verify it is Kotlin Multiplatform compatible before finishing. Do not use JVM-only APIs or Java-backed convenience methods such as `Map.putIfAbsent`; prefer stdlib/common equivalents and run at least the relevant compile/test task that exercises the `commonMain` source set.
|
|
||||||
- If wasmJs browser tests hang, first run `:lynglib:wasmJsNodeTest` and look for wasm compilation errors; hangs usually mean module instantiation failed.
|
- If wasmJs browser tests hang, first run `:lynglib:wasmJsNodeTest` and look for wasm compilation errors; hangs usually mean module instantiation failed.
|
||||||
- Do not increase test timeouts to mask wasm generation errors; fix the invalid IR instead.
|
- Do not increase test timeouts to mask wasm generation errors; fix the invalid IR instead.
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# Lyng CLI (`lyng`)
|
### Lyng CLI (`lyng`)
|
||||||
|
|
||||||
The Lyng CLI is the reference command-line tool for the Lyng language. It lets you:
|
The Lyng CLI is the reference command-line tool for the Lyng language. It lets you:
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ The Lyng CLI is the reference command-line tool for the Lyng language. It lets y
|
|||||||
- Format Lyng source files via the built-in `fmt` subcommand.
|
- Format Lyng source files via the built-in `fmt` subcommand.
|
||||||
|
|
||||||
|
|
||||||
## Building on Linux
|
#### Building on Linux
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
- JDK 17+ (for Gradle and the JVM distribution)
|
- JDK 17+ (for Gradle and the JVM distribution)
|
||||||
@ -20,7 +20,7 @@ The repository provides convenience scripts in `bin/` for local builds and insta
|
|||||||
Note: In this repository the scripts are named `bin/local_release` and `bin/local_jrelease`. In some environments these may be aliased as `bin/release` and `bin/jrelease`. The steps below use the actual file names present here.
|
Note: In this repository the scripts are named `bin/local_release` and `bin/local_jrelease`. In some environments these may be aliased as `bin/release` and `bin/jrelease`. The steps below use the actual file names present here.
|
||||||
|
|
||||||
|
|
||||||
### Option A: Native linuxX64 executable (`lyng`)
|
##### Option A: Native linuxX64 executable (`lyng`)
|
||||||
|
|
||||||
1) Build the native binary:
|
1) Build the native binary:
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ What this does:
|
|||||||
- Produces `distributables/lyng-linuxX64.zip` containing the `lyng` executable.
|
- Produces `distributables/lyng-linuxX64.zip` containing the `lyng` executable.
|
||||||
|
|
||||||
|
|
||||||
### Option B: JVM distribution (`jlyng` launcher)
|
##### Option B: JVM distribution (`jlyng` launcher)
|
||||||
|
|
||||||
This creates a JVM distribution with a launcher script, packages it as a downloadable zip, and links it to `~/bin/jlyng`.
|
This creates a JVM distribution with a launcher script, packages it as a downloadable zip, and links it to `~/bin/jlyng`.
|
||||||
|
|
||||||
@ -54,12 +54,12 @@ What this does:
|
|||||||
- Creates a symlink `~/bin/jlyng` pointing to the launcher script.
|
- Creates a symlink `~/bin/jlyng` pointing to the launcher script.
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
#### Usage
|
||||||
|
|
||||||
Once installed, ensure `~/bin` is on your `PATH`. You can then use either the native `lyng` or the JVM `jlyng` launcher (both have the same CLI surface).
|
Once installed, ensure `~/bin` is on your `PATH`. You can then use either the native `lyng` or the JVM `jlyng` launcher (both have the same CLI surface).
|
||||||
|
|
||||||
|
|
||||||
### Running scripts
|
##### Running scripts
|
||||||
|
|
||||||
- Run a script by file name and pass arguments to `ARGV`:
|
- Run a script by file name and pass arguments to `ARGV`:
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ lyng --version
|
|||||||
lyng --help
|
lyng --help
|
||||||
```
|
```
|
||||||
|
|
||||||
### Local imports for file execution
|
##### Local imports for file execution
|
||||||
|
|
||||||
When you execute a script file, the CLI builds a temporary local import manager rooted at the directory that contains the entry script.
|
When you execute a script file, the CLI builds a temporary local import manager rooted at the directory that contains the entry script.
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ Rationale:
|
|||||||
- Explicit `package` remains available as a consistency check instead of a second, conflicting naming system.
|
- Explicit `package` remains available as a consistency check instead of a second, conflicting naming system.
|
||||||
- The import search space stays local to the executed script, which avoids accidental cross-project resolution.
|
- The import search space stays local to the executed script, which avoids accidental cross-project resolution.
|
||||||
|
|
||||||
## Use in shell scripts
|
### Use in shell scripts
|
||||||
|
|
||||||
Standard unix shebangs (`#!`) are supported, so you can make Lyng scripts directly executable on Unix-like systems. For example:
|
Standard unix shebangs (`#!`) are supported, so you can make Lyng scripts directly executable on Unix-like systems. For example:
|
||||||
|
|
||||||
@ -152,7 +152,7 @@ Standard unix shebangs (`#!`) are supported, so you can make Lyng scripts direct
|
|||||||
println("Hello, world!")
|
println("Hello, world!")
|
||||||
|
|
||||||
|
|
||||||
### Formatting source: `fmt` subcommand
|
##### Formatting source: `fmt` subcommand
|
||||||
|
|
||||||
Format Lyng files with the built-in formatter.
|
Format Lyng files with the built-in formatter.
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ lyng fmt --spacing --wrap src/file.lyng
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Notes
|
#### Notes
|
||||||
|
|
||||||
- Both native and JVM distributions expose the same CLI interface. Use whichever best fits your environment.
|
- Both native and JVM distributions expose the same CLI interface. Use whichever best fits your environment.
|
||||||
- When executing scripts, all positional arguments after the script name are available in Lyng as `ARGV`.
|
- When executing scripts, all positional arguments after the script name are available in Lyng as `ARGV`.
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
import lyng.buffer
|
|
||||||
import lyng.io.net
|
|
||||||
|
|
||||||
val host = "127.0.0.1"
|
|
||||||
val port = 8092
|
|
||||||
val N = 5
|
|
||||||
val server = Net.tcpListen(port, host)
|
|
||||||
println("start tcp server at $host:$port")
|
|
||||||
|
|
||||||
fun serveClient(client: TcpSocket) = launch {
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
val data = client.read()
|
|
||||||
if (data == null) break
|
|
||||||
var line = (data as Buffer).decodeUtf8()
|
|
||||||
line = "[" + client.remoteAddress() + "]> " + line
|
|
||||||
println(line)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
println("ERROR [reader]: " + e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun serveRequests(server: TcpServer) = launch {
|
|
||||||
val readers = []
|
|
||||||
try {
|
|
||||||
for (i in 0..<5) {
|
|
||||||
val client = server.accept()
|
|
||||||
println("accept new connection: " + client.remoteAddress())
|
|
||||||
readers.add(serveClient(client as TcpSocket))
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
println("ERROR [listener]: " + e)
|
|
||||||
} finally {
|
|
||||||
server.close()
|
|
||||||
}
|
|
||||||
for (i in 0..<readers.size) {
|
|
||||||
val reader = readers[i]
|
|
||||||
(reader as Deferred).await()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val srv = serveRequests(server as TcpServer)
|
|
||||||
|
|
||||||
var clients = []
|
|
||||||
for (i in 0..<N) {
|
|
||||||
//delay(500)
|
|
||||||
clients.add(launch {
|
|
||||||
try{
|
|
||||||
val socket = Net.tcpConnect(host, port)
|
|
||||||
socket.writeUtf8("ping1ping2ping3ping4ping5")
|
|
||||||
socket.flush()
|
|
||||||
socket.close()
|
|
||||||
} catch (e) {
|
|
||||||
println("ERROR [client]: " + e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in 0..<clients.size) {
|
|
||||||
val c = clients[i]
|
|
||||||
(c as Deferred).await()
|
|
||||||
println("client done")
|
|
||||||
}
|
|
||||||
|
|
||||||
srv.await()
|
|
||||||
delay(10000)
|
|
||||||
println("FIN")
|
|
||||||
@ -1,48 +1,21 @@
|
|||||||
|
import lyng.buffer
|
||||||
import lyng.io.net
|
import lyng.io.net
|
||||||
|
|
||||||
val host = "127.0.0.1"
|
val server = Net.tcpListen(0, "127.0.0.1")
|
||||||
val clientCount = 1000
|
|
||||||
val server = Net.tcpListen(0, host, clientCount, true) as TcpServer
|
|
||||||
val port = server.localAddress().port
|
val port = server.localAddress().port
|
||||||
|
val accepted = launch {
|
||||||
fun payloadFor(index: Int) = "$index:${Random.nextInt()}:${Random.nextInt()}"
|
val client = server.accept()
|
||||||
|
val line = (client.read(4) as Buffer).decodeUtf8()
|
||||||
launch {
|
client.writeUtf8("echo:" + line)
|
||||||
try {
|
|
||||||
while(true) {
|
|
||||||
val client = server.accept() as TcpSocket
|
|
||||||
launch {
|
|
||||||
try {
|
|
||||||
val source = client.readLine()
|
|
||||||
if( source != null ) {
|
|
||||||
client.writeUtf8("pong: $source\n")
|
|
||||||
client.flush()
|
client.flush()
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
client.close()
|
client.close()
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
server.close()
|
server.close()
|
||||||
}
|
line
|
||||||
}
|
}
|
||||||
|
|
||||||
val replies = (0..<clientCount).map { index ->
|
val socket = Net.tcpConnect("127.0.0.1", port)
|
||||||
val payload = payloadFor(index)
|
socket.writeUtf8("ping")
|
||||||
launch {
|
socket.flush()
|
||||||
val socket = Net.tcpConnect(host, port) as TcpSocket
|
val reply = (socket.read(16) as Buffer).decodeUtf8()
|
||||||
try {
|
socket.close()
|
||||||
socket.writeUtf8(payload + "\n")
|
println("${accepted.await()}: $reply")
|
||||||
socket.flush()
|
|
||||||
val reply = socket.readLine()
|
|
||||||
assertEquals("pong: $payload", reply)
|
|
||||||
reply
|
|
||||||
} finally {
|
|
||||||
socket.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.joinAll()
|
|
||||||
|
|
||||||
assertEquals(clientCount, replies.size)
|
|
||||||
println("OK: $clientCount concurrent tcp clients")
|
|
||||||
|
|||||||
@ -18,7 +18,6 @@ slf4j = "2.0.17"
|
|||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" }
|
clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" }
|
||||||
clikt-core = { module = "com.github.ajalt.clikt:clikt-core", version.ref = "clikt" }
|
|
||||||
clikt-markdown = { module = "com.github.ajalt.clikt:clikt-markdown", version.ref = "clikt" }
|
clikt-markdown = { module = "com.github.ajalt.clikt:clikt-markdown", version.ref = "clikt" }
|
||||||
mordant-core = { module = "com.github.ajalt.mordant:mordant-core", version.ref = "mordant" }
|
mordant-core = { module = "com.github.ajalt.mordant:mordant-core", version.ref = "mordant" }
|
||||||
mordant-jvm-jna = { module = "com.github.ajalt.mordant:mordant-jvm-jna", version.ref = "mordant" }
|
mordant-jvm-jna = { module = "com.github.ajalt.mordant:mordant-jvm-jna", version.ref = "mordant" }
|
||||||
|
|||||||
@ -52,12 +52,6 @@ kotlin {
|
|||||||
linuxX64 {
|
linuxX64 {
|
||||||
binaries {
|
binaries {
|
||||||
executable()
|
executable()
|
||||||
all {
|
|
||||||
if (buildType == org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.RELEASE) {
|
|
||||||
debuggable = true
|
|
||||||
optimized = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@ -69,7 +63,7 @@ kotlin {
|
|||||||
// filesystem access into the execution Scope by default.
|
// filesystem access into the execution Scope by default.
|
||||||
implementation(project(":lyngio"))
|
implementation(project(":lyngio"))
|
||||||
implementation(libs.okio)
|
implementation(libs.okio)
|
||||||
implementation(libs.clikt.core)
|
implementation(libs.clikt)
|
||||||
implementation(kotlin("stdlib-common"))
|
implementation(kotlin("stdlib-common"))
|
||||||
// optional support for rendering markdown in help messages
|
// optional support for rendering markdown in help messages
|
||||||
// implementation(libs.clikt.markdown)
|
// implementation(libs.clikt.markdown)
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
package net.sergeych
|
package net.sergeych
|
||||||
|
|
||||||
import com.github.ajalt.clikt.core.CoreCliktCommand
|
import com.github.ajalt.clikt.core.CliktCommand
|
||||||
import com.github.ajalt.clikt.core.Context
|
import com.github.ajalt.clikt.core.Context
|
||||||
import com.github.ajalt.clikt.core.main
|
import com.github.ajalt.clikt.core.main
|
||||||
import com.github.ajalt.clikt.core.subcommands
|
import com.github.ajalt.clikt.core.subcommands
|
||||||
@ -43,7 +43,6 @@ import net.sergeych.lyng.io.net.createNetModule
|
|||||||
import net.sergeych.lyng.io.ws.createWsModule
|
import net.sergeych.lyng.io.ws.createWsModule
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.pacman.ImportManager
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
import net.sergeych.lyngio.net.shutdownSystemNetEngine
|
|
||||||
import net.sergeych.lyngio.console.security.PermitAllConsoleAccessPolicy
|
import net.sergeych.lyngio.console.security.PermitAllConsoleAccessPolicy
|
||||||
import net.sergeych.lyngio.fs.security.PermitAllAccessPolicy
|
import net.sergeych.lyngio.fs.security.PermitAllAccessPolicy
|
||||||
import net.sergeych.lyngio.http.security.PermitAllHttpAccessPolicy
|
import net.sergeych.lyngio.http.security.PermitAllHttpAccessPolicy
|
||||||
@ -71,28 +70,10 @@ data class CommandResult(
|
|||||||
val error: String
|
val error: String
|
||||||
)
|
)
|
||||||
|
|
||||||
private val baseCliImportManagerDefer = globalDefer {
|
|
||||||
val manager = Script.defaultImportManager.copy().apply {
|
|
||||||
installCliModules(this)
|
|
||||||
}
|
|
||||||
manager.newStdScope()
|
|
||||||
manager
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ImportManager.invalidateCliModuleCaches() {
|
|
||||||
invalidatePackageCache("lyng.io.fs")
|
|
||||||
invalidatePackageCache("lyng.io.console")
|
|
||||||
invalidatePackageCache("lyng.io.http")
|
|
||||||
invalidatePackageCache("lyng.io.ws")
|
|
||||||
invalidatePackageCache("lyng.io.net")
|
|
||||||
}
|
|
||||||
|
|
||||||
val baseScopeDefer = globalDefer {
|
val baseScopeDefer = globalDefer {
|
||||||
baseCliImportManagerDefer.await().copy().apply {
|
Script.newScope().apply {
|
||||||
invalidateCliModuleCaches()
|
|
||||||
}.newStdScope().apply {
|
|
||||||
installCliBuiltins()
|
installCliBuiltins()
|
||||||
addConst("ARGV", ObjList(mutableListOf()))
|
installCliModules(importManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,47 +195,35 @@ private fun discoverLocalCliModules(entryFile: Path): List<LocalCliModule> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
val packageName = declaredPackage ?: expectedPackage
|
val packageName = declaredPackage ?: expectedPackage
|
||||||
val previous = seenPackages[packageName]
|
val previous = seenPackages.putIfAbsent(packageName, file)
|
||||||
if (previous != null) {
|
if (previous != null) {
|
||||||
throw ScriptError(
|
throw ScriptError(
|
||||||
source.startPos,
|
source.startPos,
|
||||||
"duplicate local module '$packageName': ${previous.toString()} and ${file.toString()}"
|
"duplicate local module '$packageName': ${previous.toString()} and ${file.toString()}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
seenPackages[packageName] = file
|
|
||||||
LocalCliModule(packageName, source)
|
LocalCliModule(packageName, source)
|
||||||
}
|
}
|
||||||
.toList()
|
.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerLocalCliModules(manager: ImportManager, modules: List<LocalCliModule>) {
|
private fun registerLocalCliModules(manager: ImportManager, entryFile: Path) {
|
||||||
for (module in modules) {
|
for (module in discoverLocalCliModules(entryFile)) {
|
||||||
manager.addPackage(module.packageName) { scope ->
|
manager.addPackage(module.packageName) { scope ->
|
||||||
scope.eval(module.source)
|
scope.eval(module.source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun ImportManager.newCliScope(argv: List<String>): Scope =
|
private suspend fun newCliScope(argv: List<String>, entryFileName: String? = null): Scope {
|
||||||
newStdScope().apply {
|
val manager = baseScopeDefer.await().importManager.copy()
|
||||||
|
if (entryFileName != null) {
|
||||||
|
registerLocalCliModules(manager, canonicalPath(entryFileName.toPath()))
|
||||||
|
}
|
||||||
|
return manager.newStdScope().apply {
|
||||||
installCliBuiltins()
|
installCliBuiltins()
|
||||||
addConst("ARGV", ObjList(argv.map { ObjString(it) }.toMutableList()))
|
addConst("ARGV", ObjList(argv.map { ObjString(it) }.toMutableList()))
|
||||||
}
|
}
|
||||||
|
|
||||||
internal suspend fun newCliScope(argv: List<String>, entryFileName: String? = null): Scope {
|
|
||||||
val baseManager = baseCliImportManagerDefer.await().copy().apply {
|
|
||||||
invalidateCliModuleCaches()
|
|
||||||
}
|
|
||||||
if (entryFileName == null) {
|
|
||||||
return baseManager.newCliScope(argv)
|
|
||||||
}
|
|
||||||
val entryFile = canonicalPath(entryFileName.toPath())
|
|
||||||
val localModules = discoverLocalCliModules(entryFile)
|
|
||||||
if (localModules.isEmpty()) {
|
|
||||||
return baseManager.newCliScope(argv)
|
|
||||||
}
|
|
||||||
registerLocalCliModules(baseManager, localModules)
|
|
||||||
return baseManager.newCliScope(argv)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun runMain(args: Array<String>) {
|
fun runMain(args: Array<String>) {
|
||||||
@ -278,7 +247,7 @@ fun runMain(args: Array<String>) {
|
|||||||
.main(args)
|
.main(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Fmt : CoreCliktCommand(name = "fmt") {
|
private class Fmt : CliktCommand(name = "fmt") {
|
||||||
private val checkOnly by option("--check", help = "Check only; print files that would change").flag()
|
private val checkOnly by option("--check", help = "Check only; print files that would change").flag()
|
||||||
private val inPlace by option("-i", "--in-place", help = "Write changes back to files").flag()
|
private val inPlace by option("-i", "--in-place", help = "Write changes back to files").flag()
|
||||||
private val enableSpacing by option("--spacing", help = "Apply spacing normalization").flag()
|
private val enableSpacing by option("--spacing", help = "Apply spacing normalization").flag()
|
||||||
@ -336,7 +305,7 @@ private class Fmt : CoreCliktCommand(name = "fmt") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CoreCliktCommand() {
|
private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
||||||
|
|
||||||
override val invokeWithoutSubcommand = true
|
override val invokeWithoutSubcommand = true
|
||||||
override val printHelpOnEmptyArgs = true
|
override val printHelpOnEmptyArgs = true
|
||||||
@ -412,7 +381,6 @@ suspend fun executeSource(source: Source, initialScope: Scope? = null) {
|
|||||||
evalOnCliDispatcher(session, source)
|
evalOnCliDispatcher(session, source)
|
||||||
} finally {
|
} finally {
|
||||||
session.cancelAndJoin()
|
session.cancelAndJoin()
|
||||||
shutdownSystemNetEngine()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,172 +0,0 @@
|
|||||||
package net.sergeych
|
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import net.sergeych.lyng.EvalSession
|
|
||||||
import net.sergeych.lyng.Source
|
|
||||||
import net.sergeych.lyng.obj.ObjString
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
class CliTcpServerRegressionTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun reducedTcpServerExampleRunsWithCopiedCliImportManager() = runBlocking {
|
|
||||||
val cliScope = newCliScope(emptyList())
|
|
||||||
val session = EvalSession(cliScope)
|
|
||||||
|
|
||||||
try {
|
|
||||||
val result = evalOnCliDispatcher(
|
|
||||||
session,
|
|
||||||
Source(
|
|
||||||
"<tcp-server-regression>",
|
|
||||||
"""
|
|
||||||
import lyng.buffer
|
|
||||||
import lyng.io.net
|
|
||||||
|
|
||||||
val host = "127.0.0.1"
|
|
||||||
val server = Net.tcpListen(0, host)
|
|
||||||
val port = server.localAddress().port
|
|
||||||
val accepted = launch {
|
|
||||||
val client = server.accept()
|
|
||||||
val line = (client.read(4) as Buffer).decodeUtf8()
|
|
||||||
client.close()
|
|
||||||
server.close()
|
|
||||||
line
|
|
||||||
}
|
|
||||||
|
|
||||||
val socket = Net.tcpConnect(host, port)
|
|
||||||
socket.writeUtf8("ping")
|
|
||||||
socket.flush()
|
|
||||||
socket.close()
|
|
||||||
accepted.await()
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
assertEquals("ping", (result as ObjString).value)
|
|
||||||
} finally {
|
|
||||||
session.cancelAndJoin()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun concurrentTcpExampleRunsInCliScope() = runBlocking {
|
|
||||||
val cliScope = newCliScope(emptyList())
|
|
||||||
val session = EvalSession(cliScope)
|
|
||||||
|
|
||||||
try {
|
|
||||||
val result = evalOnCliDispatcher(
|
|
||||||
session,
|
|
||||||
Source(
|
|
||||||
"<tcp-server-concurrency-cli>",
|
|
||||||
"""
|
|
||||||
import lyng.io.net
|
|
||||||
|
|
||||||
val host = "127.0.0.1"
|
|
||||||
val clientCount = 32
|
|
||||||
val server: TcpServer = Net.tcpListen(0, host, 32, true) as TcpServer
|
|
||||||
val port: Int = server.localAddress().port
|
|
||||||
|
|
||||||
fun payloadFor(index: Int): String {
|
|
||||||
"${'$'}index:${'$'}{Random.nextInt()}:${'$'}{Random.nextInt()}"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleClient(client: TcpSocket): String {
|
|
||||||
try {
|
|
||||||
val source = client.readLine()
|
|
||||||
if( source == null ) {
|
|
||||||
return "server-eof"
|
|
||||||
}
|
|
||||||
val reply = "pong: ${'$'}source"
|
|
||||||
client.writeUtf8(reply + "\n")
|
|
||||||
client.flush()
|
|
||||||
reply
|
|
||||||
} finally {
|
|
||||||
client.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val serverJob: Deferred = launch {
|
|
||||||
var handlers: List<Deferred> = List()
|
|
||||||
try {
|
|
||||||
for( i in 0..<32 ) {
|
|
||||||
val client: TcpSocket = server.accept() as TcpSocket
|
|
||||||
handlers += launch {
|
|
||||||
handleClient(client)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handlers.joinAll()
|
|
||||||
} finally {
|
|
||||||
server.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val clientJobs = (0..<clientCount).map { index ->
|
|
||||||
val payload = payloadFor(index)
|
|
||||||
launch {
|
|
||||||
val socket: TcpSocket = Net.tcpConnect(host, port) as TcpSocket
|
|
||||||
try {
|
|
||||||
socket.writeUtf8(payload + "\n")
|
|
||||||
socket.flush()
|
|
||||||
val reply = socket.readLine()
|
|
||||||
if( reply == null ) {
|
|
||||||
"client-eof:${'$'}payload"
|
|
||||||
} else {
|
|
||||||
assertEquals("pong: ${'$'}payload", reply)
|
|
||||||
reply
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
socket.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val replies = clientJobs.joinAll()
|
|
||||||
val serverReplies = serverJob.await() as List<Object>
|
|
||||||
assertEquals(clientCount, replies.size)
|
|
||||||
assertEquals(clientCount, serverReplies.size)
|
|
||||||
assertEquals(replies.toSet, serverReplies.toSet)
|
|
||||||
"OK:${'$'}clientCount:${'$'}{replies.toSet}:${'$'}{serverReplies.toSet}"
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val text = (result as ObjString).value
|
|
||||||
assertTrue(text.startsWith("OK:32:"), text)
|
|
||||||
} finally {
|
|
||||||
session.cancelAndJoin()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun mixedModuleAndLocalCapturesWorkInCliScope() = runBlocking {
|
|
||||||
val cliScope = newCliScope(emptyList())
|
|
||||||
val session = EvalSession(cliScope)
|
|
||||||
|
|
||||||
try {
|
|
||||||
val result = evalOnCliDispatcher(
|
|
||||||
session,
|
|
||||||
Source(
|
|
||||||
"<cli-capture-regression>",
|
|
||||||
"""
|
|
||||||
val prefix = "pong"
|
|
||||||
val jobs = (0..<32).map { index ->
|
|
||||||
val payload = "${'$'}index:${'$'}{Random.nextInt()}"
|
|
||||||
launch {
|
|
||||||
delay(5)
|
|
||||||
"${'$'}prefix:${'$'}payload"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jobs.joinAll()
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
) as net.sergeych.lyng.obj.ObjList
|
|
||||||
|
|
||||||
assertEquals(32, result.list.size)
|
|
||||||
assertEquals(32, result.list.map { (it as ObjString).value }.toSet().size)
|
|
||||||
} finally {
|
|
||||||
session.cancelAndJoin()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -28,8 +28,6 @@ import java.net.InetAddress
|
|||||||
|
|
||||||
actual fun getSystemNetEngine(): LyngNetEngine = AndroidKtorNetEngine
|
actual fun getSystemNetEngine(): LyngNetEngine = AndroidKtorNetEngine
|
||||||
|
|
||||||
actual fun shutdownSystemNetEngine() {}
|
|
||||||
|
|
||||||
private object AndroidKtorNetEngine : LyngNetEngine {
|
private object AndroidKtorNetEngine : LyngNetEngine {
|
||||||
private val selectorManager: SelectorManager by lazy { ActorSelectorManager(Dispatchers.IO) }
|
private val selectorManager: SelectorManager by lazy { ActorSelectorManager(Dispatchers.IO) }
|
||||||
|
|
||||||
|
|||||||
@ -97,5 +97,3 @@ internal object UnsupportedLyngNetEngine : LyngNetEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect fun getSystemNetEngine(): LyngNetEngine
|
expect fun getSystemNetEngine(): LyngNetEngine
|
||||||
|
|
||||||
expect fun shutdownSystemNetEngine()
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
package net.sergeych.lyng.io.net
|
package net.sergeych.lyng.io.net
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import net.sergeych.lyng.Compiler
|
import net.sergeych.lyng.Compiler
|
||||||
@ -30,92 +30,44 @@ import kotlin.test.assertEquals
|
|||||||
|
|
||||||
class LyngNetTcpServerExampleTest {
|
class LyngNetTcpServerExampleTest {
|
||||||
|
|
||||||
private fun concurrentTcpScript(clientCount: Int): String = """
|
|
||||||
import lyng.io.net
|
|
||||||
|
|
||||||
val host = "127.0.0.1"
|
|
||||||
val clientCount = $clientCount
|
|
||||||
val server: TcpServer = Net.tcpListen(0, host, clientCount, true) as TcpServer
|
|
||||||
val port: Int = server.localAddress().port
|
|
||||||
|
|
||||||
fun payloadFor(index: Int): String {
|
|
||||||
"${'$'}index:${'$'}{Random.nextInt()}:${'$'}{Random.nextInt()}"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleClient(client: TcpSocket): String {
|
|
||||||
try {
|
|
||||||
val source = client.readLine()
|
|
||||||
if( source == null ) {
|
|
||||||
return "server-eof"
|
|
||||||
}
|
|
||||||
val reply = "pong: ${'$'}source"
|
|
||||||
client.writeUtf8(reply + "\n")
|
|
||||||
client.flush()
|
|
||||||
reply
|
|
||||||
} finally {
|
|
||||||
client.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val serverJob: Deferred = launch {
|
|
||||||
var handlers: List<Deferred> = List()
|
|
||||||
try {
|
|
||||||
for( i in 0..<${clientCount} ) {
|
|
||||||
val client: TcpSocket = server.accept() as TcpSocket
|
|
||||||
handlers += launch {
|
|
||||||
handleClient(client)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handlers.joinAll()
|
|
||||||
} finally {
|
|
||||||
server.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val clientJobs: List<Deferred> = (0..<clientCount).map { index ->
|
|
||||||
val payload = payloadFor(index)
|
|
||||||
launch {
|
|
||||||
val socket: TcpSocket = Net.tcpConnect(host, port) as TcpSocket
|
|
||||||
try {
|
|
||||||
socket.writeUtf8(payload + "\n")
|
|
||||||
socket.flush()
|
|
||||||
val reply = socket.readLine()
|
|
||||||
if( reply == null ) {
|
|
||||||
"client-eof:${'$'}payload"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
assertEquals("pong: ${'$'}payload", reply)
|
|
||||||
reply
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
socket.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val replies = clientJobs.joinAll()
|
|
||||||
val serverReplies = serverJob.await() as List<Object>
|
|
||||||
|
|
||||||
assertEquals(clientCount, replies.size)
|
|
||||||
assertEquals(clientCount, serverReplies.size)
|
|
||||||
assertEquals(replies.toSet, serverReplies.toSet)
|
|
||||||
"OK:${'$'}clientCount"
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun tcpServerExampleSurvivesConcurrentLoopbackLoad() = runBlocking {
|
fun tcpServerExampleRoundTripsOverLoopback() = runTest {
|
||||||
val engine = getSystemNetEngine()
|
val engine = getSystemNetEngine()
|
||||||
if (!engine.isSupported || !engine.isTcpAvailable || !engine.isTcpServerAvailable) return@runBlocking
|
if (!engine.isSupported || !engine.isTcpAvailable || !engine.isTcpServerAvailable) return@runTest
|
||||||
|
|
||||||
val scope = Script.newScope()
|
val scope = Script.newScope()
|
||||||
createNetModule(PermitAllNetAccessPolicy, scope)
|
createNetModule(PermitAllNetAccessPolicy, scope)
|
||||||
|
|
||||||
|
val code = """
|
||||||
|
import lyng.buffer
|
||||||
|
import lyng.io.net
|
||||||
|
|
||||||
|
val server = Net.tcpListen(0, "127.0.0.1")
|
||||||
|
val port = server.localAddress().port
|
||||||
|
val accepted = launch {
|
||||||
|
val client = server.accept()
|
||||||
|
val line = (client.read(4) as Buffer).decodeUtf8()
|
||||||
|
client.writeUtf8("echo:" + line)
|
||||||
|
client.flush()
|
||||||
|
client.close()
|
||||||
|
server.close()
|
||||||
|
line
|
||||||
|
}
|
||||||
|
|
||||||
|
val socket = Net.tcpConnect("127.0.0.1", port)
|
||||||
|
socket.writeUtf8("ping")
|
||||||
|
socket.flush()
|
||||||
|
val reply = (socket.read(16) as Buffer).decodeUtf8()
|
||||||
|
socket.close()
|
||||||
|
"${'$'}{accepted.await()}: ${'$'}reply"
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
val result = withContext(Dispatchers.Default) {
|
val result = withContext(Dispatchers.Default) {
|
||||||
withTimeout(20_000) {
|
withTimeout(5_000) {
|
||||||
Compiler.compile(concurrentTcpScript(clientCount = 32)).execute(scope).inspect(scope)
|
Compiler.compile(code).execute(scope).inspect(scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals("\"OK:32\"", result)
|
assertEquals("\"ping: echo:ping\"", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
package net.sergeych.lyngio.net
|
|
||||||
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.awaitAll
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withTimeout
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
class NetConcurrentLoopbackTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun concurrentTcpRoundTripsWorkAtEngineLevel() = runBlocking {
|
|
||||||
val engine = getSystemNetEngine()
|
|
||||||
if (!engine.isSupported || !engine.isTcpAvailable || !engine.isTcpServerAvailable) return@runBlocking
|
|
||||||
|
|
||||||
withTimeout(10_000) {
|
|
||||||
val clients = 32
|
|
||||||
val server = engine.tcpListen(host = "127.0.0.1", port = 0, backlog = 64, reuseAddress = true)
|
|
||||||
val serverJob = async {
|
|
||||||
coroutineScope {
|
|
||||||
val handlers = ArrayList<kotlinx.coroutines.Deferred<String>>(clients)
|
|
||||||
repeat(clients) {
|
|
||||||
val client = server.accept()
|
|
||||||
handlers += async {
|
|
||||||
try {
|
|
||||||
val payload = client.readLine()
|
|
||||||
val reply = "pong:$payload"
|
|
||||||
client.writeUtf8("$reply\n")
|
|
||||||
client.flush()
|
|
||||||
reply
|
|
||||||
} finally {
|
|
||||||
client.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handlers.awaitAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val clientJobs = coroutineScope {
|
|
||||||
(0 until clients).map { index ->
|
|
||||||
async {
|
|
||||||
val payload = "ping:$index"
|
|
||||||
val socket = engine.tcpConnect("127.0.0.1", server.localAddress().port, timeoutMillis = 2_000, noDelay = true)
|
|
||||||
try {
|
|
||||||
socket.writeUtf8("$payload\n")
|
|
||||||
socket.flush()
|
|
||||||
socket.readLine()
|
|
||||||
} finally {
|
|
||||||
socket.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val clientReplies = clientJobs.awaitAll()
|
|
||||||
val serverReplies = serverJob.await()
|
|
||||||
server.close()
|
|
||||||
|
|
||||||
assertEquals((0 until clients).map { "pong:ping:$it" }, clientReplies)
|
|
||||||
assertEquals((0 until clients).map { "pong:ping:$it" }, serverReplies)
|
|
||||||
assertTrue(clientReplies.all { it != null })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +1,8 @@
|
|||||||
package net.sergeych.lyngio.net
|
package net.sergeych.lyngio.net
|
||||||
|
|
||||||
private val systemNetEngine: LyngNetEngine = createNativeKtorNetEngine(
|
actual fun getSystemNetEngine(): LyngNetEngine = createNativeKtorNetEngine(
|
||||||
isSupported = true,
|
isSupported = true,
|
||||||
isTcpAvailable = true,
|
isTcpAvailable = true,
|
||||||
isTcpServerAvailable = true,
|
isTcpServerAvailable = true,
|
||||||
isUdpAvailable = true,
|
isUdpAvailable = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
actual fun getSystemNetEngine(): LyngNetEngine = systemNetEngine
|
|
||||||
|
|
||||||
actual fun shutdownSystemNetEngine() {
|
|
||||||
shutdownNativeKtorNetEngine(systemNetEngine)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -13,8 +13,6 @@ import org.khronos.webgl.Uint8Array
|
|||||||
|
|
||||||
actual fun getSystemNetEngine(): LyngNetEngine = jsNodeNetEngineOrNull ?: UnsupportedLyngNetEngine
|
actual fun getSystemNetEngine(): LyngNetEngine = jsNodeNetEngineOrNull ?: UnsupportedLyngNetEngine
|
||||||
|
|
||||||
actual fun shutdownSystemNetEngine() {}
|
|
||||||
|
|
||||||
private val jsNodeNetEngineOrNull: LyngNetEngine? by lazy {
|
private val jsNodeNetEngineOrNull: LyngNetEngine? by lazy {
|
||||||
if (!isNodeRuntime()) return@lazy null
|
if (!isNodeRuntime()) return@lazy null
|
||||||
val net = requireNodeModule("net") ?: return@lazy null
|
val net = requireNodeModule("net") ?: return@lazy null
|
||||||
|
|||||||
@ -45,8 +45,6 @@ import java.net.InetAddress
|
|||||||
|
|
||||||
actual fun getSystemNetEngine(): LyngNetEngine = JvmKtorNetEngine
|
actual fun getSystemNetEngine(): LyngNetEngine = JvmKtorNetEngine
|
||||||
|
|
||||||
actual fun shutdownSystemNetEngine() {}
|
|
||||||
|
|
||||||
private object JvmKtorNetEngine : LyngNetEngine {
|
private object JvmKtorNetEngine : LyngNetEngine {
|
||||||
private val selectorManager: SelectorManager by lazy { ActorSelectorManager(Dispatchers.IO) }
|
private val selectorManager: SelectorManager by lazy { ActorSelectorManager(Dispatchers.IO) }
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,8 @@
|
|||||||
package net.sergeych.lyngio.net
|
package net.sergeych.lyngio.net
|
||||||
|
|
||||||
private val systemNetEngine: LyngNetEngine = createNativeKtorNetEngine(
|
actual fun getSystemNetEngine(): LyngNetEngine = createNativeKtorNetEngine(
|
||||||
isSupported = true,
|
isSupported = true,
|
||||||
isTcpAvailable = true,
|
isTcpAvailable = true,
|
||||||
isTcpServerAvailable = true,
|
isTcpServerAvailable = true,
|
||||||
isUdpAvailable = true,
|
isUdpAvailable = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
actual fun getSystemNetEngine(): LyngNetEngine = systemNetEngine
|
|
||||||
|
|
||||||
actual fun shutdownSystemNetEngine() {
|
|
||||||
shutdownNativeKtorNetEngine(systemNetEngine)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
package net.sergeych.lyngio.net
|
package net.sergeych.lyngio.net
|
||||||
|
|
||||||
actual fun getSystemNetEngine(): LyngNetEngine = UnsupportedLyngNetEngine
|
actual fun getSystemNetEngine(): LyngNetEngine = UnsupportedLyngNetEngine
|
||||||
|
|
||||||
actual fun shutdownSystemNetEngine() {}
|
|
||||||
|
|||||||
@ -40,10 +40,7 @@ private class NativeKtorNetEngine(
|
|||||||
override val isTcpServerAvailable: Boolean,
|
override val isTcpServerAvailable: Boolean,
|
||||||
override val isUdpAvailable: Boolean,
|
override val isUdpAvailable: Boolean,
|
||||||
) : LyngNetEngine {
|
) : LyngNetEngine {
|
||||||
private var selectorManager: SelectorManager? = null
|
private val selectorManager: SelectorManager by lazy { SelectorManager(Dispatchers.Default) }
|
||||||
|
|
||||||
private fun selectorManager(): SelectorManager =
|
|
||||||
selectorManager ?: SelectorManager(Dispatchers.Default).also { selectorManager = it }
|
|
||||||
|
|
||||||
override suspend fun resolve(host: String, port: Int): List<LyngSocketAddress> {
|
override suspend fun resolve(host: String, port: Int): List<LyngSocketAddress> {
|
||||||
val rawAddress = InetSocketAddress(host, port).resolveAddress()
|
val rawAddress = InetSocketAddress(host, port).resolveAddress()
|
||||||
@ -65,7 +62,7 @@ private class NativeKtorNetEngine(
|
|||||||
noDelay: Boolean,
|
noDelay: Boolean,
|
||||||
): LyngTcpSocket {
|
): LyngTcpSocket {
|
||||||
val connectBlock: suspend () -> Socket = {
|
val connectBlock: suspend () -> Socket = {
|
||||||
aSocket(selectorManager()).tcp().connect(host, port) {
|
aSocket(selectorManager).tcp().connect(host, port) {
|
||||||
this.noDelay = noDelay
|
this.noDelay = noDelay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,7 +77,7 @@ private class NativeKtorNetEngine(
|
|||||||
reuseAddress: Boolean,
|
reuseAddress: Boolean,
|
||||||
): LyngTcpServer {
|
): LyngTcpServer {
|
||||||
val bindHost = host ?: "0.0.0.0"
|
val bindHost = host ?: "0.0.0.0"
|
||||||
val server = aSocket(selectorManager()).tcp().bind(bindHost, port) {
|
val server = aSocket(selectorManager).tcp().bind(bindHost, port) {
|
||||||
backlogSize = backlog
|
backlogSize = backlog
|
||||||
this.reuseAddress = reuseAddress
|
this.reuseAddress = reuseAddress
|
||||||
}
|
}
|
||||||
@ -89,16 +86,11 @@ private class NativeKtorNetEngine(
|
|||||||
|
|
||||||
override suspend fun udpBind(host: String?, port: Int, reuseAddress: Boolean): LyngUdpSocket {
|
override suspend fun udpBind(host: String?, port: Int, reuseAddress: Boolean): LyngUdpSocket {
|
||||||
val bindHost = host ?: "0.0.0.0"
|
val bindHost = host ?: "0.0.0.0"
|
||||||
val socket = aSocket(selectorManager()).udp().bind(bindHost, port) {
|
val socket = aSocket(selectorManager).udp().bind(bindHost, port) {
|
||||||
this.reuseAddress = reuseAddress
|
this.reuseAddress = reuseAddress
|
||||||
}
|
}
|
||||||
return NativeLyngUdpSocket(socket)
|
return NativeLyngUdpSocket(socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shutdown() {
|
|
||||||
selectorManager?.close()
|
|
||||||
selectorManager = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NativeLyngTcpSocket(
|
private class NativeLyngTcpSocket(
|
||||||
@ -222,7 +214,3 @@ private fun ByteArray.toIpHostString(): String = when (size) {
|
|||||||
}
|
}
|
||||||
else -> error("Unsupported IP address length: $size")
|
else -> error("Unsupported IP address length: $size")
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun shutdownNativeKtorNetEngine(engine: LyngNetEngine) {
|
|
||||||
(engine as? NativeKtorNetEngine)?.shutdown()
|
|
||||||
}
|
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import net.sergeych.lyng.bridge.bind
|
|||||||
import net.sergeych.lyng.bridge.bindObject
|
import net.sergeych.lyng.bridge.bindObject
|
||||||
import net.sergeych.lyng.bytecode.CmdFunction
|
import net.sergeych.lyng.bytecode.CmdFunction
|
||||||
import net.sergeych.lyng.bytecode.CmdVm
|
import net.sergeych.lyng.bytecode.CmdVm
|
||||||
import net.sergeych.lyng.bytecode.BytecodeLambdaCallable
|
|
||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.pacman.ImportManager
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
@ -636,20 +635,16 @@ class Script(
|
|||||||
addConst("MapEntry", ObjMapEntry.type)
|
addConst("MapEntry", ObjMapEntry.type)
|
||||||
|
|
||||||
addFn("launch") {
|
addFn("launch") {
|
||||||
val rawCallable = requireOnlyArg<Obj>()
|
val callable = requireOnlyArg<Obj>()
|
||||||
val currentScope = requireScope()
|
val captured = this
|
||||||
// Freeze non-module lexical state at launch time so each coroutine gets
|
|
||||||
// its own view of loop locals and other transient frame values.
|
|
||||||
val captured = if (currentScope is ModuleScope) currentScope else currentScope.snapshotForClosure()
|
|
||||||
val callable = (rawCallable as? BytecodeLambdaCallable)?.freezeForLaunch(captured) ?: rawCallable
|
|
||||||
val session = EvalSession.currentOrNull()
|
val session = EvalSession.currentOrNull()
|
||||||
val deferred = if (session != null) {
|
val deferred = if (session != null) {
|
||||||
session.launchTrackedDeferred {
|
session.launchTrackedDeferred {
|
||||||
ScopeBridge(captured).call(callable)
|
captured.call(callable)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
globalDefer {
|
globalDefer {
|
||||||
ScopeBridge(captured).call(callable)
|
captured.call(callable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ObjDeferred(deferred)
|
ObjDeferred(deferred)
|
||||||
|
|||||||
@ -1034,18 +1034,15 @@ class BytecodeCompiler(
|
|||||||
val typeValue = compileRefWithFallback(ref.castTypeRef(), null, ref.castPos()) ?: return null
|
val typeValue = compileRefWithFallback(ref.castTypeRef(), null, ref.castPos()) ?: return null
|
||||||
val objValue = ensureObjSlot(value)
|
val objValue = ensureObjSlot(value)
|
||||||
val typeObj = ensureObjSlot(typeValue)
|
val typeObj = ensureObjSlot(typeValue)
|
||||||
val sourceSlot = allocSlot()
|
|
||||||
builder.emit(Opcode.MOVE_OBJ, objValue.slot, sourceSlot)
|
|
||||||
updateSlotType(sourceSlot, SlotType.OBJ)
|
|
||||||
if (!ref.castIsNullable()) {
|
if (!ref.castIsNullable()) {
|
||||||
builder.emit(Opcode.ASSERT_IS, sourceSlot, typeObj.slot)
|
builder.emit(Opcode.ASSERT_IS, objValue.slot, typeObj.slot)
|
||||||
val resultSlot = allocSlot()
|
val resultSlot = allocSlot()
|
||||||
builder.emit(Opcode.MAKE_QUALIFIED_VIEW, sourceSlot, typeObj.slot, resultSlot)
|
builder.emit(Opcode.MAKE_QUALIFIED_VIEW, objValue.slot, typeObj.slot, resultSlot)
|
||||||
updateSlotType(resultSlot, SlotType.OBJ)
|
updateSlotType(resultSlot, SlotType.OBJ)
|
||||||
return CompiledValue(resultSlot, SlotType.OBJ)
|
return CompiledValue(resultSlot, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
val checkSlot = allocSlot()
|
val checkSlot = allocSlot()
|
||||||
builder.emit(Opcode.CHECK_IS, sourceSlot, typeObj.slot, checkSlot)
|
builder.emit(Opcode.CHECK_IS, objValue.slot, typeObj.slot, checkSlot)
|
||||||
updateSlotType(checkSlot, SlotType.BOOL)
|
updateSlotType(checkSlot, SlotType.BOOL)
|
||||||
val resultSlot = allocSlot()
|
val resultSlot = allocSlot()
|
||||||
val nullSlot = allocSlot()
|
val nullSlot = allocSlot()
|
||||||
@ -1059,7 +1056,7 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.MOVE_OBJ, nullSlot, resultSlot)
|
builder.emit(Opcode.MOVE_OBJ, nullSlot, resultSlot)
|
||||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||||
builder.mark(okLabel)
|
builder.mark(okLabel)
|
||||||
builder.emit(Opcode.MAKE_QUALIFIED_VIEW, sourceSlot, typeObj.slot, resultSlot)
|
builder.emit(Opcode.MAKE_QUALIFIED_VIEW, objValue.slot, typeObj.slot, resultSlot)
|
||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
updateSlotType(resultSlot, SlotType.OBJ)
|
updateSlotType(resultSlot, SlotType.OBJ)
|
||||||
return CompiledValue(resultSlot, SlotType.OBJ)
|
return CompiledValue(resultSlot, SlotType.OBJ)
|
||||||
@ -7820,9 +7817,7 @@ class BytecodeCompiler(
|
|||||||
scopeSlotRefPosByKey[scopeKey] = ref.pos()
|
scopeSlotRefPosByKey[scopeKey] = ref.pos()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (resolved != null) return resolved
|
return resolved
|
||||||
localSlotIndexByName[ref.name]?.let { return scopeSlotCount + it }
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveLocalSlotByRefOrName(ref: LocalSlotRef): Int? {
|
private fun resolveLocalSlotByRefOrName(ref: LocalSlotRef): Int? {
|
||||||
@ -8674,11 +8669,6 @@ class BytecodeCompiler(
|
|||||||
collectLoopVarNamesRef(ref.targetRef)
|
collectLoopVarNamesRef(ref.targetRef)
|
||||||
collectLoopVarNamesRef(ref.indexRef)
|
collectLoopVarNamesRef(ref.indexRef)
|
||||||
}
|
}
|
||||||
is RangeRef -> {
|
|
||||||
ref.left?.let { collectLoopVarNamesRef(it) }
|
|
||||||
ref.right?.let { collectLoopVarNamesRef(it) }
|
|
||||||
ref.step?.let { collectLoopVarNamesRef(it) }
|
|
||||||
}
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8805,11 +8795,6 @@ class BytecodeCompiler(
|
|||||||
collectScopeSlotsRef(ref.targetRef)
|
collectScopeSlotsRef(ref.targetRef)
|
||||||
collectScopeSlotsRef(ref.indexRef)
|
collectScopeSlotsRef(ref.indexRef)
|
||||||
}
|
}
|
||||||
is RangeRef -> {
|
|
||||||
ref.left?.let { collectScopeSlotsRef(it) }
|
|
||||||
ref.right?.let { collectScopeSlotsRef(it) }
|
|
||||||
ref.step?.let { collectScopeSlotsRef(it) }
|
|
||||||
}
|
|
||||||
is ClassOperatorRef -> {
|
is ClassOperatorRef -> {
|
||||||
collectScopeSlotsRef(ref.target)
|
collectScopeSlotsRef(ref.target)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3783,48 +3783,11 @@ class BytecodeLambdaCallable(
|
|||||||
private val returnLabels: Set<String>,
|
private val returnLabels: Set<String>,
|
||||||
override val pos: Pos,
|
override val pos: Pos,
|
||||||
) : Statement(), BytecodeCallable {
|
) : Statement(), BytecodeCallable {
|
||||||
private fun freezeRecord(record: ObjRecord): ObjRecord {
|
|
||||||
val frozenValue = when (val raw = record.value) {
|
|
||||||
is net.sergeych.lyng.FrameSlotRef -> raw.read()
|
|
||||||
is net.sergeych.lyng.RecordSlotRef -> raw.read()
|
|
||||||
is net.sergeych.lyng.ScopeSlotRef -> raw.read()
|
|
||||||
else -> raw
|
|
||||||
}
|
|
||||||
return record.copy(value = frozenValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resolveCaptureRecords(base: Scope): List<ObjRecord>? {
|
|
||||||
if (captureNames.isEmpty()) return null
|
|
||||||
return captureNames.map { name ->
|
|
||||||
base.chainLookupIgnoreClosure(
|
|
||||||
name,
|
|
||||||
followClosure = true,
|
|
||||||
caller = base.currentClassCtx
|
|
||||||
) ?: base.raiseSymbolNotFound("symbol $name not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun rebindClosure(newClosureScope: Scope): BytecodeLambdaCallable {
|
fun rebindClosure(newClosureScope: Scope): BytecodeLambdaCallable {
|
||||||
return BytecodeLambdaCallable(
|
return BytecodeLambdaCallable(
|
||||||
fn = fn,
|
fn = fn,
|
||||||
closureScope = newClosureScope,
|
closureScope = newClosureScope,
|
||||||
captureRecords = resolveCaptureRecords(newClosureScope) ?: captureRecords,
|
captureRecords = captureRecords,
|
||||||
captureNames = captureNames,
|
|
||||||
paramSlotPlan = paramSlotPlan,
|
|
||||||
argsDeclaration = argsDeclaration,
|
|
||||||
preferredThisType = preferredThisType,
|
|
||||||
returnLabels = returnLabels,
|
|
||||||
pos = pos
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun freezeForLaunch(newClosureScope: Scope): BytecodeLambdaCallable {
|
|
||||||
val frozenCaptures = captureRecords?.map(::freezeRecord)
|
|
||||||
?: resolveCaptureRecords(newClosureScope)?.map(::freezeRecord)
|
|
||||||
return BytecodeLambdaCallable(
|
|
||||||
fn = fn,
|
|
||||||
closureScope = newClosureScope,
|
|
||||||
captureRecords = frozenCaptures,
|
|
||||||
captureNames = captureNames,
|
captureNames = captureNames,
|
||||||
paramSlotPlan = paramSlotPlan,
|
paramSlotPlan = paramSlotPlan,
|
||||||
argsDeclaration = argsDeclaration,
|
argsDeclaration = argsDeclaration,
|
||||||
|
|||||||
@ -151,15 +151,7 @@ class ImportManager(
|
|||||||
fun copy(): ImportManager =
|
fun copy(): ImportManager =
|
||||||
op.withLock {
|
op.withLock {
|
||||||
ImportManager(rootScope, securityManager).apply {
|
ImportManager(rootScope, securityManager).apply {
|
||||||
for ((name, entry) in this@ImportManager.imports) {
|
imports.putAll(this@ImportManager.imports)
|
||||||
imports[name] = Entry(entry.packageName, entry.builder, entry.cachedScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun invalidatePackageCache(name: String) {
|
|
||||||
op.withLock {
|
|
||||||
imports[name]?.cachedScope = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -120,104 +120,6 @@ class TestCoroutines {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testJoinAll() = runTest {
|
|
||||||
eval(
|
|
||||||
"""
|
|
||||||
val replies = (1..6).map { n ->
|
|
||||||
launch {
|
|
||||||
delay((7 - n) * 5)
|
|
||||||
"done:${'$'}n"
|
|
||||||
}
|
|
||||||
}.joinAll()
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
["done:1", "done:2", "done:3", "done:4", "done:5", "done:6"],
|
|
||||||
replies
|
|
||||||
)
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLaunchCapturesDistinctLoopValues() = runTest {
|
|
||||||
eval(
|
|
||||||
"""
|
|
||||||
val jobs = []
|
|
||||||
for( i in 0..<8 ) {
|
|
||||||
val x = i
|
|
||||||
jobs += launch {
|
|
||||||
delay(5)
|
|
||||||
x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals([0,1,2,3,4,5,6,7], jobs.joinAll())
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testNestedLaunchCapturesDistinctLoopValues() = runTest {
|
|
||||||
eval(
|
|
||||||
"""
|
|
||||||
val outer = launch {
|
|
||||||
val jobs = []
|
|
||||||
for( i in 0..<8 ) {
|
|
||||||
val x = i
|
|
||||||
jobs += launch {
|
|
||||||
delay(5)
|
|
||||||
x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jobs.joinAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals([0,1,2,3,4,5,6,7], outer.await())
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLaunchCapturesDistinctObjectValues() = runTest {
|
|
||||||
eval(
|
|
||||||
"""
|
|
||||||
val jobs = []
|
|
||||||
for( i in 0..<8 ) {
|
|
||||||
val box = ["item:${'$'}i"]
|
|
||||||
jobs += launch {
|
|
||||||
delay(5)
|
|
||||||
box[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
["item:0", "item:1", "item:2", "item:3", "item:4", "item:5", "item:6", "item:7"],
|
|
||||||
jobs.joinAll()
|
|
||||||
)
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLaunchCanUseCapturedRangeBoundInForLoop() = runTest {
|
|
||||||
eval(
|
|
||||||
"""
|
|
||||||
val count = 8
|
|
||||||
val outer = launch {
|
|
||||||
val jobs = []
|
|
||||||
for( i in 0..<count ) {
|
|
||||||
val x = i
|
|
||||||
jobs += launch { x }
|
|
||||||
}
|
|
||||||
jobs.joinAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals([0,1,2,3,4,5,6,7], outer.await())
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testFlows() = runTest {
|
fun testFlows() = runTest {
|
||||||
eval("""
|
eval("""
|
||||||
|
|||||||
@ -29,16 +29,6 @@ extern class Deferred {
|
|||||||
val isCancelled: Bool
|
val isCancelled: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Await every task in order and return collected results. */
|
|
||||||
fun Iterable<Deferred>.joinAll(): List<Object> {
|
|
||||||
var results: List<Object> = List()
|
|
||||||
for( task in this ) {
|
|
||||||
val deferred = task as Deferred
|
|
||||||
results += deferred.await()
|
|
||||||
}
|
|
||||||
results
|
|
||||||
}
|
|
||||||
|
|
||||||
/* A deferred result that can be completed manually. */
|
/* A deferred result that can be completed manually. */
|
||||||
extern class CompletableDeferred : Deferred {
|
extern class CompletableDeferred : Deferred {
|
||||||
fun complete(value: Object): void
|
fun complete(value: Object): void
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user