The Bnlang Event Loop

Every Bnlang program runs on a single event loop, provided by libuv. The loop is what makes async I/O work: while one request is waiting on disk or the network, the loop keeps spinning and runs other callbacks that are ready.


How It Works

  1. Your script runs top to bottom. Synchronous statements execute immediately.
  2. Async calls (e.g. io.read_file_async, timers.set, network ops) hand work off to libuv and return without waiting.
  3. When your script finishes its top-level code, the runtime hands control to the event loop.
  4. As each operation completes, libuv queues the matching callback. The loop picks them up one at a time and runs them on the main thread.
  5. The process exits when there is no more pending work — no live timers, no open streams, no in-flight requests.

Example: Timers and Async I/O

The output below shows the loop in action. Notice that the synchronous print calls run first, and then the loop drains the scheduled callbacks.

import "timers" as timers;
import "io"     as io;

print("a");

timers.set(0, function () {
    print("timer fired");
});

io.read_file_async("greeting.txt", function (err, data) {
    if (err != null) { print("read err:", err); return; }
    print("file:", data);
});

print("b");

// Output (file order depends on disk timing):
// a
// b
// timer fired
// file: ...

What's Not Here

Bnlang's event loop is deliberately simple. A few things you might expect from other runtimes don't exist:

  • No microtask queue — there are no Promises, so there is no then/await microtask ordering. Callbacks run in the order their I/O completes.
  • No process.nextTick or setImmediate — use timers.set(0, fn) to defer until the next loop turn.
  • No background threads visible to scripts — heavy I/O uses libuv's thread pool internally, but your script always runs on the main thread.

Best Practices

  • Keep callbacks short. They share the loop with every other in-flight request.
  • For CPU-heavy work, slice it into chunks separated by timers.set(0, ...) so other callbacks get a turn.
  • Always close streams and clear interval timers you no longer need — they will keep the loop (and your process) alive.