1
0
Fork 0
forked from fun/fun
fun/docs/asyncio.md

5.1 KiB

Async I/O ("asyncio") in Fun

This guide explains the new asynchronous I/O primitives in Fun and how to use them to build non-blocking network and file descriptor workflows. It covers the core concepts, available helpers, common patterns, and runnable examples from the repository.

What is asyncio in Fun?

In Fun, "asyncio" refers to event-driven, non-blocking I/O built around file descriptor readiness. Instead of blocking on reads/writes, you:

  • Put descriptors (sockets, pipes, etc.) into non-blocking mode
  • Wait for them to become readable/writable using polling helpers
  • Perform small, incremental reads/writes when the OS signals readiness

This lets a single Fun script handle many concurrent connections efficiently without threads, and keeps UIs or other work responsive while I/O is in flight.

There is no special syntax (like async/await) — you compose ordinary control flow with a few focused opcodes and stdlib functions.

Building blocks

Core helpers wired into the VM (see src/vm/os/*):

  • fd_set_nonblock(fd, on) → 1/0: enable or disable O_NONBLOCK on a file descriptor
  • fd_poll_read(fd, timeout_ms) → int: >0 if fd is readable; 0 on timeout; <0 on error
  • fd_poll_write(fd, timeout_ms) → int: >0 if fd is writable; 0 on timeout; <0 on error

Common networking helpers from the stdlib:

  • tcp_connect(host, port) → fd: open a TCP connection; returns 0 on failure
  • sock_send(fd, data) → int: send bytes (may write only part in non-blocking mode)
  • sock_recv(fd, max_bytes) → string: receive up to max_bytes; empty string on EOF
  • sock_close(fd): close the descriptor

Tip: Always check return values. In non-blocking mode, partial writes and short reads are normal.

Typical patterns

  1. Connect and switch to non-blocking
fd = tcp_connect(host, port)
if (fd == 0)
  // handle connect error
ok = fd_set_nonblock(fd, 1)
if (ok == 0)
  // handle mode switch error
  1. Non-blocking write loop with readiness polling
remaining = req
while (len(remaining) > 0)
  wr = fd_poll_write(fd, 1000)  // wait up to 1s
  if (wr < 0)
    // poll error; abort
  if (wr == 0)
    continue  // timeout; try again
  n = sock_send(fd, remaining)
  if (n < 0)
    // send error; abort
  remaining = substr(remaining, n, len(remaining) - n)
  1. Non-blocking read-until-close
buf = ""
while (true)
  rd = fd_poll_read(fd, 2000)  // wait up to 2s
  if (rd < 0)
    // poll error; break
  if (rd == 0)
    // timeout: try a read to detect EOF
    data = sock_recv(fd, 4096)
    if (len(data) == 0)
      break  // likely closed
    buf = buf + data
    continue
  data = sock_recv(fd, 4096)
  if (len(data) == 0)
    break  // closed
  buf = buf + data

Timeouts and responsiveness

  • timeout_ms controls how long poll waits. Use small timeouts inside loops to interleave work across multiple sockets or tasks.
  • A timeout result (0) is not an error — treat it as an opportunity to perform other duties and try again later.
  • Negative results (<0) indicate OS-level errors from poll/select; handle or abort as appropriate.

Working with multiple connections

To multiplex several sockets:

  • Keep per-connection state (outgoing buffer, accumulate incoming, progress markers)
  • Round-robin over connections, polling each for read/write readiness with short timeouts
  • Advance each state machine a little per iteration

Because Fun keeps the primitives low-level and explicit, you can build simple cooperative schedulers, connection pools, or protocol handlers directly in Fun code.

Examples in the repository

  • examples/io/async_http_client.fun — Minimal HTTP GET over non-blocking TCP using fd_poll_* helpers
  • examples/net/http_mt_server.fun — Multi-tenant HTTP server scaffold (compare patterns for concurrency)
  • examples/net/http_mt_server_cgi.fun — Server variant that dispatches CGI-like handlers
  • lib/net/http_cgi_server.fun — Library helpers used by the server examples

Run client example from a build tree:

FUN_LIB_DIR=./lib ./build/fun examples/io/async_http_client.fun

If installed system-wide, just:

fun /usr/share/fun/examples/io/async_http_client.fun

Error handling and cleanup

  • Always close descriptors with sock_close(fd) when finished or on error paths.
  • Distinguish between timeout (wr/rd == 0), EOF (len(recv) == 0), and errors (wr/rd < 0 or send < 0).
  • For large payloads, design your loops to tolerate partial progress and resume cleanly.

FAQ

Q: Is there an async/await syntax? A: Not at this time. The model is explicit non-blocking I/O with polling helpers. You can build lightweight schedulers on top if desired.

Q: Does this work on all platforms? A: The helpers map to portable OS facilities exposed by the VM. Details may vary by platform; see docs/troubleshooting.md and open an issue if you hit differences.

Q: How do I integrate with the REPL? A: You can prototype small non-blocking fragments in the REPL, but full networking examples are easier to run as scripts.

See also