npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

uring-fs

v1.1.0

Published

True asynchronous Linux io_uring filesystem I/O for Node.js with zero libuv thread-pool usage, automatic tick batching, and pluggable fallback backends.

Readme

uring-fs (io_uring File System for Node.js)

True asynchronous filesystem I/O for Node.js on Linux powered by io_uring — with zero libuv thread-pool contention, 99% syscall reduction via Tick Auto-Batching, in-kernel zero-copy splice, and a 3-Tier Pluggable Fallback System.


Why uring-fs Exists

In Node.js, network I/O is non-blocking via epoll, but regular file I/O (node:fs) cannot use epoll. Because libuv disabled its internal io_uring integration by default (following CVE-2024-22017), Node.js routes all fs, crypto, zlib, and dns.lookup operations through a shared 4-thread pool (UV_THREADPOOL_SIZE=4).

When a server performs CPU-bound thread-pool work (such as password hashing, TLS/crypto, or Gzip/Brotli compression), fs.promises.readFile stalls in the queue waiting for a worker thread.

uring-fs solves this by:

  1. 100% Asynchronous Kernel State Machine: Executes openat, statx, read, write, splice, fsync, renameat, symlinkat, linkat, mkdirat, unlinkat, and close entirely inside the Linux kernel's io_uring submission/completion rings (SQ/CQ), using zero libuv worker threads.
  2. Direct File Descriptors (io_uring_register_files_sparse) & Kernel Operation Chaining (IOSQE_IO_LINK):
    • Registers a 1,024-slot in-kernel Direct File Descriptor table (IOSQE_FIXED_FILE), completely bypassing the POSIX process file descriptor table (/proc/self/fd) and eliminating kernel files_struct lock contention.
    • Chains multi-step lifecycles (OPENAT_DIRECT ➔ WRITE [➔ FSYNC] ➔ CLOSE_DIRECT, and linked READ pipelines) inside the kernel via IOSQE_IO_LINK in a single submission without returning to user space between steps.
  3. Configurable Optimistic Read (optimisticReadSize, default 64 KB):
    • Eliminates the STATX roundtrip for files smaller than optimisticReadSize by chaining OPENAT_DIRECT ➔ READ directly, cutting SQEs by 25% (3 instead of 4).
    • Configurable globally (configure({ optimisticReadSize }) / setOptimisticReadSize(bytes)) or per-call (readFile(path, { optimisticReadSize })).
  4. Tick Auto-Batching (uv_prepare_t + uv_check_t): Automatically batches concurrent file operations issued in the same Event Loop tick into a single io_uring_enter syscall (saving up to 99.8% of syscalls).
  5. Modern Kernel Optimizations (SINGLE_ISSUER + COOP_TASKRUN): Automatically enables IORING_SETUP_SINGLE_ISSUER and IORING_SETUP_COOP_TASKRUN on Linux 6.0+ (with graceful fallback on older kernels) to eliminate kernel submission spinlocks and inter-processor interrupts (IPIs).
  6. Zero-Copy V8 Buffer Handoff & In-Kernel splice Copy:
    • readFile uses a hot pre-faulted buffer pool for small reads and zero-copy V8 external buffer handoff for large reads.
    • copyFile uses io_uring_prep_splice (IORING_OP_SPLICE) to move bytes directly between file descriptors inside the Linux kernel page cache with zero user-space memory allocation.
  7. CVE-2024-22017 Credential Guard: Tracks geteuid() / getegid() per batch and automatically resets the ring if process privileges change.
  8. 3-Tier Pluggable Fallback System: Works everywhere (macOS, Windows, restricted Docker) with your choice of:
    • 'fs' (default): node:fs/promises
    • 'worker': Built-in dedicated worker_threads I/O pool (still isolated from libuv's 4-thread pool!)
    • Custom Provider Object: Plug in graceful-fs, fs-extra, memfs, or any custom adapter
    • false: Strict mode (throws ERR_URING_UNSUPPORTED if io_uring is inactive)

Benchmarks Across Node.js 20, 22, 24, and 26 (Linux 6.8)

| Workload / Metric | Node.js v20 | Node.js v22 | Node.js v24 | Node.js v26 | | :--- | :---: | :---: | :---: | :---: | | 1,000 Concurrent Reads (4 KB each)uring-fs vs node:fs/promises | 4.12 ms vs 16.80 ms(4.08x faster) | 3.85 ms vs 15.90 ms(4.13x faster) | 3.79 ms vs 15.40 ms(4.06x faster) | 3.62 ms vs 14.95 ms(4.13x faster) | | 1,000 Concurrent Writes (4 KB each)uring-fs vs node:fs/promises | 7.90 ms vs 21.50 ms(2.72x faster) | 7.51 ms vs 20.80 ms(2.77x faster) | 7.40 ms vs 20.10 ms(2.72x faster) | 7.15 ms vs 19.60 ms(2.74x faster) | | 100 Concurrent Reads (128 KB each)uring-fs vs node:fs/promises | 1.85 ms vs 2.29 ms(1.24x faster) | 1.73 ms vs 2.00 ms(1.16x faster) | 1.78 ms vs 2.15 ms(1.21x faster) | 1.65 ms vs 1.90 ms(1.15x faster) | | Auto-Batching Syscall Reduction(15,000 SQEs dispatched) | 99.8% saved(35 syscalls) | 99.8% saved(35 syscalls) | 99.8% saved(35 syscalls) | 99.8% saved(35 syscalls) | | Mixed Server Load (20x 128 KB + pbkdf2)uring-fs vs node:fs/promises | 1.05 ms vs 1.51 ms(1.4x faster)* | 0.43 ms vs 91.54 ms(214.1x faster) | 0.43 ms vs 77.58 ms(179.1x faster) | 0.42 ms vs 68.49 ms(164.5x faster) | | Thread-Pool Starvation Latencyuring-fs vs node:fs/promises | 0.96 ms vs 1.93 ms(2.0x faster)* | 0.34 ms vs 227.42 ms(675x faster) | 0.36 ms vs 191.79 ms(540x faster) | 0.78 ms vs 178.71 ms(229x faster) |

*Note: Early Node 20.6–20.11 releases had experimental internal libuv io_uring enabled before it was disabled in Node 20.11.1+ due to CVE-2024-22017. Even against libuv's own internal io_uring, uring-fs is 1.4x–2.0x faster while remaining protected against CVE-2024-22017.


Installation

npm install uring-fs

Linux Native io_uring Prerequisites

If <liburing.h> is present during npm install, uring-fs compiles the native C++ io_uring engine automatically. If liburing-dev is not installed yet (or on macOS/Windows), npm install still succeeds cleanly using a lightweight stub and your configured fallback backend.

# Ubuntu / Debian
sudo apt update && sudo apt install -y liburing-dev build-essential

# Fedora / RHEL
sudo dnf install -y liburing-devel gcc-c++ make

# Arch Linux
sudo pacman -S liburing base-devel

Quick Start (CommonJS & ESM)

import {
  readFile,
  writeFile,
  appendFile,
  copyFile,
  stat,
  lstat,
  mkdir,
  rename,
  symlink,
  link,
  fsync,
  fdatasync,
  unlink,
  isUringSupported,
  getRingStats,
  configure,
} from 'uring-fs';

console.log('io_uring active:', isUringSupported());

// Write with optional in-kernel fsync before close ({ flush: true })
await writeFile('hello.txt', 'Hello from Linux Kernel io_uring!\n', { flush: true });
await appendFile('hello.txt', 'Appended asynchronously.\n');

// Zero-copy in-kernel file copy via io_uring_prep_splice
await copyFile('hello.txt', 'hello_copy.txt');

// Atomic rename, symlinks, hardlinks, and lstat
await rename('hello_copy.txt', 'hello_renamed.txt');
await symlink('hello.txt', 'hello.link');
const linkInfo = await lstat('hello.link');
console.log('Is symlink:', linkInfo.isSymbolicLink());

const content = await readFile('hello.txt', 'utf8');
console.log(content);

// Inspect ring telemetry and syscall savings
console.log(getRingStats());

await unlink('hello.link');
await unlink('hello_renamed.txt');
await unlink('hello.txt');

Supported API

| Function | Kernel io_uring Opcode(s) | Description | | :--- | :--- | :--- | | readFile(path, options?) | OPENAT_DIRECT ➔ READ ➔ CLOSE_DIRECT (Optimistic <64KB)OPENAT_DIRECT ➔ STATX ➔ READ ➔ CLOSE_DIRECT (Large) | Reads entire file with configurable optimisticReadSize and zero-copy V8 handoff | | writeFile(path, data, options?) | OPENAT_DIRECT ➔ WRITE (➔ FSYNC) ➔ CLOSE_DIRECT (IOSQE_IO_LINK) | Writes string, Buffer, or Uint8Array in a single linked kernel chain | | appendFile(path, data, options?) | OPENAT_DIRECT(O_APPEND) ➔ WRITE ➔ CLOSE_DIRECT (IOSQE_IO_LINK) | Appends data atomically at end of file | | copyFile(src, dest, mode?) | OPENAT ➔ STATX ➔ SPLICE ➔ CLOSE | Zero-copy kernel-to-kernel copy via io_uring_prep_splice | | stat(path, options?) | STATX (0) | Retrieves UringStats (follows symlinks) | | lstat(path, options?) | STATX (AT_SYMLINK_NOFOLLOW) | Retrieves UringStats without following symlinks | | rename(oldPath, newPath, options?) | RENAMEAT | Atomically renames/moves a file or directory | | symlink(target, path, options?) | SYMLINKAT | Creates a symbolic link | | link(existingPath, newPath, options?) | LINKAT | Creates a hard link | | fsync(path, options?) | OPENAT_DIRECT ➔ FSYNC (0) ➔ CLOSE_DIRECT (IOSQE_IO_LINK) | Flushes modified file data and metadata to disk | | fdatasync(path, options?) | OPENAT_DIRECT ➔ FSYNC (DATASYNC) ➔ CLOSE_DIRECT (IOSQE_IO_LINK) | Flushes modified file data to disk | | mkdir(path, options?) | MKDIRAT | Creates a directory (single or { recursive: true }) | | unlink(path, options?) | UNLINKAT | Removes a file or symbolic link | | setOptimisticReadSize(bytes) | (Configuration) | Sets default optimistic read buffer size in bytes (0 disables) |


Tuning & 3-Tier Fallback Configuration

You can tune the optimistic read buffer size globally or per-call, and configure how uring-fs behaves when running outside Linux or inside a container where io_uring is restricted by seccomp:

import { configure, setOptimisticReadSize, readFile } from 'uring-fs';
import gracefulFs from 'graceful-fs';

// Tune Optimistic Read buffer size globally (default: 65536 = 64 KB; 0 disables)
configure({ optimisticReadSize: 131072 }); // 128 KB
setOptimisticReadSize(65536);              // 64 KB

// Or override per readFile call:
const data = await readFile('medium-asset.bin', { optimisticReadSize: 262144 });

// 1. Default: Fallback to node:fs/promises
configure({ fallback: 'fs' });

// 2. Dedicated Worker Pool: Uses node:worker_threads so file I/O STILL avoids
// competing with crypto/zlib on libuv's 4-thread pool even on macOS/Windows/Docker!
configure({ fallback: 'worker', workerPoolSize: 2 });

// 3. Custom Provider: Plug in graceful-fs, fs-extra, memfs, or a custom storage engine
configure({ fallback: gracefulFs.promises });

// 4. Strict Mode: Throw ERR_URING_UNSUPPORTED if kernel io_uring is unavailable
configure({ fallback: false });

License

MIT