Compare commits

..

No commits in common. "121d860043319d4b6028a2d679641cb371ba78c5" and "73ec8f9be6e4e92aea7999ac99abc9b042de27c7" have entirely different histories.

4 changed files with 39 additions and 66 deletions

View File

@ -3,12 +3,6 @@
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. 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. > **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 <https://github.com/sergeych/lyng/issues> so native high-concurrency networking can be prioritized.
--- ---

View File

@ -2,8 +2,6 @@
`lyngio` is a separate library that extends the Lyng core (`lynglib`) with powerful, multiplatform, and secure I/O capabilities. `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 <https://github.com/sergeych/lyng/issues>.
#### Why a separate module? #### 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. 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.

View File

@ -2,15 +2,14 @@ import lyng.io.net
val host = "127.0.0.1" val host = "127.0.0.1"
val clientCount = 1000 val clientCount = 1000
val clientWindow = 128 val server = Net.tcpListen(0, host, clientCount, true) as TcpServer
val server = Net.tcpListen(0, host, clientWindow, true)
val port = server.localAddress().port val port = server.localAddress().port
fun payloadFor(index: Int) = "$index:${Random.nextInt()}:${Random.nextInt()}" fun payloadFor(index: Int) = "$index:${Random.nextInt()}:${Random.nextInt()}"
val serverJob = launch { launch {
try { try {
while (true) { while(true) {
val client = server.accept() val client = server.accept()
launch { launch {
try { try {
@ -23,38 +22,25 @@ val serverJob = launch {
} }
} }
} }
} catch (e) {
if (server.isOpen()) {
throw e
}
} finally { } finally {
if (server.isOpen()) { server.close()
server.close()
}
} }
} }
var completed = 0 val replies = (0..<clientCount).map { index ->
for (batchStart in 0..<clientCount step clientWindow) { val payload = payloadFor(index)
val batchEnd = if (batchStart + clientWindow < clientCount) batchStart + clientWindow else clientCount launch {
val replies = (batchStart..<batchEnd).map { index -> val socket = Net.tcpConnect(host, port) as TcpSocket
val payload = payloadFor(index) try {
launch { socket.writeUtf8(payload + "\n")
val socket = Net.tcpConnect(host, port) as TcpSocket socket.flush()
try { val reply = socket.readLine()
socket.writeUtf8(payload + "\n") assertEquals("pong: $payload", reply)
socket.flush() } finally {
val reply = socket.readLine() socket.close()
assertEquals("pong: $payload", reply)
} finally {
socket.close()
}
} }
}.joinAll() }
completed += replies.size }.joinAll()
}
assertEquals(clientCount, completed) assertEquals(clientCount, replies.size)
server.close()
serverJob.await()
println("OK: $clientCount concurrent tcp clients") println("OK: $clientCount concurrent tcp clients")

View File

@ -30,13 +30,12 @@ import kotlin.test.assertEquals
class LyngNetTcpServerExampleTest { class LyngNetTcpServerExampleTest {
private fun concurrentTcpScript(clientCount: Int, clientWindow: Int): String = """ private fun concurrentTcpScript(clientCount: Int): String = """
import lyng.io.net import lyng.io.net
val host = "127.0.0.1" val host = "127.0.0.1"
val clientCount = $clientCount val clientCount = $clientCount
val clientWindow = $clientWindow val server: TcpServer = Net.tcpListen(0, host, clientCount, true) as TcpServer
val server: TcpServer = Net.tcpListen(0, host, clientWindow, true) as TcpServer
val port: Int = server.localAddress().port val port: Int = server.localAddress().port
fun payloadFor(index: Int): String { fun payloadFor(index: Int): String {
@ -73,32 +72,28 @@ class LyngNetTcpServerExampleTest {
} }
} }
var replies: List<Object> = List() val clientJobs: List<Deferred> = (0..<clientCount).map { index ->
for (batchStart in 0..<clientCount step clientWindow) { val payload = payloadFor(index)
val batchEnd = if (batchStart + clientWindow < clientCount) batchStart + clientWindow else clientCount launch {
val clientJobs: List<Deferred> = (batchStart..<batchEnd).map { index -> val socket: TcpSocket = Net.tcpConnect(host, port) as TcpSocket
val payload = payloadFor(index) try {
launch { socket.writeUtf8(payload + "\n")
val socket: TcpSocket = Net.tcpConnect(host, port) as TcpSocket socket.flush()
try { val reply = socket.readLine()
socket.writeUtf8(payload + "\n") if( reply == null ) {
socket.flush() "client-eof:${'$'}payload"
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<Object> val serverReplies = serverJob.await() as List<Object>
assertEquals(clientCount, replies.size) assertEquals(clientCount, replies.size)
@ -117,10 +112,10 @@ class LyngNetTcpServerExampleTest {
val result = withContext(Dispatchers.Default) { val result = withContext(Dispatchers.Default) {
withTimeout(20_000) { withTimeout(20_000) {
Compiler.compile(concurrentTcpScript(clientCount = 1_000, clientWindow = 128)).execute(scope).inspect(scope) Compiler.compile(concurrentTcpScript(clientCount = 32)).execute(scope).inspect(scope)
} }
} }
assertEquals("\"OK:1000\"", result) assertEquals("\"OK:32\"", result)
} }
} }