Skip to content

Commit 463208e

Browse files
committed
uptime: locale init per thread
Store a thread-local flag so ensure_uptime_locale() only runs setup_localization("uptime") once per thread, and call it from every public API entry point. This lets library consumers get translated strings even when the current thread did not explicitly initialize the locale.
1 parent 21ee64c commit 463208e

File tree

1 file changed

+54
-0
lines changed

1 file changed

+54
-0
lines changed

src/uucore/src/lib/features/uptime.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
// See https://github.com/uutils/coreutils/pull/7289 for discussion.
1414

1515
use crate::error::{UError, UResult};
16+
use crate::locale::{self, LocalizationError};
1617
use crate::translate;
1718
use chrono::Local;
1819
use libc::time_t;
20+
use std::cell::Cell;
1921
use thiserror::Error;
2022

2123
#[derive(Debug, Error)]
@@ -36,6 +38,29 @@ impl UError for UptimeError {
3638
}
3739
}
3840

41+
thread_local! {
42+
static LOCALE_READY: Cell<bool> = Cell::new(false);
43+
}
44+
45+
fn ensure_uptime_locale() {
46+
LOCALE_READY.with(|ready| {
47+
if ready.get() {
48+
return;
49+
}
50+
51+
match locale::setup_localization("uptime") {
52+
Ok(()) => ready.set(true),
53+
Err(LocalizationError::Bundle(msg)) if msg.contains("already initialized") => {
54+
ready.set(true);
55+
}
56+
Err(err) => {
57+
#[cfg(debug_assertions)]
58+
eprintln!("uucore::uptime localization setup failed: {err}");
59+
}
60+
}
61+
});
62+
}
63+
3964
/// Returns the formatted time string, e.g. "12:34:56"
4065
pub fn get_formatted_time() -> String {
4166
Local::now().time().format("%H:%M:%S").to_string()
@@ -52,6 +77,7 @@ pub fn get_formatted_time() -> String {
5277
/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError.
5378
#[cfg(target_os = "openbsd")]
5479
pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
80+
ensure_uptime_locale();
5581
use libc::CLOCK_BOOTTIME;
5682
use libc::clock_gettime;
5783

@@ -91,6 +117,7 @@ pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
91117
#[cfg(unix)]
92118
#[cfg(not(target_os = "openbsd"))]
93119
pub fn get_uptime(boot_time: Option<time_t>) -> UResult<i64> {
120+
ensure_uptime_locale();
94121
use crate::utmpx::Utmpx;
95122
use libc::BOOT_TIME;
96123
use std::fs::File;
@@ -150,6 +177,7 @@ pub fn get_uptime(boot_time: Option<time_t>) -> UResult<i64> {
150177
/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError.
151178
#[cfg(windows)]
152179
pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
180+
ensure_uptime_locale();
153181
use windows_sys::Win32::System::SystemInformation::GetTickCount;
154182
// SAFETY: always return u32
155183
let uptime = unsafe { GetTickCount() };
@@ -167,6 +195,7 @@ pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
167195
/// Returns a UResult with the uptime in a human-readable format(e.g. "1 day, 3:45") if successful, otherwise an UptimeError.
168196
#[inline]
169197
pub fn get_formatted_uptime(boot_time: Option<time_t>) -> UResult<String> {
198+
ensure_uptime_locale();
170199
let up_secs = get_uptime(boot_time)?;
171200

172201
if up_secs < 0 {
@@ -307,6 +336,7 @@ pub fn get_nusers() -> usize {
307336
/// e.g. "0 users", "1 user", "2 users"
308337
#[inline]
309338
pub fn format_nusers(n: usize) -> String {
339+
ensure_uptime_locale();
310340
translate!(
311341
"uptime-user-count",
312342
"count" => n
@@ -320,6 +350,7 @@ pub fn format_nusers(n: usize) -> String {
320350
/// e.g. "0 user", "1 user", "2 users"
321351
#[inline]
322352
pub fn get_formatted_nusers() -> String {
353+
ensure_uptime_locale();
323354
#[cfg(not(target_os = "openbsd"))]
324355
return format_nusers(get_nusers());
325356

@@ -335,6 +366,7 @@ pub fn get_formatted_nusers() -> String {
335366
/// The load average is a tuple of three floating point numbers representing the 1-minute, 5-minute, and 15-minute load averages.
336367
#[cfg(unix)]
337368
pub fn get_loadavg() -> UResult<(f64, f64, f64)> {
369+
ensure_uptime_locale();
338370
use crate::libc::c_double;
339371
use libc::getloadavg;
340372

@@ -357,6 +389,7 @@ pub fn get_loadavg() -> UResult<(f64, f64, f64)> {
357389
/// Returns a UResult with an UptimeError.
358390
#[cfg(windows)]
359391
pub fn get_loadavg() -> UResult<(f64, f64, f64)> {
392+
ensure_uptime_locale();
360393
Err(UptimeError::WindowsLoadavg)?
361394
}
362395

@@ -368,6 +401,7 @@ pub fn get_loadavg() -> UResult<(f64, f64, f64)> {
368401
/// e.g. "load average: 0.00, 0.00, 0.00"
369402
#[inline]
370403
pub fn get_formatted_loadavg() -> UResult<String> {
404+
ensure_uptime_locale();
371405
let loadavg = get_loadavg()?;
372406
Ok(translate!(
373407
"uptime-lib-format-loadavg",
@@ -392,4 +426,24 @@ mod tests {
392426
assert_eq!("1 user", format_nusers(1));
393427
assert_eq!("2 users", format_nusers(2));
394428
}
429+
430+
#[test]
431+
fn test_format_nusers_threaded() {
432+
unsafe {
433+
std::env::set_var("LANG", "en_US.UTF-8");
434+
}
435+
let _ = locale::setup_localization("top");
436+
let _ = locale::setup_localization("uptime");
437+
438+
assert_eq!("uptime-user-count", format_nusers(0));
439+
440+
std::thread::spawn(move || {
441+
let _ = locale::setup_localization("uptime");
442+
assert_eq!("0 users", format_nusers(0));
443+
assert_eq!("1 user", format_nusers(1));
444+
assert_eq!("2 users", format_nusers(2));
445+
})
446+
.join()
447+
.expect("thread should succeed");
448+
}
395449
}

0 commit comments

Comments
 (0)