Skip to main content

Enabling io_uring for the MooseFS Client on Ubuntu 26.04

This guide shows how to run the MooseFS Community Edition client (mfsmount) with FUSE‑over‑io_uring on a stock Ubuntu 26.04 ("Resolute Raccoon") machine.

io_uring lets the kernel and the MooseFS client exchange FUSE requests through a shared ring buffer instead of the traditional read()/write() calls on /dev/fuse. That means fewer system calls and context switches per operation, which can noticeably improve throughput and latency on busy mounts. mfsmount detects at startup whether the FUSE library it is linked against supports io_uring; when it does, you can turn it on with -o io_uring.

The short version​

There are two independent things you need, and Ubuntu 26.04 only gives you one of them out of the box:

  1. A kernel that supports FUSE‑over‑io_uring, with the feature switched on. Good news: the stock Ubuntu 26.04 kernel (Linux 7.0) is already built with CONFIG_FUSE_IO_URING=y. You do not need to compile a custom kernel. You only have to flip a runtime switch: enable_uring.

  2. A FUSE userspace library new enough to actually work. This is the catch. The libfuse3 package shipped in Ubuntu 26.04 is 3.18.2, which was the first release series to include io_uring — and it does not work correctly for this use case. The fixes that make FUSE‑over‑io_uring actually function landed after 3.18.2, on the way to 3.19. So you need to build libfuse from the current upstream sources and have mfsmount use that build.

Everything below is just those two points, done carefully.

Before you start​

This guide assumes:

  • A clean Ubuntu 26.04 LTS install on amd64, with sudo access.
  • A MooseFS master already running somewhere on your network that this client will connect to. We'll use the hostname mfsmaster in the examples — replace it with your master's hostname or IP.
  • You are comfortable pasting shell commands into a terminal.

A quick reality check that you're on a kernel with the feature compiled in:

grep CONFIG_FUSE_IO_URING "/boot/config-$(uname -r)"

You want to see CONFIG_FUSE_IO_URING=y. If instead you get # CONFIG_FUSE_IO_URING is not set or no output at all, your kernel can't do this and the rest of the guide won't help until you boot a kernel that has it.

Step 1 — Turn on the kernel's enable_uring switch​

Even though the capability is compiled in, the kernel keeps it off by default. Turn it on:

echo 1 | sudo tee /sys/module/fuse/parameters/enable_uring

Confirm it took:

cat /sys/module/fuse/parameters/enable_uring
# expect: Y

This setting is not persistent — it resets to N on every reboot. To make it stick, use a systemd tmpfiles rule, which works whether fuse is built in or loaded as a module:

echo 'w /sys/module/fuse/parameters/enable_uring - - - - 1' | \
sudo tee /etc/tmpfiles.d/fuse-iouring.conf

That file is applied automatically early in every boot. (If you prefer the classic approach and fuse is a loadable module on your system — check with lsmod | grep '^fuse' — you can instead create /etc/modprobe.d/fuse.conf containing options fuse enable_uring=1. If fuse is built into the kernel, add fuse.enable_uring=1 to GRUB_CMDLINE_LINUX in /etc/default/grub and run sudo update-grub.)

Step 2 — Build a working libfuse​

We'll build the current upstream libfuse and install it into its own directory, /opt/libfuse-git, so the system's libfuse3 package is left completely untouched. Nothing else on the machine will pick up this build unless we explicitly point it there, which is exactly what we want.

Install the build tools and clone the sources:

sudo apt update
sudo apt install -y git meson ninja-build pkg-config gcc \
liburing-dev python3 libc6-dev

git clone --depth 1 https://github.com/libfuse/libfuse.git
cd libfuse

Configure, compile, and install into the isolated prefix. -Denable-io-uring=true is the default upstream, but we pass it explicitly so there's no ambiguity:

meson setup build --prefix=/opt/libfuse-git --libdir=lib -Denable-io-uring=true
meson compile -C build
sudo meson install -C build

Check what you built:

