diff --git a/docs/lyng.io.net.md b/docs/lyng.io.net.md index fc725a5..ea8659b 100644 --- a/docs/lyng.io.net.md +++ b/docs/lyng.io.net.md @@ -3,6 +3,12 @@ This module provides minimal raw transport networking for Lyng scripts. It is implemented in `lyngio` and backed by Ktor sockets on the JVM and Linux Native, and by Node networking APIs on JS/Node runtimes. > **Note:** `lyngio` is a separate library module. It must be explicitly added as a dependency to your host application and initialized in your Lyng scopes. +> +> **Important native platform limit:** current native TCP/UDP support is backed by a selector with a per-process file descriptor ceiling. On Linux/macOS native targets this makes high-connection-count servers and same-process load tests unsuitable once the process approaches that limit. +> +> **Recommendation:** for serious HTTP/TCP servers, prefer the JVM target today. On native targets, keep concurrency bounded, batch local load tests in waves, and use multiple worker processes behind a reverse proxy if you need more throughput before the backend is reworked. +> +> **Need this fixed?** Please open or upvote an issue at so native high-concurrency networking can be prioritized. --- diff --git a/docs/lyngio.md b/docs/lyngio.md index 8ad8f70..9cb19cb 100644 --- a/docs/lyngio.md +++ b/docs/lyngio.md @@ -2,6 +2,8 @@ `lyngio` is a separate library that extends the Lyng core (`lynglib`) with powerful, multiplatform, and secure I/O capabilities. +> **Important native networking limit:** `lyng.io.net` on current native targets is suitable for modest workloads, local tools, and test servers, but not yet for high-connection-count production servers. For serious HTTP/TCP serving, prefer the JVM target for now. If native high-concurrency networking matters for your use case, please open or upvote an issue at . + #### Why a separate module? 1. **Security:** I/O and process execution are sensitive operations. By keeping them in a separate module, we ensure that the Lyng core remains 100% safe by default. You only enable what you explicitly need. diff --git a/examples/tcpserver.lyng b/examples/tcpserver.lyng index 09a9901..ebec974 100644 --- a/examples/tcpserver.lyng +++ b/examples/tcpserver.lyng @@ -2,14 +2,15 @@ import lyng.io.net val host = "127.0.0.1" val clientCount = 1000 -val server = Net.tcpListen(0, host, clientCount, true) +val clientWindow = 128 +val server = Net.tcpListen(0, host, clientWindow, true) val port = server.localAddress().port fun payloadFor(index: Int) = "$index:${Random.nextInt()}:${Random.nextInt()}" -launch { +val serverJob = launch { try { - while(true) { + while (true) { val client = server.accept() launch { try { @@ -22,25 +23,38 @@ launch { } } } + } catch (e) { + if (server.isOpen()) { + throw e + } } finally { - server.close() + if (server.isOpen()) { + server.close() + } } } -val replies = (0.. - val payload = payloadFor(index) - launch { - val socket = Net.tcpConnect(host, port) as TcpSocket - try { - socket.writeUtf8(payload + "\n") - socket.flush() - val reply = socket.readLine() - assertEquals("pong: $payload", reply) - } finally { - socket.close() +var completed = 0 +for (batchStart in 0.. + val payload = payloadFor(index) + launch { + val socket = Net.tcpConnect(host, port) as TcpSocket + try { + socket.writeUtf8(payload + "\n") + socket.flush() + val reply = socket.readLine() + assertEquals("pong: $payload", reply) + } finally { + socket.close() + } } - } -}.joinAll() + }.joinAll() + completed += replies.size +} -assertEquals(clientCount, replies.size) +assertEquals(clientCount, completed) +server.close() +serverJob.await() println("OK: $clientCount concurrent tcp clients") diff --git a/lyngio/src/commonTest/kotlin/net/sergeych/lyng/io/net/LyngNetTcpServerExampleTest.kt b/lyngio/src/commonTest/kotlin/net/sergeych/lyng/io/net/LyngNetTcpServerExampleTest.kt index 590b1e6..6bca4b5 100644 --- a/lyngio/src/commonTest/kotlin/net/sergeych/lyng/io/net/LyngNetTcpServerExampleTest.kt +++ b/lyngio/src/commonTest/kotlin/net/sergeych/lyng/io/net/LyngNetTcpServerExampleTest.kt @@ -30,12 +30,13 @@ import kotlin.test.assertEquals class LyngNetTcpServerExampleTest { - private fun concurrentTcpScript(clientCount: Int): String = """ + private fun concurrentTcpScript(clientCount: Int, clientWindow: 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 clientWindow = $clientWindow + val server: TcpServer = Net.tcpListen(0, host, clientWindow, true) as TcpServer val port: Int = server.localAddress().port fun payloadFor(index: Int): String { @@ -72,28 +73,32 @@ class LyngNetTcpServerExampleTest { } } - val clientJobs: List = (0.. - 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" + var replies: List = List() + for (batchStart in 0.. = (batchStart.. + 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() } - else { - assertEquals("pong: ${'$'}payload", reply) - reply - } - } finally { - socket.close() } } + replies += clientJobs.joinAll() } - val replies = clientJobs.joinAll() val serverReplies = serverJob.await() as List assertEquals(clientCount, replies.size) @@ -112,10 +117,10 @@ class LyngNetTcpServerExampleTest { val result = withContext(Dispatchers.Default) { withTimeout(20_000) { - Compiler.compile(concurrentTcpScript(clientCount = 32)).execute(scope).inspect(scope) + Compiler.compile(concurrentTcpScript(clientCount = 1_000, clientWindow = 128)).execute(scope).inspect(scope) } } - assertEquals("\"OK:32\"", result) + assertEquals("\"OK:1000\"", result) } }