Skip to content

Commit 5dcde30

Browse files
authored
uucore: share a FluentResource between threads - improving perf up to 6x (#11220)
1 parent 85467d8 commit 5dcde30

1 file changed

Lines changed: 48 additions & 25 deletions

File tree

src/uucore/src/lib/mods/locale.rs

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,19 @@ include!(concat!(env!("OUT_DIR"), "/embedded_locales.rs"));
6565

6666
// A struct to handle localization with optional English fallback
6767
struct Localizer {
68-
primary_bundle: FluentBundle<FluentResource>,
69-
fallback_bundle: Option<FluentBundle<FluentResource>>,
68+
primary_bundle: FluentBundle<&'static FluentResource>,
69+
fallback_bundle: Option<FluentBundle<&'static FluentResource>>,
7070
}
7171

7272
impl Localizer {
73-
fn new(primary_bundle: FluentBundle<FluentResource>) -> Self {
73+
fn new(primary_bundle: FluentBundle<&'static FluentResource>) -> Self {
7474
Self {
7575
primary_bundle,
7676
fallback_bundle: None,
7777
}
7878
}
7979

80-
fn with_fallback(mut self, fallback_bundle: FluentBundle<FluentResource>) -> Self {
80+
fn with_fallback(mut self, fallback_bundle: FluentBundle<&'static FluentResource>) -> Self {
8181
self.fallback_bundle = Some(fallback_bundle);
8282
self
8383
}
@@ -107,7 +107,10 @@ impl Localizer {
107107
}
108108
}
109109

110-
// Global localizer stored in thread-local OnceLock
110+
// Cache localizer. FluentResource cannot be shared between threads while FluentBundle can be shared
111+
static UUCORE_FLUENT: OnceLock<FluentResource> = OnceLock::new();
112+
static CHECKSUM_FLUENT: OnceLock<FluentResource> = OnceLock::new();
113+
static UTIL_FLUENT: OnceLock<FluentResource> = OnceLock::new();
111114
thread_local! {
112115
static LOCALIZER: OnceLock<Localizer> = const { OnceLock::new() };
113116
}
@@ -136,8 +139,8 @@ fn create_bundle(
136139
locale: &LanguageIdentifier,
137140
locales_dir: &Path,
138141
util_name: &str,
139-
) -> Result<FluentBundle<FluentResource>, LocalizationError> {
140-
let mut bundle = FluentBundle::new(vec![locale.clone()]);
142+
) -> Result<FluentBundle<&'static FluentResource>, LocalizationError> {
143+
let mut bundle: FluentBundle<&'static FluentResource> = FluentBundle::new(vec![locale.clone()]);
141144

142145
// Disable Unicode directional isolate characters
143146
bundle.set_use_isolating(false);
@@ -148,7 +151,8 @@ fn create_bundle(
148151
.and_then(|locale_path| fs::read_to_string(locale_path).ok())
149152
.and_then(|ftl| FluentResource::try_new(ftl).ok())
150153
{
151-
bundle.add_resource_overriding(resource);
154+
// use Box::leak to provide 'static lifetime for shared FluentBundle between threads
155+
bundle.add_resource_overriding(Box::leak(Box::new(resource)));
152156
}
153157
};
154158

@@ -193,10 +197,11 @@ fn init_localization(
193197
.expect("Default locale should always be valid");
194198

195199
// Try to create a bundle that combines common and utility-specific strings
196-
let english_bundle = create_bundle(&default_locale, locales_dir, util_name).or_else(|_| {
197-
// Fallback to embedded utility-specific and common strings
198-
create_english_bundle_from_embedded(&default_locale, util_name)
199-
})?;
200+
let english_bundle: FluentBundle<&'static FluentResource> =
201+
create_bundle(&default_locale, locales_dir, util_name).or_else(|_| {
202+
// Fallback to embedded utility-specific and common strings
203+
create_english_bundle_from_embedded(&default_locale, util_name)
204+
})?;
200205

201206
let loc = if locale == &default_locale {
202207
// If requesting English, just use English as primary (no fallback needed)
@@ -220,8 +225,18 @@ fn init_localization(
220225
}
221226

222227
/// Helper function to parse FluentResource from content string
223-
fn parse_fluent_resource(content: &str) -> Result<FluentResource, LocalizationError> {
224-
FluentResource::try_new(content.to_string()).map_err(
228+
fn parse_fluent_resource(
229+
content: &str,
230+
cache: &'static OnceLock<FluentResource>,
231+
) -> Result<&'static FluentResource, LocalizationError> {
232+
// global cache breaks unit tests
233+
if cfg!(not(test)) {
234+
if let Some(res) = cache.get() {
235+
return Ok(res);
236+
}
237+
}
238+
239+
let resource = FluentResource::try_new(content.to_string()).map_err(
225240
|(_partial_resource, errs): (FluentResource, Vec<ParserError>)| {
226241
if let Some(first_err) = errs.into_iter().next() {
227242
let snippet = first_err
@@ -238,42 +253,48 @@ fn parse_fluent_resource(content: &str) -> Result<FluentResource, LocalizationEr
238253
LocalizationError::LocalesDirNotFound("Parse error without details".to_string())
239254
}
240255
},
241-
)
256+
)?;
257+
// global cache breaks unit tests
258+
if cfg!(not(test)) {
259+
Ok(cache.get_or_init(|| resource))
260+
} else {
261+
Ok(Box::leak(Box::new(resource)))
262+
}
242263
}
243264

244265
/// Create a bundle from embedded English locale files with common uucore strings
245266
fn create_english_bundle_from_embedded(
246267
locale: &LanguageIdentifier,
247268
util_name: &str,
248-
) -> Result<FluentBundle<FluentResource>, LocalizationError> {
269+
) -> Result<FluentBundle<&'static FluentResource>, LocalizationError> {
249270
// Only support English from embedded files
250271
if *locale != "en-US" {
251272
return Err(LocalizationError::LocalesDirNotFound(
252273
"Embedded locales only support en-US".to_string(),
253274
));
254275
}
255276

256-
let mut bundle = FluentBundle::new(vec![locale.clone()]);
277+
let mut bundle: FluentBundle<&'static FluentResource> = FluentBundle::new(vec![locale.clone()]);
257278
bundle.set_use_isolating(false);
258279

259280
// First, try to load common uucore strings
260281
if let Some(uucore_content) = get_embedded_locale("uucore/en-US.ftl") {
261-
let uucore_resource = parse_fluent_resource(uucore_content)?;
282+
let uucore_resource = parse_fluent_resource(uucore_content, &UUCORE_FLUENT)?;
262283
bundle.add_resource_overriding(uucore_resource);
263284
}
264285

265286
// Checksum algorithms need locale messages from checksum_common
266287
if util_name.ends_with("sum") {
267288
if let Some(uucore_content) = get_embedded_locale("checksum_common/en-US.ftl") {
268-
let uucore_resource = parse_fluent_resource(uucore_content)?;
289+
let uucore_resource = parse_fluent_resource(uucore_content, &CHECKSUM_FLUENT)?;
269290
bundle.add_resource_overriding(uucore_resource);
270291
}
271292
}
272293

273294
// Then, try to load utility-specific strings
274295
let locale_key = format!("{util_name}/en-US.ftl");
275296
if let Some(ftl_content) = get_embedded_locale(&locale_key) {
276-
let resource = parse_fluent_resource(ftl_content)?;
297+
let resource = parse_fluent_resource(ftl_content, &UTIL_FLUENT)?;
277298
bundle.add_resource_overriding(resource);
278299
}
279300

@@ -428,7 +449,8 @@ pub fn setup_localization(p: &str) -> Result<(), LocalizationError> {
428449
// No locales directory found, use embedded English with common strings directly
429450
let default_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE)
430451
.expect("Default locale should always be valid");
431-
let english_bundle = create_english_bundle_from_embedded(&default_locale, p)?;
452+
let english_bundle: FluentBundle<&'static FluentResource> =
453+
create_english_bundle_from_embedded(&default_locale, p)?;
432454
let localizer = Localizer::new(english_bundle);
433455

434456
LOCALIZER.with(|lock| {
@@ -593,14 +615,15 @@ mod tests {
593615
fn create_test_bundle(
594616
locale: &LanguageIdentifier,
595617
test_locales_dir: &Path,
596-
) -> Result<FluentBundle<FluentResource>, LocalizationError> {
597-
let mut bundle = FluentBundle::new(vec![locale.clone()]);
618+
) -> Result<FluentBundle<&'static FluentResource>, LocalizationError> {
619+
let mut bundle: FluentBundle<&'static FluentResource> =
620+
FluentBundle::new(vec![locale.clone()]);
598621
bundle.set_use_isolating(false);
599622

600623
// Only load from the test directory - no common strings or utility-specific paths
601624
let locale_path = test_locales_dir.join(format!("{locale}.ftl"));
602625
if let Ok(ftl_content) = fs::read_to_string(&locale_path) {
603-
let resource = parse_fluent_resource(&ftl_content)?;
626+
let resource = parse_fluent_resource(&ftl_content, &UUCORE_FLUENT)?;
604627
bundle.add_resource_overriding(resource);
605628
return Ok(bundle);
606629
}
@@ -763,7 +786,7 @@ invalid-syntax = This is { $missing
763786
#[test]
764787
fn test_localizer_format_primary_bundle() {
765788
let temp_dir = create_test_locales_dir();
766-
let en_bundle = create_test_bundle(
789+
let en_bundle: FluentBundle<&'static FluentResource> = create_test_bundle(
767790
&LanguageIdentifier::from_str("en-US").unwrap(),
768791
temp_dir.path(),
769792
)

0 commit comments

Comments
 (0)