libuv clib Async I/O / Event Loop

Async I/O event loop for PascalAI. TCP/UDP servers, timers, async file system, thread pool, child processes, signals, and DNS — the same library that powers Node.js.

ppm install libuv

Overview

libuv wraps libuv — the cross-platform asynchronous I/O library originally written for Node.js and now used across dozens of runtimes and server frameworks. It provides a single-threaded event loop that handles networking, timers, and I/O completion notifications without spawning threads for every connection.

All network and file operations are non-blocking: you initiate an operation, register a handler in your PascalAI agent, and the loop calls back when work completes. CPU-bound or truly blocking tasks (legacy file I/O, DNS lookups) are dispatched to a configurable thread pool so the event loop thread stays responsive.

CLib package

Requires libuv1-dev. Ubuntu/Debian: sudo apt install libuv1-dev

Core Capabilities

CapabilityDescription
Event loopSingle-threaded, non-blocking I/O via callbacks. Run modes: default, once, no-wait.
TCPAsync TCP servers and clients with keep-alive, Nagle control, and backpressure.
UDPConnectionless datagram sockets for low-latency or broadcast use cases.
File systemAsync stat, read, write, readdir, rename, unlink, mkdir — all via thread pool.
TimersOne-shot and repeating timers with millisecond precision (setTimeout / setInterval equivalents).
Thread poolPush blocking work off the loop thread. Default 4 threads; set UV_THREADPOOL_SIZE to scale.
Child processSpawn subprocesses, pipe their stdio, and receive exit signals as loop events.
SignalsHandle SIGTERM, SIGINT, and other POSIX signals as first-class event sources.
DNSAsync getaddrinfo hostname resolution dispatched to the thread pool.

Concurrency Model

libuv uses a single-threaded event loop. All callbacks fire on the same thread, so there are no data races between handlers — you never need locks for shared application state.

Truly blocking work (DNS lookups, file I/O, and anything you queue via QueueWork) runs on a background thread pool. When the work completes, the result callback is marshalled back to the event loop thread. This keeps latency predictable and avoids the "thundering herd" problems common in thread-per-connection servers.

UV_THREADPOOL_SIZE

Set the environment variable UV_THREADPOOL_SIZE=N before starting your program to control the thread pool size. The default is 4. For file-heavy workloads, values of 8–16 are common.

Comparison

LibraryLanguageNotes
libuvCCross-platform; covers networking, file I/O, child processes, signals, DNS, and timers in one library.
libeventCOlder; focuses on network events. Does not include file I/O or child process abstractions.
Boost.AsioC++Full-featured but C++ ABI; heavier dependency chain. libuv has a simpler, stable C ABI.
epoll / kqueueC (OS API)Raw OS primitives. libuv wraps these for cross-platform compatibility (Linux, macOS, Windows).

Quick Start

TCP echo server setup

uses libuv;

var loop := DefaultLoop();
var server := NewTCP(loop);

TCPBind(server, '0.0.0.0', 8080);
TCPListen(server, 128);  { backlog = 128 }

{ Handlers registered on the agent fire when clients connect/send data }
WriteLn('TCP server listening on :8080');
Run(loop, uvRunDefault);  { blocks until Stop() is called }

CloseLoop(loop);

One-shot and repeating timers

uses libuv;

var loop  := DefaultLoop();
var once  := NewTimer(loop);
var tick  := NewTimer(loop);

{ Fire once after 500 ms }
TimerStart(once, 500, 0);

{ Fire every 1000 ms starting immediately }
TimerStart(tick, 0, 1000);

Run(loop, uvRunDefault);

TimerClose(once);
TimerClose(tick);
CloseLoop(loop);

Async file read

uses libuv;

var loop := DefaultLoop();

{ FSReadFile dispatches to the thread pool; callback fires on the loop thread }
FSReadFile(loop, '/etc/hostname');

Run(loop, uvRunDefault);
CloseLoop(loop);

Types

