Skip to content

Commit 54666c4

Browse files
committed
refactor: remove splice optimization
It seems to be upstream now, see: rust-lang/rust#75272 However for rsop splice is not used anymore, see 'test_splice' script.
1 parent 22aa982 commit 54666c4

File tree

3 files changed

+36
-89
lines changed

3 files changed

+36
-89
lines changed

Cargo.toml

+1-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const_format = { version = "0.2", features = ["const_generics"] }
99
crossbeam-utils = "0.8"
1010
lazy_static = "1.4"
1111
log = { version = "0.4", features = ["max_level_trace", "release_max_level_info"] }
12+
nix = { version = "0.22", default-features = false }
1213
regex = "1.5"
1314
serde = { version = "1.0", features = ["derive"]}
1415
shlex = "1.0.0"
@@ -23,6 +24,3 @@ toml = "0.5"
2324
tree_magic_mini = "3.0"
2425
url = "2.2"
2526
xdg = "2.1"
26-
27-
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
28-
nix = { version = "0.22", default-features = false }

src/handler.rs

+14-86
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
use std::collections::HashMap;
22
use std::env;
3-
#[cfg(any(target_os = "linux", target_os = "android"))]
43
use std::fs::File;
5-
use std::io::{self, stdin, Read, Write};
6-
#[cfg(not(any(target_os = "linux", target_os = "android")))]
7-
use std::io::{copy, StdinLock};
4+
use std::io::{self, copy, Read, Write};
85
use std::os::unix::fs::FileTypeExt;
9-
#[cfg(any(target_os = "linux", target_os = "android"))]
106
use std::os::unix::io::{AsRawFd, FromRawFd};
117
use std::path::{Path, PathBuf};
12-
use std::process::{Child, ChildStdout, Command, Stdio};
8+
use std::process::{Child, Command, Stdio};
139
use std::rc::Rc;
1410

1511
use crate::config;
@@ -108,20 +104,6 @@ lazy_static::lazy_static! {
108104
nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE).expect("Unable to get page size").unwrap() as usize;
109105
}
110106