PKG_CONFIG_PATH=/opt/libfuse-git/lib/pkgconfig pkg-config --modversion fuse3
# expect: 3.19.x (or newer)

You now have a working FUSE library at /opt/libfuse-git/lib/libfuse3.so.4, alongside — but separate from — the distribution's /lib/x86_64-linux-gnu/libfuse3.so.4.

Step 3 — Install the MooseFS CE client​

Try the official package first. The MooseFS 4 CE repository has a native Ubuntu 26.04 ("Resolute Raccoon") build — the resolute suite — with moosefs-client for both amd64 and arm64:

curl https://repository.moosefs.com/moosefs.key | \
sudo gpg -o /etc/apt/keyrings/moosefs.gpg --dearmor
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/moosefs.gpg] http://repository.moosefs.com/moosefs-4/apt/ubuntu/resolute resolute main" | \
sudo tee /etc/apt/sources.list.d/moosefs.list
sudo apt update
sudo apt install -y moosefs-client

(On arm64, change arch=amd64 to arch=arm64.)

Now confirm the client is linked against libfuse3 (the io_uring‑capable FUSE generation) rather than the older libfuse2:

ldd "$(command -v mfsmount)" | grep -i fuse
  • If you see libfuse3.so.4, the package will work — continue to Step 4, Option A.
  • If you see libfuse.so.2 (libfuse 2), this build of the package can't do io_uring at all. Skip to Step 4, Option B and build the client from source against libfuse3.

Step 4 — Point the client at your libfuse build​

mfsmount is dynamically linked to libfuse3.so.4, and by default the loader resolves that to the distribution copy. We need this one binary — and nothing else on the system — to load our /opt/libfuse-git build instead.

Bake an rpath into the installed mfsmount binary so it looks in /opt/libfuse-git/lib first. This affects only mfsmount; every other program keeps using the system libfuse.

sudo apt install -y patchelf
sudo patchelf --add-rpath /opt/libfuse-git/lib "$(command -v mfsmount)"

Verify that mfsmount now resolves libfuse to the isolated build:

ldd "$(command -v mfsmount)" | grep -i fuse
# expect a path under /opt/libfuse-git/lib/libfuse3.so.4
note

A future apt upgrade of moosefs-client will replace the binary and drop the rpath. Just re‑run the patchelf command after any client upgrade (or sudo apt-mark hold moosefs-client if you want to freeze the version).

Option B — Build the MooseFS client from source​

Use this if the packaged client is libfuse2‑based, if no suitable package exists for your release, or if you simply prefer building it yourself. Building against the isolated libfuse guarantees mfsmount uses it, and we bake in the rpath at link time.

sudo apt install -y build-essential autoconf automake libtool pkg-config zlib1g-dev

git clone https://github.com/moosefs/moosefs.git
cd moosefs
./autogen.sh

# Point the build at the isolated libfuse and record its location in the binary:
PKG_CONFIG_PATH=/opt/libfuse-git/lib/pkgconfig \
LDFLAGS="-Wl,-rpath,/opt/libfuse-git/lib" \
./configure --disable-mfsmaster --disable-mfschunkserver \
--enable-mfsmount --prefix=/usr/local

make -j"$(nproc)"
sudo make install

Watch the ./configure summary: it should report that it found FUSE version 3.x. The client binary lands at /usr/local/sbin/mfsmount, which keeps it clearly separate from anything the package manager installs. Confirm the linkage the same way:

ldd /usr/local/sbin/mfsmount | grep -i fuse
# expect a path under /opt/libfuse-git/lib/libfuse3.so.4

Step 5 — Mount MooseFS with io_uring​

Create a mount point and mount, adding -o io_uring:

sudo mkdir -p /mnt/mfs
sudo mfsmount /mnt/mfs -H mfsmaster -o io_uring

If your mfsmount came from Option B, call it by its full path (/usr/local/sbin/mfsmount) or make sure /usr/local/sbin is ahead of /usr/sbin in your PATH.

