Document native networking limits
This commit is contained in:
parent
717758149b
commit
121d860043
@ -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.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
`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.
|
||||||
|
|||||||
@ -2,14 +2,15 @@ import lyng.io.net
|
|||||||
|
|
||||||
val host = "127.0.0.1"
|
val host = "127.0.0.1"
|
||||||
val clientCount = 1000
|
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
|
val port = server.localAddress().port
|
||||||
|
|
||||||
fun payloadFor(index: Int) = "$index:${Random.nextInt()}:${Random.nextInt()}"
|
fun payloadFor(index: Int) = "$index:${Random.nextInt()}:${Random.nextInt()}"
|
||||||
|
|
||||||
launch {
|
val serverJob = launch {
|
||||||
try {
|
try {
|
||||||
while(true) {
|
while (true) {
|
||||||
val client = server.accept()
|
val client = server.accept()
|
||||||
launch {
|
launch {
|
||||||
try {
|
try {
|
||||||
@ -22,25 +23,38 @@ launch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (server.isOpen()) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
server.close()
|
if (server.isOpen()) {
|
||||||
|
server.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val replies = (0..<clientCount).map { index ->
|
var completed = 0
|
||||||
val payload = payloadFor(index)
|
for (batchStart in 0..<clientCount step clientWindow) {
|
||||||
launch {
|
val batchEnd = if (batchStart + clientWindow < clientCount) batchStart + clientWindow else clientCount
|
||||||
val socket = Net.tcpConnect(host, port) as TcpSocket
|
val replies = (batchStart..<batchEnd).map { index ->
|
||||||
try {
|
val payload = payloadFor(index)
|
||||||
socket.writeUtf8(payload + "\n")
|
launch {
|
||||||
socket.flush()
|
val socket = Net.tcpConnect(host, port) as TcpSocket
|
||||||
val reply = socket.readLine()
|
try {
|
||||||
assertEquals("pong: $payload", reply)
|
socket.writeUtf8(payload + "\n")
|
||||||
} finally {
|
socket.flush()
|
||||||
socket.close()
|
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")
|
println("OK: $clientCount concurrent tcp clients")
|
||||||
|
|||||||
@ -30,12 +30,13 @@ import kotlin.test.assertEquals
|
|||||||
|
|
||||||
class LyngNetTcpServerExampleTest {
|
class LyngNetTcpServerExampleTest {
|
||||||
|
|
||||||
private fun concurrentTcpScript(clientCount: Int): String = """
|
private fun concurrentTcpScript(clientCount: Int, clientWindow: 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 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
|
val port: Int = server.localAddress().port
|
||||||
|
|
||||||
fun payloadFor(index: Int): String {
|
fun payloadFor(index: Int): String {
|
||||||
@ -72,28 +73,32 @@ class LyngNetTcpServerExampleTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val clientJobs: List<Deferred> = (0..<clientCount).map { index ->
|
var replies: List<Object> = List()
|
||||||
val payload = payloadFor(index)
|
for (batchStart in 0..<clientCount step clientWindow) {
|
||||||
launch {
|
val batchEnd = if (batchStart + clientWindow < clientCount) batchStart + clientWindow else clientCount
|
||||||
val socket: TcpSocket = Net.tcpConnect(host, port) as TcpSocket
|
val clientJobs: List<Deferred> = (batchStart..<batchEnd).map { index ->
|
||||||
try {
|
val payload = payloadFor(index)
|
||||||
socket.writeUtf8(payload + "\n")
|
launch {
|
||||||
socket.flush()
|
val socket: TcpSocket = Net.tcpConnect(host, port) as TcpSocket
|
||||||
val reply = socket.readLine()
|
try {
|
||||||
if( reply == null ) {
|
socket.writeUtf8(payload + "\n")
|
||||||
"client-eof:${'$'}payload"
|
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<Object>
|
val serverReplies = serverJob.await() as List<Object>
|
||||||
|
|
||||||
assertEquals(clientCount, replies.size)
|
assertEquals(clientCount, replies.size)
|
||||||
@ -112,10 +117,10 @@ class LyngNetTcpServerExampleTest {
|
|||||||
|
|
||||||
val result = withContext(Dispatchers.Default) {
|
val result = withContext(Dispatchers.Default) {
|
||||||
withTimeout(20_000) {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user