mirror of
https://github.com/trapexit/mergerfs.git
synced 2026-10-26 19:01:56 -04:00
Reinstate the splice read/write/move paths removed in 84592a9f, now
gated behind runtime options with negotiated pipe sizing and fallback
logging.
- Transport: msgbuf-piped splice receive, fd-referenced splice reply
(vmsplice + splice), splice-move pipe-bounce, and restored
fuse_buf_copy/fuse_buf_flags plumbing for copy fallbacks
- Negotiation: new pipe-max-size option (auto-raised at INIT when
possible; the sysctl read/write is factored into src/pipe_max_size.cpp
so fuse_init.cpp and option_parser.cpp share one implementation);
fuse-msg-size clamped to what the pipe can hold; splice is disabled
at INIT with a warning if the sysctl can't fit even the minimum,
retracting the FUSE_CAP_SPLICE_* bits already advertised to the
kernel in that case
- Read replies and splice-move are gated at 128 KiB (below that the
syscalls cost more than the copy they avoid); every fallback path
degrades to the copy path and logs once per process via SysLog
- msgbuf pipe cache is process-global and mutex-guarded (mutex_lockguard)
so pipes can move between read and worker threads under
process-thread mode
- API simplified after review: collapsed the three per-axis options
(splice-read/write/move) into a single splice boolean (default on);
no-splice-read/write/move and the bare splice-read/write/move
spellings stay registered as accepted no-ops so existing mount
options don't fail the mount, but they do NOT alter the splice
knob - upgraders with no-splice-* in their config get splice
enabled and must set splice=false explicitly (see
deprecated_options.md)
- Fixed several correctness issues found in parallel review: a
dropped write_buf registration that silently corrupted writes, a
dirty-pipe reuse bug in msgbuf_free, an iov double-send in
fuse_reply_data's splice-move fallback, and pipe-capacity math that
assumed a 4096-byte page size
- Modernized the transport/negotiation code to this codebase's C++
style: RAII (unique_ptr/DEFER) instead of malloc/free and manual fd
close(), std::from_chars/to_chars instead of stoul/snprintf,
_ReplyPipe's pipe ops as methods instead of free functions closing
over a thread_local global, and vendored/libfuse/lib/buffer.cpp (a
near-verbatim port of upstream's buffer.c) brought in line with the
rest of the fork
- Documented splice and pipe-max-size options (mkdocs, FAQ,
deprecated-options list); added tools/bench-splice.sh
- Benchmarked on tmpfs: seq_write +21%, seq_read +52%, small_write
+5%, small_read +12%; validated with a 1h soak (~271 GiB
hash-verified I/O, zero integrity failures), a live-mount round-trip
of I/O above the splice threshold, and the existing unit/smoke
suites at their pre-existing baseline
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
97 lines
3.8 KiB
C++
97 lines
3.8 KiB
C++
/*
|
|
ISC License
|
|
|
|
Copyright (c) 2022, Antonio SJ Musumeci <trapexit@spawn.link>
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "base_types.h"
|
|
#include "fuse_msgbuf_t.h"
|
|
|
|
u32 msgbuf_get_pagesize();
|
|
void msgbuf_set_bufsize(const u64 size);
|
|
u64 msgbuf_get_bufsize();
|
|
|
|
fuse_msgbuf_t *msgbuf_alloc();
|
|
fuse_msgbuf_t *msgbuf_alloc_page_aligned();
|
|
void msgbuf_free(fuse_msgbuf_t *msgbuf);
|
|
|
|
int msgbuf_ensure_pipe(fuse_msgbuf_t *msgbuf);
|
|
void msgbuf_drain_pipe(fuse_msgbuf_t *msgbuf);
|
|
|
|
void msgbuf_clear();
|
|
void msgbuf_gc();
|
|
|
|
u64 msgbuf_alloc_count();
|
|
|
|
/* Shared splice-transport plumbing used by both fuse_msgbuf.cpp and
|
|
fuse_lowlevel.cpp/fuse.cpp - factored here so the two translation
|
|
units can't independently drift on pipe sizing/logging. */
|
|
|
|
/* Below this size the extra splice/vmsplice syscalls cost more than
|
|
the copy they avoid (benchmark rationale: see src/config.cpp's
|
|
comment on the `splice` option). Shared by the read-reply gate
|
|
(fuse.cpp) and the write/splice-move gate (fuse_lowlevel.cpp) so
|
|
the two thresholds can't silently drift apart. */
|
|
constexpr u32 FUSE_SPLICE_MIN_SIZE = 131072u;
|
|
|
|
/* Read /proc/sys/fs/pipe-max-size once per process (cached) and
|
|
return it, clamped to UINT32_MAX; returns 1048576 (the historical
|
|
assumed default) if the sysctl can't be read or parsed. */
|
|
u32 fuse_effective_pipe_max();
|
|
|
|
enum class splice_fallback_tag_t
|
|
{
|
|
PIPE_MAX_READ_FALLBACK,
|
|
MSGBUF_FSETPIPE_EP,
|
|
MSGBUF_FSETPIPE_ANY,
|
|
MSGBUF_PIPE_CREATE,
|
|
REPLY_PIPE_FSETPIPE_EP,
|
|
REPLY_PIPE_FSETPIPE_ANY,
|
|
REPLY_PIPE_ENSURE,
|
|
REPLY_PIPE_CAP,
|
|
REPLY_VMSPLICE_HDR,
|
|
REPLY_FD_SPLICE,
|
|
RECEIVE_MSGBUF_PIPE,
|
|
RECEIVE_SPLICE_ERR,
|
|
REPLY_SPLICE_MOVE_WRITEV,
|
|
};
|
|
|
|
/* Warn once per process per tag so a busy mount doesn't spam
|
|
per-request logs when splice degrades to the copy path. */
|
|
void fuse_splice_fallback_log(splice_fallback_tag_t tag);
|
|
|
|
/* Size a pipe's kernel buffer via F_SETPIPE_SZ, clamping the request
|
|
to INT_MAX and retrying once at the 1MiB default on failure -
|
|
unconditionally when retry_unconditionally is set, otherwise only
|
|
when the first attempt failed with EPERM. Logs tag_any once if the
|
|
sized request failed at all, tag_ep once if every attempt failed.
|
|
Returns the granted size (>0) or -errno of the last attempt (<=0). */
|
|
int fuse_pipe_set_size(int fd,
|
|
u32 cap,
|
|
bool retry_unconditionally,
|
|
splice_fallback_tag_t tag_any,
|
|
splice_fallback_tag_t tag_ep);
|
|
|
|
/* Bounded, non-blocking pipe drain: consume exactly the bytes FIONREAD
|
|
reports as queued, reading into scratch (capped at scratch_size),
|
|
retrying on EINTR. Never blocks - the write end is assumed still
|
|
held by the caller, so a blocking read on an already-drained empty
|
|
pipe would wedge the thread forever. No-op if fd is -1. Shared by
|
|
msgbuf_drain_pipe (this file) and _ReplyPipe::drain
|
|
(fuse_lowlevel.cpp) so the receive-side and reply-side pipes can't
|
|
independently drift on drain semantics. */
|
|
void fuse_drain_pipe_fd(int fd, void *scratch, size_t scratch_size);
|