-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
uptime: locale init per thread #9028
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2fa7a32
43cd6e5
f549910
0b2affa
d561365
dca827e
43e5cb2
17f85cb
15ed57b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,10 +13,12 @@ | |
| // See https://github.com/uutils/coreutils/pull/7289 for discussion. | ||
|
|
||
| use crate::error::{UError, UResult}; | ||
| use crate::locale::{self, LocalizationError}; | ||
| use crate::translate; | ||
| use jiff::Timestamp; | ||
| use jiff::tz::TimeZone; | ||
| use libc::time_t; | ||
| use std::cell::Cell; | ||
| use thiserror::Error; | ||
|
|
||
| #[derive(Debug, Error)] | ||
|
|
@@ -37,6 +39,37 @@ impl UError for UptimeError { | |
| } | ||
| } | ||
|
|
||
| // setup_localization() stores its state in thread local storage, | ||
| // so every thread that might format uptime strings has to call it once. | ||
| // Track that per thread so our helpers can lazily initialize | ||
| // the locale only once per thread. | ||
| thread_local! { | ||
| static LOCALE_READY: Cell<bool> = const { Cell::new(false) }; | ||
| } | ||
|
|
||
| // Lazily initialize the uptime localization for the current thread, marking it | ||
| // done even if a sibling thread already ran setup_localization() so we avoid | ||
| // propagating the "already initialized" error and only log unexpected failures | ||
| // in debug builds. | ||
| fn ensure_uptime_locale() { | ||
| LOCALE_READY.with(|ready| { | ||
| if ready.get() { | ||
| return; | ||
| } | ||
|
|
||
| match locale::setup_localization("uptime") { | ||
| Ok(()) => ready.set(true), | ||
| Err(LocalizationError::Bundle(msg)) if msg.contains("already initialized") => { | ||
| ready.set(true); | ||
| } | ||
| Err(err) => { | ||
| #[cfg(debug_assertions)] | ||
| eprintln!("uucore::uptime localization setup failed: {err}"); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| /// Returns the formatted time string, e.g. "12:34:56" | ||
| pub fn get_formatted_time() -> String { | ||
| Timestamp::now() | ||
|
|
@@ -98,6 +131,7 @@ fn get_macos_boot_time_sysctl() -> Option<time_t> { | |
| /// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError. | ||
| #[cfg(target_os = "openbsd")] | ||
| pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> { | ||
| ensure_uptime_locale(); | ||
| use libc::CLOCK_BOOTTIME; | ||
| use libc::clock_gettime; | ||
|
|
||
|
|
@@ -136,6 +170,7 @@ pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> { | |
| #[cfg(unix)] | ||
| #[cfg(not(target_os = "openbsd"))] | ||
| pub fn get_uptime(boot_time: Option<time_t>) -> UResult<i64> { | ||
| ensure_uptime_locale(); | ||
| use crate::utmpx::Utmpx; | ||
| use libc::BOOT_TIME; | ||
| use std::fs::File; | ||
|
|
@@ -266,6 +301,7 @@ impl FormattedUptime { | |
| /// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError. | ||
| #[cfg(windows)] | ||
| pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> { | ||
| ensure_uptime_locale(); | ||
| use windows_sys::Win32::System::SystemInformation::GetTickCount; | ||
| // SAFETY: always return u32 | ||
| let uptime = unsafe { GetTickCount() }; | ||
|
|
@@ -287,6 +323,7 @@ pub fn get_formatted_uptime( | |
| boot_time: Option<time_t>, | ||
| output_format: OutputFormat, | ||
| ) -> UResult<String> { | ||
| ensure_uptime_locale(); | ||
| let up_secs = get_uptime(boot_time)?; | ||
|
|
||
| if up_secs < 0 { | ||
|
|
@@ -415,6 +452,7 @@ pub fn get_nusers() -> usize { | |
| /// e.g. "0 users", "1 user", "2 users" | ||
| #[inline] | ||
| pub fn format_nusers(n: usize) -> String { | ||
| ensure_uptime_locale(); | ||
| translate!( | ||
| "uptime-user-count", | ||
| "count" => n | ||
|
|
@@ -428,6 +466,7 @@ pub fn format_nusers(n: usize) -> String { | |
| /// e.g. "0 user", "1 user", "2 users" | ||
| #[inline] | ||
| pub fn get_formatted_nusers() -> String { | ||
| ensure_uptime_locale(); | ||
| #[cfg(not(target_os = "openbsd"))] | ||
| return format_nusers(get_nusers()); | ||
|
|
||
|
|
@@ -443,6 +482,7 @@ pub fn get_formatted_nusers() -> String { | |
| /// The load average is a tuple of three floating point numbers representing the 1-minute, 5-minute, and 15-minute load averages. | ||
| #[cfg(unix)] | ||
| pub fn get_loadavg() -> UResult<(f64, f64, f64)> { | ||
| ensure_uptime_locale(); | ||
| use crate::libc::c_double; | ||
| use libc::getloadavg; | ||
|
|
||
|
|
@@ -465,6 +505,7 @@ pub fn get_loadavg() -> UResult<(f64, f64, f64)> { | |
| /// Returns a UResult with an UptimeError. | ||
| #[cfg(windows)] | ||
| pub fn get_loadavg() -> UResult<(f64, f64, f64)> { | ||
| ensure_uptime_locale(); | ||
| Err(UptimeError::WindowsLoadavg)? | ||
| } | ||
|
|
||
|
|
@@ -476,6 +517,7 @@ pub fn get_loadavg() -> UResult<(f64, f64, f64)> { | |
| /// e.g. "load average: 0.00, 0.00, 0.00" | ||
| #[inline] | ||
| pub fn get_formatted_loadavg() -> UResult<String> { | ||
| ensure_uptime_locale(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i am not a fan to duplicate this call in every functions of uptime
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the suggestion. Unfortunately |
||
| let loadavg = get_loadavg()?; | ||
| let mut args = fluent::FluentArgs::new(); | ||
| args.set("avg1", format!("{:.2}", loadavg.0)); | ||
|
|
@@ -503,6 +545,29 @@ mod tests { | |
| assert_eq!("2 users", format_nusers(2)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_format_nusers_threaded() { | ||
| unsafe { | ||
| std::env::set_var("LANG", "en_US.UTF-8"); | ||
| } | ||
| let _ = locale::setup_localization("top"); | ||
| let _ = locale::setup_localization("uptime"); | ||
|
|
||
| assert_eq!("uptime-user-count", format_nusers(0)); | ||
|
|
||
| std::thread::spawn(move || { | ||
| unsafe { | ||
| std::env::set_var("LANG", "en_US.UTF-8"); | ||
| } | ||
| let _ = locale::setup_localization("uptime"); | ||
| assert_eq!("0 users", format_nusers(0)); | ||
| assert_eq!("1 user", format_nusers(1)); | ||
| assert_eq!("2 users", format_nusers(2)); | ||
| }) | ||
| .join() | ||
| .expect("thread should succeed"); | ||
| } | ||
|
|
||
| /// Test that sysctl kern.boottime is accessible on macOS and returns valid boot time. | ||
| /// This ensures the fallback mechanism added for issue #3621 works correctly. | ||
| #[test] | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.