Skip to content

Commit c42f840

Browse files
authored
Fixed usage of ConcurrentDictionary<TKey, TValue> to use Lazy<T> when it matters (Fixes #114) (#117)
* ICU4N.Impl.PluralRulesLoader::GetRulesForRulesId(): Changed to use Lazy<PluralRules?> for the ConcurrentDictionary value to ensure that only the first thread can build a PluralRules instance. * ICU4N.Util.VersionInfo::GetInstance(): Use Lazy<VersionInfo> for the cache value to ensure all threads share the same instance. They are compared using reference equality, so this is important. * ICU4N.Text.NormalizationTransliterator: Changed SOURCE_CACHE to use Lazy<SourceTargetUtility> to match the upstream code, which held a lock to ensure the cache is populated by only one thread.
1 parent cd4fbf8 commit c42f840

3 files changed

Lines changed: 14 additions & 11 deletions

File tree

src/ICU4N.Transliterator/Text/NormalizationTransliterator.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ protected override void HandleTransliterate(IReplaceable text,
174174
offsets.Limit = limit;
175175
}
176176

177-
internal static readonly ConcurrentDictionary<Normalizer2, SourceTargetUtility> SOURCE_CACHE = new ConcurrentDictionary<Normalizer2, SourceTargetUtility>();
177+
internal static readonly ConcurrentDictionary<Normalizer2, Lazy<SourceTargetUtility>> SOURCE_CACHE = new ConcurrentDictionary<Normalizer2, Lazy<SourceTargetUtility>>();
178178

179179
// TODO Get rid of this if Normalizer2 becomes a Transform
180180
internal class NormalizingTransform : ITransform<string, string>
@@ -196,7 +196,8 @@ public virtual string Transform(string source)
196196
public override void AddSourceTargetSet(UnicodeSet inputFilter, UnicodeSet sourceSet, UnicodeSet targetSet)
197197
#pragma warning restore 672
198198
{
199-
SourceTargetUtility cache = SOURCE_CACHE.GetOrAdd(norm2, (norm2) => new SourceTargetUtility(new NormalizingTransform(norm2), norm2));
199+
SourceTargetUtility cache = SOURCE_CACHE.GetOrAdd(norm2, (norm2) =>
200+
new Lazy<SourceTargetUtility>(() => new SourceTargetUtility(new NormalizingTransform(norm2), norm2))).Value;
200201
cache.AddSourceTargetSet(this, inputFilter, sourceSet, targetSet);
201202
}
202203
}

src/ICU4N/Impl/PluralRulesLoader.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ internal class PluralRulesLoader // ICU4N: Marked internal until implementation
2020
: PluralRulesFactory
2121
#pragma warning restore 612, 618
2222
{
23-
private readonly ConcurrentDictionary<string, PluralRules?> rulesIdToRules = new ConcurrentDictionary<string, PluralRules?>();
23+
private readonly ConcurrentDictionary<string, Lazy<PluralRules?>> rulesIdToRules = new ConcurrentDictionary<string, Lazy<PluralRules?>>();
2424
// lazy init, use getLocaleIdToRulesIdMap to access
2525
private IDictionary<string, string>? localeIdToCardinalRulesId;
2626
private IDictionary<string, string>? localeIdToOrdinalRulesId;
@@ -229,8 +229,8 @@ private IDictionary<string, UCultureInfo> LoadRulesIdMaps()
229229
if (rulesId is null)
230230
throw new ArgumentNullException(nameof(rulesId));
231231

232-
return rulesIdToRules.GetOrAdd(rulesId, (key) => {
233-
232+
var rules = rulesIdToRules.GetOrAdd(rulesId, (key) => new Lazy<PluralRules?>(() =>
233+
{
234234
UResourceBundle setb;
235235
try
236236
{
@@ -245,7 +245,7 @@ private IDictionary<string, UCultureInfo> LoadRulesIdMaps()
245245

246246
var ruleList = new PluralRules.RuleList();
247247
bool parseFailure = false;
248-
for (int i = 0; i < setb.Length; ++i)
248+
for (int i = 0; i < setb.Length && !parseFailure; ++i)
249249
{
250250
UResourceBundle b = setb.Get(i);
251251
if (PluralRules.TryParseRule(b.Key, b.GetString(), out PluralRules.Rule rule))
@@ -261,7 +261,9 @@ private IDictionary<string, UCultureInfo> LoadRulesIdMaps()
261261
return null; // can be null
262262

263263
return new PluralRules(ruleList.Finish());
264-
});
264+
}));
265+
266+
return rules.Value;
265267
}
266268

267269
/// <summary>
@@ -289,11 +291,11 @@ public override PluralRules GetInstance(string localeName, PluralType type)
289291
throw new ArgumentNullException(nameof(localeName));
290292

291293
string? rulesId = GetRulesIdForLocale(localeName, type);
292-
if (rulesId == null || rulesId.Trim().Length == 0)
294+
if (string.IsNullOrWhiteSpace(rulesId))
293295
{
294296
return PluralRules.Default;
295297
}
296-
PluralRules? rules = GetRulesForRulesId(rulesId);
298+
PluralRules? rules = GetRulesForRulesId(rulesId!); // [!] Checked for null above
297299
if (rules == null)
298300
{
299301
rules = PluralRules.Default;

src/ICU4N/Util/VersionInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public sealed class VersionInfo : IComparable<VersionInfo>
1515
/// <summary>
1616
/// Map of singletons
1717
/// </summary>
18-
private static readonly ConcurrentDictionary<int, VersionInfo> MAP_ = new ConcurrentDictionary<int, VersionInfo>();
18+
private static readonly ConcurrentDictionary<int, Lazy<VersionInfo>> MAP_ = new ConcurrentDictionary<int, Lazy<VersionInfo>>();
1919

2020
// public data members -------------------------------------------------
2121

@@ -333,7 +333,7 @@ public static VersionInfo GetInstance(int major, int minor, int milli,
333333
int key = GetInt32(major, minor, milli, micro);
334334
int version = key;
335335

336-
return MAP_.GetOrAdd(version, (version) => new VersionInfo(version));
336+
return MAP_.GetOrAdd(version, (version) => new Lazy<VersionInfo>(() => new VersionInfo(version))).Value;
337337
}
338338

339339
/// <summary>

0 commit comments

Comments
 (0)