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.
Requires libuv1-dev. Ubuntu/Debian: sudo apt install libuv1-dev
Core Capabilities
| Capability | Description |
|---|---|
| Event loop | Single-threaded, non-blocking I/O via callbacks. Run modes: default, once, no-wait. |
| TCP | Async TCP servers and clients with keep-alive, Nagle control, and backpressure. |
| UDP | Connectionless datagram sockets for low-latency or broadcast use cases. |
| File system | Async stat, read, write, readdir, rename, unlink, mkdir — all via thread pool. |
| Timers | One-shot and repeating timers with millisecond precision (setTimeout / setInterval equivalents). |
| Thread pool | Push blocking work off the loop thread. Default 4 threads; set UV_THREADPOOL_SIZE to scale. |
| Child process | Spawn subprocesses, pipe their stdio, and receive exit signals as loop events. |
| Signals | Handle SIGTERM, SIGINT, and other POSIX signals as first-class event sources. |
| DNS | Async 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.
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
| Library | Language | Notes |
|---|---|---|
| libuv | C | Cross-platform; covers networking, file I/O, child processes, signals, DNS, and timers in one library. |
| libevent | C | Older; focuses on network events. Does not include file I/O or child process abstractions. |
| Boost.Asio | C++ | Full-featured but C++ ABI; heavier dependency chain. libuv has a simpler, stable C ABI. |
| epoll / kqueue | C (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.
| Function | Description |
|---|---|
| NewLoop | Create a new, independent event loop. |
| DefaultLoop | Return 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.
| Function | Description |
|---|---|
| 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.
| Function | Description |
|---|---|
| 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.
| Function | Description |
|---|---|
| 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.
| Function | Description |
|---|---|
| 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.
| Function | Description |
|---|---|
| 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.
| Function | Description |
|---|---|
| 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.
| Function | Description |
|---|---|
| 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.
| Function | Returns | Description |
|---|---|---|
| HRTime | Int64 | Monotonic high-resolution time in nanoseconds. Use for benchmarking and latency measurement. |
| CPUCount | Integer | Number of logical CPU cores available to the process. |
| TotalMemory | Int64 | Total physical memory of the system in bytes. |
| FreeMemory | Int64 | Currently available physical memory in bytes. |
| Hostname | string | System hostname as a string. |
| LibUVVersion | string | Runtime 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
| Mode | Value | Behaviour |
|---|---|---|
| uvRunDefault | 0 | Run until there are no more active handles or pending requests. This is the normal server mode. |
| uvRunOnce | 1 | Execute at most one I/O event, then return. May block if no events are ready yet. |
| uvRunNoWait | 2 | Process any ready events immediately, then return without blocking. Useful for integrating with a custom main loop. |
Package Info
System Requirements
libuv1-devUbuntu/Debian:
sudo apt install libuv1-dev
Handle Types
TUVLoop — event loopTUVTCP — TCP socketTUVUdp — UDP socketTUVTimer — timerTUVAsync — async signalTUVProcess — child processTUVPipe — IPC pipeTUVWork — thread pool workTUVFs — file system requestTUVAddrInfo — 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