You can tune the ring depth if you like; the FUSE default is 8:

sudo mfsmount /mnt/mfs -H mfsmaster -o io_uring -o io_uring_q_depth=16

If io_uring can't be started for any reason, mfsmount reports it and falls back to the classic FUSE transport, so the mount still succeeds — see Troubleshooting below to find out why.

Step 6 — Confirm io_uring is actually in use​

Three quick checks, from most to least direct.

The kernel switch is on:

cat /sys/module/fuse/parameters/enable_uring # Y

The running client loaded your libfuse, not the system one:

pid=$(pgrep -x mfsmount | head -n1)
grep -o '/[^ ]*libfuse3[^ ]*' "/proc/$pid/maps" | sort -u
# expect: /opt/libfuse-git/lib/libfuse3.so.4

The io_uring worker threads exist. When FUSE‑over‑io_uring is active, libfuse spins up one ring worker thread per CPU, named fuse-ring-N:

ps -L -p "$(pgrep -x mfsmount | head -n1)" -o comm= | grep fuse-ring | sort -u
# expect: fuse-ring-0, fuse-ring-1, ...

If you see fuse-ring-* threads and the maps point at /opt/libfuse-git, io_uring is working.

Step 7 — Make it permanent​

Two things need to survive a reboot:

  • The enable_uring switch — already handled by the tmpfiles.d rule in Step 1.
  • The mount itself — the cleanest way is a small systemd service or an /etc/fstab entry using the MooseFS mount helper. The exact fstab syntax and available options are documented in man mfsmount and man mfsmount.cfg; the important part for this guide is simply to include io_uring among the mount options, e.g. inside /etc/mfs/mfsmount.cfg alongside your mfsmaster= line. Whichever method you choose, if you used Option A, remember to re‑apply the patchelf rpath after any moosefs-client package upgrade.

Troubleshooting​

mfsmount prints failed to start io-uring: Unknown error -95 (or silently falls back to classic FUSE). -95 is EOPNOTSUPP. Work through it in this order:

  • Is the kernel switch on? cat /sys/module/fuse/parameters/enable_uring must print Y before you mount. If it's N, the kernel won't advertise io_uring during the FUSE handshake and the client falls back.
  • Which libfuse did the client actually load? Re‑run the /proc/<pid>/maps check from Step 6. If it shows /lib/x86_64-linux-gnu/libfuse3.so.4 (the 3.18.2 system copy), the rpath from Step 4 didn't take — the packaged binary was probably upgraded and reverted. Re‑run the patchelf command.
  • Is io_uring restricted system‑wide? Some hardened setups disable it via sysctl: sysctl kernel.io_uring_disabled should be 0. A value of 1 or 2 blocks io_uring regardless of everything else.
  • Is the client libfuse2‑based? ldd "$(command -v mfsmount)" | grep -i fuse. If it links libfuse.so.2, it cannot do io_uring; use Step 4, Option B.

Everything looks right but it still falls back. Double‑check that the libfuse build reported version 3.19.x or newer in Step 2. The 3.18.2 release that ships with Ubuntu 26.04 carries only the io_uring security fixes (CVE‑2026‑33150 and CVE‑2026‑33179); the changes that make the feature usable came afterward, so an older build will not work here.

Why this dance is necessary (background)​

FUSE‑over‑io_uring is a young feature. Kernel support was merged in Linux 6.14 and is present and enabled in Ubuntu 26.04's Linux 7.0 kernel. The userspace half landed in libfuse 3.18.0, but the first releases were rough: 3.18.1 and 3.18.2 were rushed out to fix memory‑safety bugs in the new code path, and the work that makes the transport actually negotiate and run correctly continued afterward toward the 3.19 release. Because Ubuntu 26.04 froze on libfuse 3.18.2, the distribution's own package can't drive io_uring end‑to‑end even though the kernel underneath it is fully capable. Building the current libfuse and pointing mfsmount at it bridges that gap until a fixed libfuse is packaged.

References​