type
  TUVLoop    = Integer;  { event loop handle }
  TUVTCP     = Integer;  { TCP socket handle }
  TUVUdp     = Integer;  { UDP socket handle }
  TUVTimer   = Integer;  { timer handle }
  TUVAsync   = Integer;  { async signal handle }
  TUVProcess = Integer;  { child process handle }
  TUVPipe    = Integer;  { pipe (IPC) handle }
  TUVWork    = Integer;  { thread pool work request }
  TUVFs      = Integer;  { file system request }

  TUVRunMode = (
    uvRunDefault = 0,  { run until no more active handles/requests }
    uvRunOnce    = 1,  { run one iteration, may block }
    uvRunNoWait  = 2   { run one iteration, never blocks }
  );

  TUVAddrInfo = record
    Host : string;
    Port : Integer;
    IP   : string;
  end;

Loop API

The event loop is the heart of libuv. Create one loop per thread, register handles and requests, then call Run to process events.

FunctionDescription
NewLoopCreate a new, independent event loop.
DefaultLoopReturn the default/global loop shared by the process.
Run(Loop, Mode)Run the loop. uvRunDefault blocks until all handles are inactive. uvRunOnce processes at most one event. uvRunNoWait polls without blocking.
Stop(Loop)Request the loop to stop after the current iteration completes.
CloseLoop(Loop)Close and free all loop resources. Call after Run returns.
LoopAlive(Loop)Returns True if there are active handles or pending requests.

TCP API

Build async TCP servers and clients. All data transfer is non-blocking; read callbacks are invoked by the event loop as data arrives.

FunctionDescription
NewTCP(Loop)Allocate a TCP handle on the given loop.
TCPBind(TCP, Host, Port)Bind the handle to a local address and port.
TCPListen(TCP, Backlog)Start accepting connections. Backlog is the kernel queue size.
TCPConnect(TCP, Host, Port)Initiate an async connection to a remote host.
TCPWrite(TCP, Data)Write a string to the TCP stream (buffered, non-blocking).
TCPStartRead(TCP)Start delivering incoming data via read callbacks.
TCPStopRead(TCP)Pause read callbacks without closing the connection.
TCPClose(TCP)Close the handle and release its resources.
TCPPeerName(TCP)Returns a TUVAddrInfo with the remote Host, Port, and resolved IP.
TCPKeepAlive(TCP, Enable, Delay)Enable TCP keep-alive probes after Delay seconds of inactivity.
TCPNoDelay(TCP, Enable)Disable the Nagle algorithm for lower latency at the cost of more small packets.

UDP API

UDP sockets are connectionless and have no backpressure. Use them for real-time telemetry, game state, or multicast where occasional packet loss is acceptable.

FunctionDescription
NewUDP(Loop)Allocate a UDP socket handle.
UDPBind(UDP, Host, Port)Bind the socket to a local address and port for receiving.
UDPSend(UDP, Data, Host, Port)Send a datagram to a remote address. Completes asynchronously.
UDPStartRecv(UDP)Start delivering incoming datagrams via receive callbacks.
UDPClose(UDP)Close the UDP socket and release resources.
uses libuv;

var loop   := DefaultLoop();
var sender := NewUDP(loop);

UDPSend(sender, 'hello', '127.0.0.1', 9000);

Run(loop, uvRunOnce);  { process the send completion callback }
UDPClose(sender);
CloseLoop(loop);

Timers API

Timers run on the event loop thread. Set RepeatMs = 0 for a one-shot timeout, or provide a non-zero value for an interval that keeps firing until stopped.

FunctionDescription
NewTimer(Loop)Allocate a timer handle on the loop.
TimerStart(T, TimeoutMs, RepeatMs)Start the timer. First fires after TimeoutMs ms; repeats every RepeatMs ms if non-zero.
TimerStop(T)Stop a running timer without closing the handle (can be restarted).
TimerClose(T)Close and free the timer handle.

Thread Pool API

Push any blocking work off the event loop thread. libuv queues it to the thread pool and calls back on the loop thread when done. Use this for CPU-heavy computations or calls to blocking C libraries.

FunctionDescription
QueueWork(Loop, WorkID)Dispatch a named work item to the thread pool. The completion callback fires on the loop thread.

File System API

All file system calls are dispatched to the thread pool internally and complete asynchronously via callbacks. They are safe to call from the event loop thread without stalling it.

