diff --git a/rust/src/zstd_cli.rs b/rust/src/zstd_cli.rs index df6c801fd..3dbe1e34b 100644 --- a/rust/src/zstd_cli.rs +++ b/rust/src/zstd_cli.rs @@ -210,6 +210,7 @@ struct Cli { mmap_dict: i32, progress: i32, workers: Option, + single_thread: bool, block_size: Option, mem_limit: Option, ldm: bool, @@ -254,6 +255,7 @@ impl Cli { mmap_dict: ZSTD_PS_AUTO, progress: FIO_PS_AUTO, workers: None, + single_thread: false, block_size: None, mem_limit: None, ldm: false, @@ -332,8 +334,11 @@ unsafe fn default_worker_count() -> i32 { } #[cfg(feature = "compression")] -unsafe fn resolved_worker_count(workers: Option) -> i32 { +unsafe fn resolved_worker_count(workers: Option, single_thread: bool) -> i32 { match workers { + /* --single-thread pins zero workers; a bare zero (-T0 or the zstdmt + * program name) auto-detects the core count as in the C CLI. */ + Some(0) if single_thread => 0, Some(0) => unsafe { UTIL_countPhysicalCores() }.max(1), Some(workers) => workers, None => unsafe { default_worker_count() }, @@ -385,7 +390,7 @@ fn usage(advanced: bool) { let _ = writeln!(out, "\nImplemented advanced compression controls:"); let _ = writeln!( out, - " --fast[=#], --ultra, --long[=#], --threads=#, --block-size=#" + " --fast[=#], --ultra, --long[=#], --threads=#, --single-thread, --block-size=#" ); let _ = writeln!( out, @@ -638,6 +643,7 @@ fn parse_long_option( | "--no-row-match-finder" | "--row-match-finder" | "--rsyncable" + | "--single-thread" | "--compress-literals" | "--no-compress-literals" | "--exclude-compressed" @@ -809,6 +815,14 @@ fn parse_long_option( cli.workers = Some(parse_worker_count(&value)?); Ok(None) } + "--single-thread" => { + /* As in the C CLI: zero workers plus a latch that suppresses the + * automatic core-count resolution, so fileio runs its + * single-thread streaming mode (slightly different from -T1). */ + cli.workers = Some(0); + cli.single_thread = true; + Ok(None) + } "--memlimit" | "--memory" | "--memlimit-decompress" => { let value = next_value(attached, args, index, name)?; cli.mem_limit = Some(parse_u32(&value, "memory limit")?); @@ -854,7 +868,6 @@ fn parse_long_option( | "--trace" | "--format" | "--priority" - | "--single-thread" | "--auto-threads" | "--fake-stdin-is-console" | "--fake-stdout-is-console" @@ -1012,7 +1025,7 @@ unsafe fn apply_preferences(cli: &Cli, prefs: *mut FIO_prefs_t, ctx: *mut FIO_ct }), ); #[cfg(feature = "compression")] - FIO_setNbWorkers(prefs, resolved_worker_count(cli.workers)); + FIO_setNbWorkers(prefs, resolved_worker_count(cli.workers, cli.single_thread)); FIO_setLdmFlag(prefs, u32::from(cli.ldm)); FIO_setAdaptiveMode(prefs, i32::from(cli.adapt)); FIO_setRsyncable(prefs, i32::from(cli.rsyncable)); @@ -1502,6 +1515,36 @@ mod tests { let cli = parse(&["zstdmt", "input"]); assert_eq!(cli.workers, Some(0)); + assert!(!cli.single_thread); + } + + #[test] + fn single_thread_pins_zero_workers() { + let cli = parse(&["zstd", "--single-thread", "input"]); + + assert_eq!(cli.workers, Some(0)); + assert!(cli.single_thread); + } + + #[test] + fn a_later_thread_count_overrides_single_thread_workers() { + /* Mirrors the C CLI: -T after --single-thread wins the worker count, + * while the single-thread latch stays set. */ + let cli = parse(&["zstd", "--single-thread", "-T2", "input"]); + + assert_eq!(cli.workers, Some(2)); + assert!(cli.single_thread); + } + + #[test] + fn single_thread_rejects_attached_values() { + let error = parse_args(vec![ + OsString::from("zstd"), + OsString::from("--single-thread=1"), + ]) + .expect_err("an attached value must not activate --single-thread"); + + assert!(error.contains("does not take an argument")); } #[test]