From 9a5491f6706a80b63fb9de5ebbae1136939d2b43 Mon Sep 17 00:00:00 2001 From: mattsu Date: Wed, 22 Oct 2025 14:44:42 +0900 Subject: [PATCH 1/4] feat(sort): add adaptive buffer sizing and fast paths - move heuristics into a new buffer_hint module and default to automatic sizing when the buffer flag is absent - tune chunk and external sort buffers to avoid runaway allocations - add fast lexicographic and ASCII case-insensitive comparisons for the default mode - refresh spell-check and dependency metadata for the new code --- .../cspell.dictionaries/jargon.wordlist.txt | 6 + fuzz/Cargo.lock | 1 + src/uu/sort/Cargo.toml | 2 - src/uu/sort/src/buffer_hint.rs | 152 ++++++++++++++++++ src/uu/sort/src/chunks.rs | 13 +- src/uu/sort/src/ext_sort.rs | 12 +- src/uu/sort/src/sort.rs | 144 +++++++++++++++-- 7 files changed, 305 insertions(+), 25 deletions(-) create mode 100644 src/uu/sort/src/buffer_hint.rs diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index ef8a2026fc1..fd18b774595 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -6,6 +6,7 @@ autogenerated autogenerates bitmask bitwise +bufferram bytewise canonicalization canonicalize @@ -45,6 +46,7 @@ fileio filesystem filesystems flamegraph +freeram fsxattr fullblock getfacl @@ -123,6 +125,7 @@ shortcode shortcodes siginfo sigusr +strcasecmp subcommand subexpression submodule @@ -131,8 +134,11 @@ symlink symlinks syscall syscalls +sysconf +sysinfo tokenize toolchain +totalram truthy ucase unbuffered diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index d7477fde6d1..13e5cd1bbd3 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1644,6 +1644,7 @@ dependencies = [ "fluent", "fnv", "itertools", + "libc", "memchr", "nix", "rand", diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index a44cc7570ae..c1b4c07084c 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -36,8 +36,6 @@ thiserror = { workspace = true } unicode-width = { workspace = true } uucore = { workspace = true, features = ["fs", "parser", "version-cmp"] } fluent = { workspace = true } - -[target.'cfg(target_os = "linux")'.dependencies] nix = { workspace = true } [dev-dependencies] diff --git a/src/uu/sort/src/buffer_hint.rs b/src/uu/sort/src/buffer_hint.rs new file mode 100644 index 00000000000..bb0ea754094 --- /dev/null +++ b/src/uu/sort/src/buffer_hint.rs @@ -0,0 +1,152 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +//! Heuristics for determining buffer size for external sorting. +use std::ffi::OsString; + +use crate::{ + FALLBACK_AUTOMATIC_BUF_SIZE, MAX_AUTOMATIC_BUF_SIZE, MIN_AUTOMATIC_BUF_SIZE, STDIN_FILE, +}; + +// Heuristics to size the external sort buffer without overcommit memory. +pub(crate) fn automatic_buffer_size(files: &[OsString]) -> usize { + let file_hint = file_size_hint(files); + let mem_hint = available_memory_hint(); + + // Prefer the tighter bound when both hints exist, otherwise fall back to whichever hint is available. + match (file_hint, mem_hint) { + (Some(file), Some(mem)) => file.min(mem), + (Some(file), None) => file, + (None, Some(mem)) => mem, + (None, None) => FALLBACK_AUTOMATIC_BUF_SIZE, + } +} + +fn file_size_hint(files: &[OsString]) -> Option { + // Estimate total bytes across real files; non-regular inputs are skipped. + let mut total_bytes: u128 = 0; + + for file in files { + if file == STDIN_FILE { + continue; + } + + let Ok(metadata) = std::fs::metadata(file) else { + continue; + }; + + if !metadata.is_file() { + continue; + } + + total_bytes = total_bytes.saturating_add(metadata.len() as u128); + + if total_bytes >= (MAX_AUTOMATIC_BUF_SIZE as u128) * 8 { + break; + } + } + + if total_bytes == 0 { + return None; + } + + let desired_bytes = desired_file_buffer_bytes(total_bytes); + Some(clamp_hint(desired_bytes)) +} + +fn available_memory_hint() -> Option { + #[cfg(target_os = "linux")] + if let Some(bytes) = uucore::parser::parse_size::available_memory_bytes() { + return Some(clamp_hint(bytes / 4)); + } + + physical_memory_bytes().map(|bytes| clamp_hint(bytes / 4)) +} + +fn clamp_hint(bytes: u128) -> usize { + let min = MIN_AUTOMATIC_BUF_SIZE as u128; + let max = MAX_AUTOMATIC_BUF_SIZE as u128; + let clamped = bytes.clamp(min, max); + clamped.min(usize::MAX as u128) as usize +} + +fn desired_file_buffer_bytes(total_bytes: u128) -> u128 { + if total_bytes == 0 { + return 0; + } + + let max = MAX_AUTOMATIC_BUF_SIZE as u128; + + if total_bytes <= max { + return total_bytes.saturating_mul(12).clamp(total_bytes, max); + } + + let quarter = total_bytes / 4; + quarter.max(max) +} + +fn physical_memory_bytes() -> Option { + #[cfg(all( + target_family = "unix", + not(target_os = "redox"), + any(target_os = "linux", target_os = "android") + ))] + { + physical_memory_bytes_unix() + } + + #[cfg(any( + not(target_family = "unix"), + target_os = "redox", + not(any(target_os = "linux", target_os = "android")) + ))] + { + // No portable or safe API is available here to detect total physical memory. + None + } +} + +#[cfg(all( + target_family = "unix", + not(target_os = "redox"), + any(target_os = "linux", target_os = "android") +))] +fn physical_memory_bytes_unix() -> Option { + use nix::unistd::{SysconfVar, sysconf}; + + let pages = match sysconf(SysconfVar::_PHYS_PAGES) { + Ok(Some(pages)) if pages > 0 => u128::try_from(pages).ok()?, + _ => return None, + }; + + let page_size = match sysconf(SysconfVar::PAGE_SIZE) { + Ok(Some(page_size)) if page_size > 0 => u128::try_from(page_size).ok()?, + _ => return None, + }; + + Some(pages.saturating_mul(page_size)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn desired_buffer_matches_total_when_small() { + let six_mebibytes = 6 * 1024 * 1024; + let expected = ((six_mebibytes as u128) * 12) + .clamp(six_mebibytes as u128, crate::MAX_AUTOMATIC_BUF_SIZE as u128); + assert_eq!(desired_file_buffer_bytes(six_mebibytes as u128), expected); + } + + #[test] + fn desired_buffer_caps_at_max_for_large_inputs() { + let large = 256 * 1024 * 1024; // 256 MiB + assert_eq!( + desired_file_buffer_bytes(large as u128), + crate::MAX_AUTOMATIC_BUF_SIZE as u128 + ); + } +} diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index af10a008844..837cb1fa95c 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -271,11 +271,12 @@ fn read_to_buffer( if max_buffer_size > buffer.len() { // we can grow the buffer let prev_len = buffer.len(); - if buffer.len() < max_buffer_size / 2 { - buffer.resize(buffer.len() * 2, 0); + let target = if buffer.len() < max_buffer_size / 2 { + buffer.len().saturating_mul(2) } else { - buffer.resize(max_buffer_size, 0); - } + max_buffer_size + }; + buffer.resize(target.min(max_buffer_size), 0); read_target = &mut buffer[prev_len..]; continue; } @@ -295,8 +296,8 @@ fn read_to_buffer( // We need to read more lines let len = buffer.len(); - // resize the vector to 10 KB more - buffer.resize(len + 1024 * 10, 0); + let grow_by = (len / 2).max(1024 * 1024); + buffer.resize(len + grow_by, 0); read_target = &mut buffer[len..]; } else { // This file has been fully read. diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index e43ad4b3a38..70d29acf54c 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -86,9 +86,15 @@ fn reader_writer< ) -> UResult<()> { let separator = settings.line_ending.into(); - // Heuristically chosen: Dividing by 10 seems to keep our memory usage roughly - // around settings.buffer_size as a whole. - let buffer_size = settings.buffer_size / 10; + // Cap oversized buffer requests at 512MiB to avoid unnecessary allocations. + // It's a safeguard against excessively large user-specified buffers: halving requests beyond 512 MiB keeps chunk sizes reasonable, preventing runaway memory usage and the overhead of allocating unnecessarily huge buffers. + let mut buffer_size = match settings.buffer_size { + size if size <= 512 * 1024 * 1024 => size, + size => size / 2, + }; + if !settings.buffer_size_is_explicit { + buffer_size = buffer_size.max(8 * 1024 * 1024); + } let read_result: ReadResult = read_write_loop( files, tmp_dir, diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 1d4948186fe..3ceadcbfc0c 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -9,6 +9,7 @@ // spell-checker:ignore (misc) HFKJFK Mbdfhn getrlimit RLIMIT_NOFILE rlim bigdecimal extendedbigdecimal hexdigit +mod buffer_hint; mod check; mod chunks; mod custom_str_cmp; @@ -54,6 +55,7 @@ use uucore::show_error; use uucore::translate; use uucore::version_cmp::version_cmp; +use crate::buffer_hint::automatic_buffer_size; use crate::tmp_dir::TmpDirWrapper; mod options { @@ -115,10 +117,12 @@ const DECIMAL_PT: u8 = b'.'; const NEGATIVE: &u8 = &b'-'; const POSITIVE: &u8 = &b'+'; -// Choosing a higher buffer size does not result in performance improvements -// (at least not on my machine). TODO: In the future, we should also take the amount of -// available memory into consideration, instead of relying on this constant only. -const DEFAULT_BUF_SIZE: usize = 1_000_000_000; // 1 GB +// The automatic buffer heuristics clamp to this range to avoid +// over-committing memory on constrained systems while still keeping +// reasonably large chunks for typical workloads. +const MIN_AUTOMATIC_BUF_SIZE: usize = 512 * 1024; // 512 KiB +const FALLBACK_AUTOMATIC_BUF_SIZE: usize = 32 * 1024 * 1024; // 32 MiB +const MAX_AUTOMATIC_BUF_SIZE: usize = 1024 * 1024 * 1024; // 1 GiB #[derive(Debug, Error)] pub enum SortError { @@ -283,6 +287,7 @@ pub struct GlobalSettings { threads: String, line_ending: LineEnding, buffer_size: usize, + buffer_size_is_explicit: bool, compress_prog: Option, merge_batch_size: usize, precomputed: Precomputed, @@ -296,6 +301,8 @@ struct Precomputed { num_infos_per_line: usize, floats_per_line: usize, selections_per_line: usize, + fast_lexicographic: bool, + fast_ascii_insensitive: bool, } impl GlobalSettings { @@ -336,6 +343,47 @@ impl GlobalSettings { .iter() .filter(|s| matches!(s.settings.mode, SortMode::GeneralNumeric)) .count(); + + self.precomputed.fast_lexicographic = self.can_use_fast_lexicographic(); + self.precomputed.fast_ascii_insensitive = self.can_use_fast_ascii_insensitive(); + } + + /// Returns true when the fast lexicographic path can be used safely. + fn can_use_fast_lexicographic(&self) -> bool { + self.mode == SortMode::Default + && !self.ignore_case + && !self.dictionary_order + && !self.ignore_non_printing + && !self.ignore_leading_blanks + && self.selectors.len() == 1 + && { + let selector = &self.selectors[0]; + !selector.needs_selection + && matches!(selector.settings.mode, SortMode::Default) + && !selector.settings.ignore_case + && !selector.settings.dictionary_order + && !selector.settings.ignore_non_printing + && !selector.settings.ignore_blanks + } + } + + /// Returns true when the ASCII case-insensitive fast path is valid. + fn can_use_fast_ascii_insensitive(&self) -> bool { + self.mode == SortMode::Default + && self.ignore_case + && !self.dictionary_order + && !self.ignore_non_printing + && !self.ignore_leading_blanks + && self.selectors.len() == 1 + && { + let selector = &self.selectors[0]; + !selector.needs_selection + && matches!(selector.settings.mode, SortMode::Default) + && selector.settings.ignore_case + && !selector.settings.dictionary_order + && !selector.settings.ignore_non_printing + && !selector.settings.ignore_blanks + } } } @@ -359,9 +407,10 @@ impl Default for GlobalSettings { separator: None, threads: String::new(), line_ending: LineEnding::Newline, - buffer_size: DEFAULT_BUF_SIZE, + buffer_size: FALLBACK_AUTOMATIC_BUF_SIZE, + buffer_size_is_explicit: false, compress_prog: None, - merge_batch_size: 32, + merge_batch_size: default_merge_batch_size(), precomputed: Precomputed::default(), } } @@ -1037,6 +1086,32 @@ fn get_rlimit() -> UResult { const STDIN_FILE: &str = "-"; +#[cfg(target_os = "linux")] +const LINUX_BATCH_DIVISOR: usize = 4; +#[cfg(target_os = "linux")] +const LINUX_BATCH_MIN: usize = 32; +#[cfg(target_os = "linux")] +const LINUX_BATCH_MAX: usize = 256; + +fn default_merge_batch_size() -> usize { + #[cfg(target_os = "linux")] + { + // Adjust merge batch size dynamically based on available file descriptors. + match get_rlimit() { + Ok(limit) => { + let usable_limit = limit.saturating_div(LINUX_BATCH_DIVISOR); + usable_limit.clamp(LINUX_BATCH_MIN, LINUX_BATCH_MAX) + } + Err(_) => 64, + } + } + + #[cfg(not(target_os = "linux"))] + { + 64 + } +} + #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -1157,14 +1232,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } - settings.buffer_size = - matches - .get_one::(options::BUF_SIZE) - .map_or(Ok(DEFAULT_BUF_SIZE), |s| { - GlobalSettings::parse_byte_count(s).map_err(|e| { - USimpleError::new(2, format_error_message(&e, s, options::BUF_SIZE)) - }) - })?; + if let Some(size_str) = matches.get_one::(options::BUF_SIZE) { + settings.buffer_size = GlobalSettings::parse_byte_count(size_str).map_err(|e| { + USimpleError::new(2, format_error_message(&e, size_str, options::BUF_SIZE)) + })?; + settings.buffer_size_is_explicit = true; + } else { + settings.buffer_size = automatic_buffer_size(&files); + settings.buffer_size_is_explicit = false; + } let mut tmp_dir = TmpDirWrapper::new( matches @@ -1611,6 +1687,26 @@ fn compare_by<'a>( a_line_data: &LineData<'a>, b_line_data: &LineData<'a>, ) -> Ordering { + if global_settings.precomputed.fast_lexicographic { + let cmp = a.line.cmp(b.line); + return if global_settings.reverse { + cmp.reverse() + } else { + cmp + }; + } + + if global_settings.precomputed.fast_ascii_insensitive { + let cmp = ascii_case_insensitive_cmp(a.line, b.line); + if cmp != Ordering::Equal || a.line == b.line { + return if global_settings.reverse { + cmp.reverse() + } else { + cmp + }; + } + } + let mut selection_index = 0; let mut num_info_index = 0; let mut parsed_float_index = 0; @@ -1722,6 +1818,26 @@ fn compare_by<'a>( } } +/// Compare two byte slices in ASCII case-insensitive order without allocating. +/// We lower each byte on the fly so that binary input (including `NUL`) stays +/// untouched and we avoid locale-sensitive routines such as `strcasecmp`. +fn ascii_case_insensitive_cmp(a: &[u8], b: &[u8]) -> Ordering { + #[inline] + fn lower(byte: u8) -> u8 { + byte.to_ascii_lowercase() + } + + for (lhs, rhs) in a.iter().copied().zip(b.iter().copied()) { + let l = lower(lhs); + let r = lower(rhs); + if l != r { + return l.cmp(&r); + } + } + + a.len().cmp(&b.len()) +} + // This function cleans up the initial comparison done by leading_num_common for a general numeric compare. // In contrast to numeric compare, GNU general numeric/FP sort *should* recognize positive signs and // scientific notation, so we strip those lines only after the end of the following numeric string. From 9914a222033f321774b9648a1ee82ad16550e9cb Mon Sep 17 00:00:00 2001 From: mattsu Date: Wed, 22 Oct 2025 14:44:56 +0900 Subject: [PATCH 2/4] fix(sort): reuse SIGINT handler for temporary directory cleanup - keep the latest path/lock pair in a shared registry so SIGINT always cleans the active directory - guard handler installation with an atomic flag and reset state when the wrapper is dropped --- src/uu/sort/src/tmp_dir.rs | 116 ++++++++++++++++++++++++++++--------- 1 file changed, 90 insertions(+), 26 deletions(-) diff --git a/src/uu/sort/src/tmp_dir.rs b/src/uu/sort/src/tmp_dir.rs index 474e01ae2f3..815ba510970 100644 --- a/src/uu/sort/src/tmp_dir.rs +++ b/src/uu/sort/src/tmp_dir.rs @@ -2,10 +2,11 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use std::sync::atomic::{AtomicBool, Ordering}; use std::{ fs::File, path::{Path, PathBuf}, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, OnceLock}, }; use tempfile::TempDir; @@ -29,6 +30,70 @@ pub struct TmpDirWrapper { lock: Arc>, } +#[derive(Default, Clone)] +struct HandlerRegistration { + lock: Option>>, + path: Option, +} + +fn handler_state() -> Arc> { + // Lazily create the global HandlerRegistration so all TmpDirWrapper instances and the + // SIGINT handler operate on the same lock/path snapshot. + static HANDLER_STATE: OnceLock>> = OnceLock::new(); + HANDLER_STATE + .get_or_init(|| Arc::new(Mutex::new(HandlerRegistration::default()))) + .clone() +} + +fn ensure_signal_handler_installed(state: Arc>) -> UResult<()> { + // This shared state must originate from `handler_state()` so the handler always sees + // the current lock/path pair and can clean up the active temp directory on SIGINT. + // Install a shared SIGINT handler so the active temp directory is deleted when the user aborts. + // Guard to ensure the SIGINT handler is registered once per process and reused. + static HANDLER_INSTALLED: AtomicBool = AtomicBool::new(false); + + if HANDLER_INSTALLED + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire) + .is_err() + { + return Ok(()); + } + + let handler_state = state.clone(); + if let Err(e) = ctrlc::set_handler(move || { + // Load the latest lock/path snapshot so the handler cleans the active temp dir. + let (lock, path) = { + let state = handler_state.lock().unwrap(); + (state.lock.clone(), state.path.clone()) + }; + + if let Some(lock) = lock { + let _guard = lock.lock().unwrap(); + if let Some(path) = path { + if let Err(e) = remove_tmp_dir(&path) { + show_error!( + "{}", + translate!( + "sort-failed-to-delete-temporary-directory", + "error" => e + ) + ); + } + } + } + + std::process::exit(2) + }) { + HANDLER_INSTALLED.store(false, Ordering::Release); + return Err(USimpleError::new( + 2, + translate!("sort-failed-to-set-up-signal-handler", "error" => e), + )); + } + + Ok(()) +} + impl TmpDirWrapper { pub fn new(path: PathBuf) -> Self { Self { @@ -52,31 +117,14 @@ impl TmpDirWrapper { ); let path = self.temp_dir.as_ref().unwrap().path().to_owned(); - let lock = self.lock.clone(); - ctrlc::set_handler(move || { - // Take the lock so that `next_file_path` returns no new file path, - // and the program doesn't terminate before the handler has finished - let _lock = lock.lock().unwrap(); - if let Err(e) = remove_tmp_dir(&path) { - show_error!( - "{}", - translate!( - "sort-failed-to-delete-temporary-directory", - "error" => e - ) - ); - } - std::process::exit(2) - }) - .map_err(|e| { - USimpleError::new( - 2, - translate!( - "sort-failed-to-set-up-signal-handler", - "error" => e - ), - ) - }) + let state = handler_state(); + { + let mut guard = state.lock().unwrap(); + guard.lock = Some(self.lock.clone()); + guard.path = Some(path); + } + + ensure_signal_handler_installed(state) } pub fn next_file(&mut self) -> UResult<(File, PathBuf)> { @@ -100,6 +148,22 @@ impl TmpDirWrapper { } } +impl Drop for TmpDirWrapper { + fn drop(&mut self) { + let state = handler_state(); + let mut guard = state.lock().unwrap(); + + if guard + .lock + .as_ref() + .is_some_and(|current| Arc::ptr_eq(current, &self.lock)) + { + guard.lock = None; + guard.path = None; + } + } +} + /// Remove the directory at `path` by deleting its child files and then itself. /// Errors while deleting child files are ignored. fn remove_tmp_dir(path: &Path) -> std::io::Result<()> { From 9a0243be7326aeb6520440b004cb9977f15c523e Mon Sep 17 00:00:00 2001 From: mattsu Date: Thu, 23 Oct 2025 19:18:10 +0900 Subject: [PATCH 3/4] refactor(sort): simplify merge batch size to fixed value Remove Linux-specific dynamic adjustment based on file descriptors and use a fixed batch size of 64 for consistency across platforms. --- src/uu/sort/src/sort.rs | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 902c706c003..22c96a436ed 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1111,32 +1111,6 @@ fn default_merge_batch_size() -> usize { } } -#[cfg(target_os = "linux")] -const LINUX_BATCH_DIVISOR: usize = 4; -#[cfg(target_os = "linux")] -const LINUX_BATCH_MIN: usize = 32; -#[cfg(target_os = "linux")] -const LINUX_BATCH_MAX: usize = 256; - -fn default_merge_batch_size() -> usize { - #[cfg(target_os = "linux")] - { - // Adjust merge batch size dynamically based on available file descriptors. - match get_rlimit() { - Ok(limit) => { - let usable_limit = limit.saturating_div(LINUX_BATCH_DIVISOR); - usable_limit.clamp(LINUX_BATCH_MIN, LINUX_BATCH_MAX) - } - Err(_) => 64, - } - } - - #[cfg(not(target_os = "linux"))] - { - 64 - } -} - #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { From 800f71d2b282f624259a3f7c077d5f2b618c51ae Mon Sep 17 00:00:00 2001 From: mattsu Date: Thu, 23 Oct 2025 10:26:19 +0000 Subject: [PATCH 4/4] fix Cargo.lock linux environments --- fuzz/Cargo.lock | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 08b57b22609..b7c7e85cf2e 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -806,9 +806,9 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -1018,9 +1018,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "onig" @@ -1142,9 +1142,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7" dependencies = [ "unicode-ident", ] @@ -1303,9 +1303,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "self_cell" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" +checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" [[package]] name = "serde" @@ -1421,9 +1421,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.107" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -1669,7 +1669,6 @@ dependencies = [ "fluent", "fnv", "itertools", - "libc", "memchr", "nix", "rand",