Browse Source

Make sure TryGetGlyphTypeface does not fail for concurrent access

pull/21269/head
Benedikt Stebner 6 months ago
parent
commit
e8f34d205f
  1. 21
      src/Avalonia.Base/Media/FontManager.cs
  2. 59
      tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs

21
src/Avalonia.Base/Media/FontManager.cs

@ -360,25 +360,20 @@ namespace Avalonia.Media
source = SystemFontsKey;
}
if (!_fontCollections.TryGetValue(source, out fontCollection))
fontCollection = _fontCollections.GetOrAdd(source, static (key, platformImpl) =>
{
if (source == SystemFontsKey)
if (key == SystemFontsKey)
{
fontCollection = new SystemFontCollection(PlatformImpl);
}
else
{
if (source.IsAbsoluteResm() || source.IsAvares())
{
fontCollection = new EmbeddedFontCollection(source, source);
}
return new SystemFontCollection(platformImpl);
}
if (fontCollection != null)
if (key.IsAbsoluteResm() || key.IsAvares())
{
return _fontCollections.TryAdd(fontCollection.Key, fontCollection);
return new EmbeddedFontCollection(key, key);
}
}
return null!;
}, PlatformImpl);
return fontCollection != null;
}

59
tests/Avalonia.Base.UnitTests/Media/FontManagerTests.cs

@ -1,5 +1,10 @@
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.UnitTests;
using Xunit;
@ -86,5 +91,59 @@ namespace Avalonia.Base.UnitTests.Media
Assert.Equal("DejaVu", FontManager.Current.DefaultFontFamily.Name);
}
}
[Fact]
public async Task TryGetGlyphTypeface_Should_Be_Thread_Safe_For_Embedded_Fonts()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var fontManager = FontManager.Current;
var fontCollections = (ConcurrentDictionary<Uri, IFontCollection>)
typeof(FontManager)
.GetField("_fontCollections", BindingFlags.NonPublic | BindingFlags.Instance)!
.GetValue(fontManager)!;
const string fontUri =
"resm:Avalonia.Base.UnitTests.Assets?assembly=Avalonia.Base.UnitTests#Noto Mono";
var collectionKey =
new Uri("resm:Avalonia.Base.UnitTests.Assets?assembly=Avalonia.Base.UnitTests");
// Warm up to validate the font URI is correct.
Assert.True(fontManager.TryGetGlyphTypeface(new Typeface(new FontFamily(fontUri)), out _));
const int iterations = 50;
int failures = 0;
for (int i = 0; i < iterations; i++)
{
// Remove the cached collection so both threads must re-create it.
fontCollections.TryRemove(collectionKey, out _);
using var barrier = new Barrier(2);
bool r1 = false, r2 = false;
var t1 = Task.Run(() =>
{
barrier.SignalAndWait();
r1 = fontManager.TryGetGlyphTypeface(
new Typeface(new FontFamily(fontUri)), out _);
});
var t2 = Task.Run(() =>
{
barrier.SignalAndWait();
r2 = fontManager.TryGetGlyphTypeface(
new Typeface(new FontFamily(fontUri)), out _);
});
await Task.WhenAll(t1, t2);
if (!r1 || !r2)
Interlocked.Increment(ref failures);
}
Assert.Equal(0, failures);
}
}
}
}

Loading…
Cancel
Save