Zero-Copy & Servers
Network servers are where Lumina's design comes together: stackful coroutines for one-task-per-connection code without callback hell, capabilities so the server's I/O is visible in its signature, and zero-copy primitives so bytes do not bounce through user space when they don't need to.
An HTTP server in a screenful
function response(body: String): String =
`HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: {body.length()}\r\nConnection: close\r\n\r\n{body}`
function main(console: Console): Void {
port = 18093
server = Net.listen(port)
console.println(`listening on http://127.0.0.1:{port}`)
while true {
conn = server.accept()
if conn > 0 {
spawn(() => {
request = conn.read(8192)
if request.length() > 0 {
conn.send(response("Hello from Lumina!\n"))
}
conn.close()
return 0
})
}
}
}
$ lumina run server.lumina &
listening on http://127.0.0.1:18093
$ curl http://127.0.0.1:18093/
Hello from Lumina!
Every connection gets its own task (spawn). The handler is straight-line code: read, respond, close. When it waits on the socket, the task suspends and the scheduler runs other connections. This is the Go model, with no async coloring needed in the handler.
The idiomatic shape: capabilities + streams
The repository's benchmark server, examples/http_server.lumina, shows the production shape:
function main(net: Network, console: Console): Integer {
port = resolvePort(18081)
console.println(`Lumina HTTP server listening on 0.0.0.0:{port}`)
listener = net.listen("0.0.0.0", port)
for await client_fd in listener.incoming() {
spawn(() => handleClient(client_fd))
}
return 0
}
mainreceivesNetworkas a capability, so a test can hand it a fake (see Capabilities).listener.incoming()is an async stream of accepted connections, consumed withfor await.- The handler uses
client.readHttpHead(), which reads the whole request head in one buffered, SIMD-scanned pass, andclient.writeParts(headers, body), which coalesces both strings into a singlewritev(2)call without concatenating them first. It keeps the connection alive in a loop.
lumina run examples/http_server.lumina serves Hello, World! on port 18081. tools/http_bench_wrk.sh benchmarks it against a Tokio + hyper reference server in tools/tokio_http_reference.
Zero-copy: kernel splice
Proxies and gateways mostly move bytes from one socket to another. spliceTo does that inside the kernel, so the payload never enters Lumina's memory:
async function main(): Integer {
listener = tcpListen("127.0.0.1", 29111)
spawn(() => sendHello()) // a client that sends "hello"
src = TcpSocket.new(tcpAccept(listener))
readerFd = tcpConnect("127.0.0.1", 29111)
dst = TcpSocket.new(tcpAccept(listener))
reader = TcpSocket.new(readerFd)
transferred = src.spliceTo(dst, 0, 5) // kernel splice: 5 bytes src -> dst
data = reader.recv(5) // "hello"
...
}
This is the shape of tests/spec/19_stdlib_net/05_tcp_splice_loopback.lumina.
src.spliceTo(dst, skip, count)skipsskipbytes, then movescountbytes.count == 0means "until EOF".- It returns the number of bytes transferred, or a negative sentinel:
-1EOF,-2splice unsupported on this platform,-3I/O error. - On Linux it uses
splice(2)through a pipe, with an io_uring path where available.
Zero-copy: peek before you read
sock.peek(maxLen) looks at incoming bytes with MSG_PEEK without consuming them. A server can inspect the first bytes ("is this TLS? HTTP/1? HTTP/2?") and then hand the untouched stream to the right handler, or splice it onward. tests/spec/19_stdlib_net/04_tcp_peek_loopback.lumina shows it end to end.
Zero-copy inside the program
Zero-copy is not only about sockets:
substringand slicing return views (StringView,BytesView) instead of copies, and the compiler rejects a view that outlives its source (see Memory).MutableBytes.allocate(capacity)gives a growable byte buffer withwriteByte,writeBytesandwriteString.freeze()turns it into an immutableBytesby handing over the buffer, without a copy.- Buffer pools in
lumina.iolet hot paths reuse buffers instead of allocating per request.
Under the hood
- Event loop: epoll plus eventfd on Linux drives socket readiness for the coroutine scheduler. An io_uring backend exists for networking (
examples/uring_network.luminaexercises it through@ExternCbindings). - Multi-threading: a work-stealing thread pool runs connection tasks across all cores in progress, on top of cooperative tasks interleaving on I/O.
- Allocation discipline: combine the server with
@Memory.noAllocon hot helpers orlumina build --no-gcto keep the request path out of the GC.
The layered design
Language_ZeroCopy.md describes zero-copy as a stack of layers, from the kernel up:
- AF_XDP kernel-bypass packet I/O (layer 0) designed.
sendfile, file-to-socket zero-copy (layer 2) designed.- Zero-copy on non-TCP sockets, UDP and Unix domain sockets designed.
PeekableStream/SpliceableStreaminterfaces and composable pipeline decorators over them designed.- kqueue (macOS) and IOCP (Windows) event backends next to epoll and io_uring designed.
On top sits an HTTP framework with router, middleware and TLS in the standard library designed. The examples above speak raw HTTP/1.1, and stdlib/net/ has WebSocket and JSON-RPC modules.
Next: Interop & Stdlib