FunctionDescription
FSStat(Loop, Path)Async stat — retrieve file size, modification time, type, and permissions.
FSReadFile(Loop, Path)Async whole-file read. Callback receives the file contents as a string.
FSWriteFile(Loop, Path, Data)Async whole-file write. Creates or truncates the target file.
FSScanDir(Loop, Path)Async directory listing. Callback receives a list of entry names and types.
FSRename(Loop, OldPath, NewPath)Async atomic rename/move within the same filesystem.
FSUnlink(Loop, Path)Async file deletion.
FSMkdir(Loop, Path, Mode)Async directory creation. Mode is the Unix permission bits (e.g. $1ED for 0755).

DNS API

Hostname resolution is dispatched to the thread pool to avoid blocking the event loop. The resolved TUVAddrInfo is returned via callback.

FunctionDescription
GetAddrInfo(Loop, Host, Port)Async hostname resolution. Callback receives a TUVAddrInfo with the resolved IP, host, and port.

Child Process API

Spawn subprocesses and manage their lifecycle from the event loop. Stdio can be piped through TUVPipe handles for bidirectional communication.

FunctionDescription
Spawn(Loop, Exe, Args)Spawn a child process. Returns a TUVProcess handle. Exit event fires on the loop when the child terminates.
ProcessKill(P, Signal)Send a signal to a child process (e.g. 15 for SIGTERM, 9 for SIGKILL).
uses libuv;

var loop := DefaultLoop();
var proc := Spawn(loop, '/usr/bin/python3', ['-c', 'print("hello")']);

WriteLn('Child PID spawned');
Run(loop, uvRunDefault);  { wait for child to exit }
CloseLoop(loop);

Utilities API

System information helpers useful for diagnostics, capacity planning, and high-precision timing.

FunctionReturnsDescription
HRTimeInt64Monotonic high-resolution time in nanoseconds. Use for benchmarking and latency measurement.
CPUCountIntegerNumber of logical CPU cores available to the process.
TotalMemoryInt64Total physical memory of the system in bytes.
FreeMemoryInt64Currently available physical memory in bytes.
HostnamestringSystem hostname as a string.
LibUVVersionstringRuntime version string of the linked libuv shared library.
uses libuv;

var t0 := HRTime();
{ ... do work ... }
var elapsed := HRTime() - t0;
WriteLn('Elapsed: ' + IntToStr(elapsed div 1000000) + ' ms');

WriteLn('libuv ' + LibUVVersion());
WriteLn('Host: ' + Hostname());
WriteLn('CPUs: ' + IntToStr(CPUCount()));
WriteLn('Free memory: ' + IntToStr(FreeMemory() div 1048576) + ' MB');

Run Modes

ModeValueBehaviour
uvRunDefault0Run until there are no more active handles or pending requests. This is the normal server mode.
uvRunOnce1Execute at most one I/O event, then return. May block if no events are ready yet.
uvRunNoWait2Process any ready events immediately, then return without blocking. Useful for integrating with a custom main loop.

Package Info

Version1.0.0
Typeclib
C Librarylibuv1-dev
Authorgustavo

System Requirements

libuv1-dev

Ubuntu/Debian:
sudo apt install libuv1-dev

Handle Types

TUVLoop — event loop
TUVTCP — TCP socket
TUVUdp — UDP socket
TUVTimer — timer
TUVAsync — async signal
TUVProcess — child process
TUVPipe — IPC pipe
TUVWork — thread pool work
TUVFs — file system request
TUVAddrInfo — resolved address

Functions

  • NewLoop / DefaultLoop
  • Run / Stop / CloseLoop
  • LoopAlive
  • NewTCP / TCPBind
  • TCPListen / TCPConnect
  • TCPWrite / TCPStartRead
  • TCPStopRead / TCPClose
  • TCPPeerName
  • TCPKeepAlive / TCPNoDelay
  • NewUDP / UDPBind
  • UDPSend / UDPStartRecv
  • UDPClose
  • NewTimer / TimerStart
  • TimerStop / TimerClose
  • QueueWork
  • FSStat / FSReadFile
  • FSWriteFile / FSScanDir
  • FSRename / FSUnlink
  • FSMkdir
  • GetAddrInfo
  • Spawn / ProcessKill
  • HRTime / CPUCount
  • TotalMemory / FreeMemory
  • Hostname / LibUVVersion

See Also

  • curl (HTTP/REST client)
  • nats (pub/sub messaging)
  • grpc (RPC over HTTP/2)
  • zmq (socket messaging)