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
}

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.

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:

Under the hood

The layered design

Language_ZeroCopy.md describes zero-copy as a stack of layers, from the kernel up:

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