111-
#[cfg(not(any(target_os = "linux", target_os = "android")))]
112-
trait ReadPipe: Read + Send {}
113-
114-
#[cfg(any(target_os = "linux", target_os = "android"))]
115-
trait ReadPipe: Read + AsRawFd + Send {}
116-
117-
#[cfg(not(any(target_os = "linux", target_os = "android")))]
118-
impl ReadPipe for StdinLock<'_> {}
119-
120-
#[cfg(any(target_os = "linux", target_os = "android"))]
121-
impl ReadPipe for File {}
122-
123-
impl ReadPipe for ChildStdout {}
124-
125107
impl HandlerMapping {
126108
pub fn new(cfg: &config::Config) -> anyhow::Result<HandlerMapping> {
127109
let mut handlers_open = FileHandlers::new(&cfg.default_handler_open);
@@ -214,7 +196,11 @@ impl HandlerMapping {
214196
}
215197

216198
pub fn handle_pipe(&self, mode: RsopMode) -> Result<(), HandlerError> {
217-
let stdin = Self::stdin_reader()?;
199+
let stdin_locked = io::stdin().lock();
200+
// Unlocked stdin via io::stdin does not allow use of copy optimisation (splice & co)
201+
// The conversion to File via its fd allows breaking the Send requirement of the MutexGuard
202+
let stdin = unsafe { File::from_raw_fd(stdin_locked.as_raw_fd()) };
203+
//let stdin = BufReader::new(stdin_file);
218204
self.dispatch_pipe(stdin, &mode)
219205
}
220206

@@ -301,7 +287,7 @@ impl HandlerMapping {
301287
#[allow(clippy::wildcard_in_or_patterns)]
302288
fn dispatch_pipe<T>(&self, mut pipe: T, mode: &RsopMode) -> Result<(), HandlerError>
303289
where
304-
T: ReadPipe,
290+
T: Read + Send,
305291
{
306292
// Handler candidates
307293
let handlers = match mode {
@@ -520,7 +506,7 @@ impl HandlerMapping {
520506
mode: &RsopMode,
521507
) -> Result<(), HandlerError>
522508
where
523-
T: ReadPipe,
509+
T: Read + Send,
524510
{
525511
let term_size = Self::term_size();
526512

@@ -609,7 +595,7 @@ impl HandlerMapping {
609595
term_size: &termsize::Size,
610596
) -> Result<(), HandlerError>
611597
where
612-
T: ReadPipe,
598+
T: Read + Send,
613599
{
614600
// Write to a temporary file if handler does not support reading from stdin
615601
let input = if handler.no_pipe {
@@ -683,34 +669,16 @@ impl HandlerMapping {
683669
Ok(())
684670
}
685671

686-
#[cfg(not(any(target_os = "linux", target_os = "android")))]
687-
fn stdin_reader() -> anyhow::Result<StdinLock<'static>> {
688-
let stdin = Box::leak(Box::new(stdin()));
689-
Ok(stdin.lock())
690-
}
691-
692-
#[cfg(any(target_os = "linux", target_os = "android"))]
693-
fn stdin_reader() -> anyhow::Result<File> {
694-
// Unfortunately, stdin is buffered, and there is no clean way to get it
695-
// unbuffered to read only what we want for the header, so use fd hack to get an unbuffered reader
696-
// see https://users.rust-lang.org/t/add-unbuffered-rawstdin-rawstdout/26013
697-
// On plaforms other than linux we don't care about buffering because we use chunk copy instead of splice
698-
let stdin = stdin();
699-
let reader = unsafe { File::from_raw_fd(stdin.as_raw_fd()) };
700-
Ok(reader)
701-
}
702-
703-
#[cfg(not(any(target_os = "linux", target_os = "android")))]
704-
// Default chunk copy using stdlib's std::io::copy when splice syscall is not available
705-
fn pipe_forward<S, D>(src: &mut S, dst: &mut D, header: &[u8]) -> anyhow::Result<u64>
672+
// Default copy using stdlib's std::io::copy (uses splice syscall when available on Linux)
673+
fn pipe_forward<S, D>(src: &mut S, dst: &mut D, header: &[u8]) -> anyhow::Result<usize>
706674
where
707675
S: Read,
708676
D: Write,
709677
{
710678
dst.write_all(header)?;
711679
log::trace!("Header written ({} bytes)", header.len());
712680

713-
let copied = copy(src, dst)?;
681+
let copied = copy(src, dst)? as usize;
714682
log::trace!(
715683
"Pipe exhausted, moved {} bytes total",
716684
header.len() + copied
@@ -719,49 +687,9 @@ impl HandlerMapping {
719687
Ok(header.len() + copied)
720688
}
721689

722-
#[cfg(any(target_os = "linux", target_os = "android"))]
723-
// Efficient 0-copy implementation using splice
724-
fn pipe_forward<S, D>(src: &mut S, dst: &mut D, header: &[u8]) -> anyhow::Result<usize>
725-
where
726-
S: AsRawFd,
727-
D: AsRawFd + Write,
728-
{
729-
dst.write_all(header)?;
730-
log::trace!("Header written ({} bytes)", header.len());
731-
732-
let mut c = 0;
733-
const SPLICE_LEN: usize = 2usize.pow(62); // splice returns -EINVAL for pipe to file with usize::MAX len
734-
const SPLICE_FLAGS: nix::fcntl::SpliceFFlags = nix::fcntl::SpliceFFlags::empty();
735-
736-
loop {
737-
let rc = nix::fcntl::splice(
738-
src.as_raw_fd(),
739-
None,
740-
dst.as_raw_fd(),
741-
None,
742-
SPLICE_LEN,
743-
SPLICE_FLAGS,
744-
);
745-
let moved = match rc {
746-
Err(e) if e == nix::errno::Errno::EPIPE => 0,
747-
Err(e) => return Err(anyhow::Error::new(e)),
748-
Ok(m) => m,
749-
};
750-
log::trace!("moved = {}", moved);
751-
if moved == 0 {
752-
break;
753-
}
754-
c += moved;
755-
}
756-
757-
log::trace!("Pipe exhausted, moved {} bytes total", header.len() + c);
758-
759-
Ok(header.len() + c)
760-
}
761-
762690
fn pipe_to_tmpfile<T>(header: &[u8], mut pipe: T) -> anyhow::Result<tempfile::NamedTempFile>
763691
where
764-
T: ReadPipe,
692+
T: Read + Send,
765693
{
766694
let mut tmp_file = tempfile::Builder::new()
767695
.prefix(const_format::concatcp!(env!("CARGO_PKG_NAME"), '_'))

test_splice

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash -eu
2+
3+
set -o pipefail
4+
5+
6+
# auto cleanup
7+
at_exit() {
8+
set +u
9+
rm -Rf "$TMP_DIR"
10+
set -u
11+
}
12+
trap at_exit EXIT
13+
14+
readonly TMP_FILE="$(mktemp /tmp/"$(basename -- "$0")".XXXXXXXXXX)"
15+
16+
17+
dd if=/dev/urandom of="${TMP_FILE}" bs=4k count=1000
18+
19+
cargo build --release
20+
21+
RSOP_MODE=preview strace ./target/release/rsop <"${TMP_FILE}" 2>&1 | grep splice

0 commit comments

Comments
 (0)