After handling a request, a sync read thread polls its /dev/fuse fd for
up to fuse-read-spin-us microseconds before blocking in read(). Default
0 (disabled). Measured at 50us (tmpfs branch, direct_io): sequential
128K/1M reads +27-32%, 128K writes +14%, metadata walks +24-32%; no
change on 8 parallel streams or 1M writes. Costs roughly one core on a
busy-but-quiet mount, nothing when idle.
Describe why mergerfs does not aggregate branch performance like
RAID/ZFS (whose aggregate throughput is also capped by shared
interconnects) and instead provides independent per-branch
performance: best case A+B+C across concurrent streams, worst case
single-stream limited to one branch's speed.
- Store cloned read-worker fds on fuse_session and close them there.
- Set up clone fallback through session helpers used by the loop.
- Carry per-request fds through receive, process, and reply paths.
Tests show significant performance increases in highly concurrent
workloads on a high core count system. Upto 15% better on reads and
27% on writes.
- Expose the ioprio enabled flag as an atomic for relaxed inline checks
- Inline SetFrom construction so disabled read/write paths avoid a function call
- Move priority application into a slow helper used only when ioprio is enabled
- Guard fuse_cfg_t log file and filepath shared pointers with a shared mutex
- Replace atomic shared pointer loads and stores with shared and unique locks
- Replace atomic shared pointer loads/stores with shared/unique locking.
- Store the configured readdir mode directly as std::string.
- Copy the active ReadDir implementation under lock before invoking it.
- Use relaxed increment under the map bucket lock when duplicating file handles.
- Use acq_rel decrement when releasing and checking for final references.
- Load moved ref_count with relaxed ordering during map insertion.
- Explain that the mutex is per readdir call.
- Document that only dirent deduplication and list append are serialized.
- Clarify that per-branch opendir and getdents64 work runs outside the critical section.
- Replace the per-file Mutex with std::shared_mutex for shared write access.
- Run direct and cached writes under a shared lock while the fd is stable.
- Retry failed writes under a unique lock before move-on-ENOSPC relocation.
- Add helpers that release path locks and update node state under f.lock.
- Update getattr and setattr stat-cache invalidation while preserving ESTALE handling.
- Rework create, open, tmpfile, and set_path_info to mutate lookup/open state under the shared lock.
- Document the lock/unlock pair as an acquire/release barrier.
- Explain that releasedir waits for in-flight readdir calls before destroy/free.
- Warn against removing the synchronization despite it not being a critical section.
Important changes:
- Switch mergerfs and vendored libfuse Makefiles to -std=c++20.
- Select the latest available GCC/G++ or toolset in container builds.
- Verify compiler C++20 support during package installation.
- Fix dynamic SysLog formatting for fmt C++20 checks.
- Add mergerfs_mount() context manager in posix_parity.py that creates
temporary branch dirs in tests/.test_tmp/, mounts an ephemeral mergerfs
instance, and cleans up after use.
- Add find_mergerfs(), find_fusermount(), mount_mergerfs(), unmount_mergerfs()
helpers.
- Update run-tests to invoke each TEST_* script without a mountpoint arg.
- Update README.md to document the new `python3 tests/run-tests` invocation.
- Fix 44 tests to use mergerfs_mount() instead of expecting a pre-mounted path.
- Add tempfile import to 10 tests that were missing it.
- Fix TEST_mount_lifecycle to use mergerfs_mount() context manager.
- Fix TEST_posix_tmpfile missing join import.
- Fix TEST_cfg_xattr_modes error message framing (no "(known bug)").
- Fix TEST_posix_copy_file_range: loop-copy full size, compare st_size and
SHA256 hash; add NativeTmp for space management; route native temp to
tests/.test_tmp/ to avoid ENOSPC on /tmp.
- Fix TEST_posix_getattr_fgetattr: st_cmp skips st_size for dirs; add
compare_ino param for safe inode comparison when inodecalc=passthrough.
- Fix TEST_unlink_rename, TEST_use_* tests: namespace cleanup, consistent
variable naming, None check for cmpfunc.
- Fix TEST_readlink_semantics: remove type annotations to support older
Python; wrap in mergerfs_mount() try/except.
- Add flush=True to run-tests print so test names show immediately.
- Fix typo in static.installer: /pkg/ -> /pkgs/
- Add missing mkdir -p /build before makeself
- Mount pkgs directory via podman build -v so static.installer can access
previously built static tarballs without relying on bind mounts relative
to the build context
- Register qemu-user-static binfmt handlers for cross-compilation
- Make --git-repo optional when using --setup
- Reserve a zero stat CRC to represent an invalid cache fingerprint.
- Include device, size, inode, mtime, and ctime data in the fingerprint.
- Recompute the fingerprint during auto-cache open before keeping cache.
- Make the test runner fail on failed tests and report explicit skips
- Strengthen assertions for O_DIRECT, LUP policy, EXDEV fallback, and xattrs
- Fix cleanup and file descriptor handling across test scripts
No functional change: libfuse/helper.cpp (fuse_daemonize,
fuse_helper_opt_proc, fuse_parse_cmdline) and mergerfs
fuse_init.cpp (_want helpers) reformatted to match the project's
GNU-style brace placement and aligned parameter lists.
- fuse_opt: reformat process_opt_param to project style; return the
rv from sscanf through a named local so the error branch prints
the failing option cleanly.
- cpu.cpp: when parsing /sys/devices/system/cpu/*/topology/core_id,
bail out cleanly on stream read failure rather than recording an
uninitialized core_id.
- fusermount: free(copy) on the success path of parsing the mount
option (previously leaked on every mount), and replace the fixed-
buffer sprintf into procfile with snprintf bounded by the buffer
size.
- maintenance_thread: widen the iteration count from int to u64
so a long-lived daemon doesn't wrap after ~68 years of ticks
(real effect: silence signed-overflow warnings under UBSan).
- ThreadPool::destroy used to iterate _threads directly while another
thread could be adjusting the same vector. Snapshot the vector
under the mutex and iterate the local copy for pthread_cancel /
pthread_join.
- ObjPool::construct now returns NULL when the pooled allocator
returns NULL instead of invoking placement-new on a null
pointer. In destroy, compute to_node(obj) before running the
destructor - the destructor may scrub bytes the to_node helper
reads.
- In the async/sync workers in fuse_loop, msgbuf_alloc can return
NULL under memory pressure. Fail the worker cleanly with a
diagnostic rather than dereferencing. Also introduce a small
_nproc() helper that treats hardware_concurrency()==0 as 1, so
the downstream min/max arithmetic doesn't produce a zero thread
count.
The signal handler reads the global session pointer from an async-
signal context while another thread installs/removes the session
via set/remove_signal_handlers. Use std::atomic<fuse_session*> with
acquire/release ordering so the handler either sees NULL or a fully
published session, never a torn pointer.