Fix native CLI TCP regression path
This commit is contained in:
parent
aa1b74620e
commit
402b8bb1b3
68
examples/tcp-server.lyng
Normal file
68
examples/tcp-server.lyng
Normal file
@ -0,0 +1,68 @@
|
||||
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")
|
||||
@ -18,6 +18,7 @@ slf4j = "2.0.17"
|
||||
|
||||
[libraries]
|
||||
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" }
|
||||
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" }
|
||||
|
||||
@ -52,6 +52,12 @@ kotlin {
|
||||
linuxX64 {
|
||||
binaries {
|
||||
executable()
|
||||
all {
|
||||
if (buildType == org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.RELEASE) {
|
||||
debuggable = true
|
||||
optimized = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
@ -63,7 +69,7 @@ kotlin {
|
||||
// filesystem access into the execution Scope by default.
|
||||
implementation(project(":lyngio"))
|
||||
implementation(libs.okio)
|
||||
implementation(libs.clikt)
|
||||
implementation(libs.clikt.core)
|
||||
implementation(kotlin("stdlib-common"))
|
||||
// optional support for rendering markdown in help messages
|
||||
// implementation(libs.clikt.markdown)
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
package net.sergeych
|
||||
|
||||
import com.github.ajalt.clikt.core.CliktCommand
|
||||
import com.github.ajalt.clikt.core.CoreCliktCommand
|
||||
import com.github.ajalt.clikt.core.Context
|
||||
import com.github.ajalt.clikt.core.main
|
||||
import com.github.ajalt.clikt.core.subcommands
|
||||
@ -43,6 +43,7 @@ import net.sergeych.lyng.io.net.createNetModule
|
||||
import net.sergeych.lyng.io.ws.createWsModule
|
||||
import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
import net.sergeych.lyngio.net.shutdownSystemNetEngine
|
||||
import net.sergeych.lyngio.console.security.PermitAllConsoleAccessPolicy
|
||||
import net.sergeych.lyngio.fs.security.PermitAllAccessPolicy
|
||||
import net.sergeych.lyngio.http.security.PermitAllHttpAccessPolicy
|
||||
@ -70,10 +71,18 @@ data class CommandResult(
|
||||
val error: String
|
||||
)
|
||||
|
||||
private val baseCliImportManagerDefer = globalDefer {
|
||||
val manager = Script.defaultImportManager.copy().apply {
|
||||
installCliModules(this)
|
||||
}
|
||||
manager.newStdScope()
|
||||
manager
|
||||
}
|
||||
|
||||
val baseScopeDefer = globalDefer {
|
||||
Script.newScope().apply {
|
||||
baseCliImportManagerDefer.await().copy().newStdScope().apply {
|
||||
installCliBuiltins()
|
||||
installCliModules(importManager)
|
||||
addConst("ARGV", ObjList(mutableListOf()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,23 +217,33 @@ private fun discoverLocalCliModules(entryFile: Path): List<LocalCliModule> {
|
||||
.toList()
|
||||
}
|
||||
|
||||
private fun registerLocalCliModules(manager: ImportManager, entryFile: Path) {
|
||||
for (module in discoverLocalCliModules(entryFile)) {
|
||||
private fun registerLocalCliModules(manager: ImportManager, modules: List<LocalCliModule>) {
|
||||
for (module in modules) {
|
||||
manager.addPackage(module.packageName) { scope ->
|
||||
scope.eval(module.source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun newCliScope(argv: List<String>, entryFileName: String? = null): Scope {
|
||||
val manager = baseScopeDefer.await().importManager.copy()
|
||||
if (entryFileName != null) {
|
||||
registerLocalCliModules(manager, canonicalPath(entryFileName.toPath()))
|
||||
}
|
||||
return manager.newStdScope().apply {
|
||||
private suspend fun ImportManager.newCliScope(argv: List<String>): Scope =
|
||||
newStdScope().apply {
|
||||
installCliBuiltins()
|
||||
addConst("ARGV", ObjList(argv.map { ObjString(it) }.toMutableList()))
|
||||
}
|
||||
|
||||
internal suspend fun newCliScope(argv: List<String>, entryFileName: String? = null): Scope {
|
||||
val baseManager = baseCliImportManagerDefer.await()
|
||||
if (entryFileName == null) {
|
||||
return baseManager.newCliScope(argv)
|
||||
}
|
||||
val entryFile = canonicalPath(entryFileName.toPath())
|
||||
val localModules = discoverLocalCliModules(entryFile)
|
||||
if (localModules.isEmpty()) {
|
||||
return baseManager.newCliScope(argv)
|
||||
}
|
||||
val manager = baseManager.copy()
|
||||
registerLocalCliModules(manager, localModules)
|
||||
return manager.newCliScope(argv)
|
||||
}
|
||||
|
||||
fun runMain(args: Array<String>) {
|
||||
@ -248,7 +267,7 @@ fun runMain(args: Array<String>) {
|
||||
.main(args)
|
||||
}
|
||||
|
||||
private class Fmt : CliktCommand(name = "fmt") {
|
||||
private class Fmt : CoreCliktCommand(name = "fmt") {
|
||||
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 enableSpacing by option("--spacing", help = "Apply spacing normalization").flag()
|
||||
@ -306,7 +325,7 @@ private class Fmt : CliktCommand(name = "fmt") {
|
||||
}
|
||||
}
|
||||
|
||||
private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
||||
private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CoreCliktCommand() {
|
||||
|
||||
override val invokeWithoutSubcommand = true
|
||||
override val printHelpOnEmptyArgs = true
|
||||
@ -382,6 +401,7 @@ suspend fun executeSource(source: Source, initialScope: Scope? = null) {
|
||||
evalOnCliDispatcher(session, source)
|
||||
} finally {
|
||||
session.cancelAndJoin()
|
||||
shutdownSystemNetEngine()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
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
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -28,6 +28,8 @@ import java.net.InetAddress
|
||||
|
||||
actual fun getSystemNetEngine(): LyngNetEngine = AndroidKtorNetEngine
|
||||
|
||||
actual fun shutdownSystemNetEngine() {}
|
||||
|
||||
private object AndroidKtorNetEngine : LyngNetEngine {
|
||||
private val selectorManager: SelectorManager by lazy { ActorSelectorManager(Dispatchers.IO) }
|
||||
|
||||
|
||||
@ -97,3 +97,5 @@ internal object UnsupportedLyngNetEngine : LyngNetEngine {
|
||||
}
|
||||
|
||||
expect fun getSystemNetEngine(): LyngNetEngine
|
||||
|
||||
expect fun shutdownSystemNetEngine()
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
package net.sergeych.lyngio.net
|
||||
|
||||
actual fun getSystemNetEngine(): LyngNetEngine = createNativeKtorNetEngine(
|
||||
private val systemNetEngine: LyngNetEngine = createNativeKtorNetEngine(
|
||||
isSupported = true,
|
||||
isTcpAvailable = true,
|
||||
isTcpServerAvailable = true,
|
||||
isUdpAvailable = true,
|
||||
)
|
||||
|
||||
actual fun getSystemNetEngine(): LyngNetEngine = systemNetEngine
|
||||
|
||||
actual fun shutdownSystemNetEngine() {
|
||||
shutdownNativeKtorNetEngine(systemNetEngine)
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ import org.khronos.webgl.Uint8Array
|
||||
|
||||
actual fun getSystemNetEngine(): LyngNetEngine = jsNodeNetEngineOrNull ?: UnsupportedLyngNetEngine
|
||||
|
||||
actual fun shutdownSystemNetEngine() {}
|
||||
|
||||
private val jsNodeNetEngineOrNull: LyngNetEngine? by lazy {
|
||||
if (!isNodeRuntime()) return@lazy null
|
||||
val net = requireNodeModule("net") ?: return@lazy null
|
||||
|
||||
@ -45,6 +45,8 @@ import java.net.InetAddress
|
||||
|
||||
actual fun getSystemNetEngine(): LyngNetEngine = JvmKtorNetEngine
|
||||
|
||||
actual fun shutdownSystemNetEngine() {}
|
||||
|
||||
private object JvmKtorNetEngine : LyngNetEngine {
|
||||
private val selectorManager: SelectorManager by lazy { ActorSelectorManager(Dispatchers.IO) }
|
||||
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
package net.sergeych.lyngio.net
|
||||
|
||||
actual fun getSystemNetEngine(): LyngNetEngine = createNativeKtorNetEngine(
|
||||
private val systemNetEngine: LyngNetEngine = createNativeKtorNetEngine(
|
||||
isSupported = true,
|
||||
isTcpAvailable = true,
|
||||
isTcpServerAvailable = true,
|
||||
isUdpAvailable = true,
|
||||
)
|
||||
|
||||
actual fun getSystemNetEngine(): LyngNetEngine = systemNetEngine
|
||||
|
||||
actual fun shutdownSystemNetEngine() {
|
||||
shutdownNativeKtorNetEngine(systemNetEngine)
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
package net.sergeych.lyngio.net
|
||||
|
||||
actual fun getSystemNetEngine(): LyngNetEngine = UnsupportedLyngNetEngine
|
||||
|
||||
actual fun shutdownSystemNetEngine() {}
|
||||
|
||||
@ -40,7 +40,10 @@ private class NativeKtorNetEngine(
|
||||
override val isTcpServerAvailable: Boolean,
|
||||
override val isUdpAvailable: Boolean,
|
||||
) : LyngNetEngine {
|
||||
private val selectorManager: SelectorManager by lazy { SelectorManager(Dispatchers.Default) }
|
||||
private var selectorManager: SelectorManager? = null
|
||||
|
||||
private fun selectorManager(): SelectorManager =
|
||||
selectorManager ?: SelectorManager(Dispatchers.Default).also { selectorManager = it }
|
||||
|
||||
override suspend fun resolve(host: String, port: Int): List<LyngSocketAddress> {
|
||||
val rawAddress = InetSocketAddress(host, port).resolveAddress()
|
||||
@ -62,7 +65,7 @@ private class NativeKtorNetEngine(
|
||||
noDelay: Boolean,
|
||||
): LyngTcpSocket {
|
||||
val connectBlock: suspend () -> Socket = {
|
||||
aSocket(selectorManager).tcp().connect(host, port) {
|
||||
aSocket(selectorManager()).tcp().connect(host, port) {
|
||||
this.noDelay = noDelay
|
||||
}
|
||||
}
|
||||
@ -77,7 +80,7 @@ private class NativeKtorNetEngine(
|
||||
reuseAddress: Boolean,
|
||||
): LyngTcpServer {
|
||||
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
|
||||
this.reuseAddress = reuseAddress
|
||||
}
|
||||
@ -86,11 +89,16 @@ private class NativeKtorNetEngine(
|
||||
|
||||
override suspend fun udpBind(host: String?, port: Int, reuseAddress: Boolean): LyngUdpSocket {
|
||||
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
|
||||
}
|
||||
return NativeLyngUdpSocket(socket)
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
selectorManager?.close()
|
||||
selectorManager = null
|
||||
}
|
||||
}
|
||||
|
||||
private class NativeLyngTcpSocket(
|
||||
@ -214,3 +222,7 @@ private fun ByteArray.toIpHostString(): String = when (size) {
|
||||
}
|
||||
else -> error("Unsupported IP address length: $size")
|
||||
}
|
||||
|
||||
internal fun shutdownNativeKtorNetEngine(engine: LyngNetEngine) {
|
||||
(engine as? NativeKtorNetEngine)?.shutdown()
|
||||
}
|
||||
|
||||
@ -151,8 +151,10 @@ class ImportManager(
|
||||
fun copy(): ImportManager =
|
||||
op.withLock {
|
||||
ImportManager(rootScope, securityManager).apply {
|
||||
imports.putAll(this@ImportManager.imports)
|
||||
for ((name, entry) in this@ImportManager.imports) {
|
||||
imports[name] = Entry(entry.packageName, entry.builder, entry.cachedScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user