Fix race condition when getting the loader task

This commit is contained in:
Flamefire committed 2014-09-24 15:33:24 +02:00
1 parent 649f81494e
commit 466c443a20
1 file changed
+92 -75
+92 -75
View File
@@ -346,7 +346,9 @@ namespace Vocaluxe.Lib.Draw
} }
/// <summary> /// <summary>
/// Adds the texture to the cache /// Adds the texture to the cache<br />
/// Asserts that the cache entry does not exist yet. So make Get/Add cache atomic through use of _TextureCache lock!<br />
/// Thread safe
/// </summary> /// </summary>
/// <param name="texture"></param> /// <param name="texture"></param>
/// <param name="origSize"></param> /// <param name="origSize"></param>
@@ -367,25 +369,37 @@ namespace Vocaluxe.Lib.Draw
/// <summary> /// <summary>
/// Gets a new texture reference from the cache <br /> /// Gets a new texture reference from the cache <br />
/// Thread safe but you should hold the _TextureCache lock if you need the loader task /// Thread safe
/// </summary> /// </summary>
/// <param name="texturePath"></param> /// <param name="texturePath"></param>
/// <param name="loader">Task that loads the texture or null if not loading</param>
/// <returns></returns> /// <returns></returns>
private CTextureRef _GetFromCache(string texturePath) private CTextureRef _GetFromCache(string texturePath, out Task<Size> loader)
{ {
loader = null;
if (String.IsNullOrEmpty(texturePath)) if (String.IsNullOrEmpty(texturePath))
return null; return null;
lock (_Textures) lock (_TextureCache)
{ lock (_Textures)
STextureCacheEntry cacheEntry;
if (_TextureCache.TryGetValue(texturePath, out cacheEntry))
{ {
if (cacheEntry.Texture.RefCount <= 0) STextureCacheEntry cacheEntry;
_TextureCache.Remove(texturePath); if (_TextureCache.TryGetValue(texturePath, out cacheEntry))
else {
return _GetTextureReference(cacheEntry.OrigSize, cacheEntry.Texture); if (cacheEntry.Texture.RefCount <= 0)
_TextureCache.Remove(texturePath);
else
{
if (cacheEntry.OrigSize.Width < 0)
{
lock (_BitmapsLoading)
{
loader = _BitmapsLoading[texturePath];
}
}
return _GetTextureReference(cacheEntry.OrigSize, cacheEntry.Texture);
}
}
} }
}
return null; return null;
} }
@@ -522,47 +536,31 @@ namespace Vocaluxe.Lib.Draw
/// <summary> /// <summary>
/// Replaces one oldTexture by newTexture summing the refCounts. Also disposes oldTexture /// Replaces one oldTexture by newTexture summing the refCounts. Also disposes oldTexture
/// IMPORTANT: Caller MUST hold the _Textures lock!
/// </summary> /// </summary>
/// <param name="oldTexture"></param> /// <param name="oldTexture"></param>
/// <param name="newTexture"></param> /// <param name="newTexture"></param>
private void _MergeTextures(TTextureType oldTexture, TTextureType newTexture) private void _MergeTextures(TTextureType oldTexture, TTextureType newTexture)
{ {
_EnsureMainThread();
Debug.Assert(oldTexture.RefCount > 0); Debug.Assert(oldTexture.RefCount > 0);
newTexture.RefCount += oldTexture.RefCount; lock (_Textures)
// ReSharper disable AccessToDisposedClosure
IEnumerable<int> oldKeys = _Textures.Where(pair => pair.Value == oldTexture).Select(pair => pair.Key).ToArray();
// ReSharper restore AccessToDisposedClosure
foreach (int key in oldKeys)
_Textures[key] = newTexture;
oldTexture.Dispose();
}
/// <summary>
/// Checks if a texture is currently beeing loaded and waits for completion if it is <br />
/// Required for cached entries, but should not be called from inside any locks unless async is true
/// </summary>
/// <param name="textureRef"></param>
/// <param name="filePath"></param>
/// <param name="async">If true, do not block</param>
private void _WaitForTextureLoaded(CTextureRef textureRef, string filePath, bool async)
{
// Check if texture is already loaded
if (textureRef.OrigSize.Width > 0)
return;
Task<Size> loader;
lock (_BitmapsLoading)
{ {
if (!_BitmapsLoading.TryGetValue(filePath, out loader)) newTexture.RefCount += oldTexture.RefCount;
Debug.Assert(false, "Corruption: Texture not loaded but no loader found"); IEnumerable<int> oldKeys = _Textures.Where(pair => pair.Value == oldTexture).Select(pair => pair.Key).ToArray();
foreach (int key in oldKeys)
_Textures[key] = newTexture;
if (!String.IsNullOrEmpty(oldTexture.TexturePath))
{
newTexture.TexturePath = oldTexture.TexturePath;
STextureCacheEntry cacheEntry;
if (_TextureCache.TryGetValue(newTexture.TexturePath, out cacheEntry))
{
cacheEntry.Texture = newTexture;
_TextureCache[newTexture.TexturePath] = cacheEntry;
}
}
oldTexture.Dispose();
} }
// We found the texture in the cache, but its bitmap is not yet loaded
// So wait for completion and update the origSize once it is available
// Note: loader.Result blocks till completion of the task
if (async)
Task.Factory.StartNew(() => textureRef.OrigSize = loader.Result);
else
textureRef.OrigSize = loader.Result;
} }
#endregion private/protected #endregion private/protected
@@ -575,13 +573,15 @@ namespace Vocaluxe.Lib.Draw
{ {
_EnsureMainThread(); _EnsureMainThread();
CTextureRef textureRef; CTextureRef textureRef;
Task<Size> loader;
lock (_TextureCache) lock (_TextureCache)
{ {
textureRef = _GetFromCache(texturePath); textureRef = _GetFromCache(texturePath, out loader);
} }
if (textureRef != null) if (textureRef != null)
{ {
_WaitForTextureLoaded(textureRef, texturePath, false); if (loader != null)
textureRef.OrigSize = loader.Result;
Debug.Assert(textureRef.OrigSize.Width > 0); Debug.Assert(textureRef.OrigSize.Width > 0);
return textureRef; return textureRef;
} }
@@ -621,10 +621,11 @@ namespace Vocaluxe.Lib.Draw
CTextureRef textureRef; CTextureRef textureRef;
Size origSize = bmp.GetSize(); Size origSize = bmp.GetSize();
TTextureType texture = null; TTextureType texture = null;
Task<Size> loader;
// Make the Get/Add Cache methods atomic // Make the Get/Add Cache methods atomic
lock (_TextureCache) lock (_TextureCache)
{ {
textureRef = _GetFromCache(texturePath); textureRef = _GetFromCache(texturePath, out loader);
if (textureRef == null) if (textureRef == null)
{ {
_WriteBitmapToTexture(ref texture, bmp); _WriteBitmapToTexture(ref texture, bmp);
@@ -634,8 +635,8 @@ namespace Vocaluxe.Lib.Draw
} }
if (textureRef == null) if (textureRef == null)
textureRef = _GetTextureReference(origSize, texture); textureRef = _GetTextureReference(origSize, texture);
else else if (loader != null)
_WaitForTextureLoaded(textureRef, texturePath, false); textureRef.OrigSize = loader.Result;
Debug.Assert(textureRef.OrigSize.Width > 0); Debug.Assert(textureRef.OrigSize.Width > 0);
return textureRef; return textureRef;
} }
@@ -693,13 +694,14 @@ namespace Vocaluxe.Lib.Draw
CTextureRef textureRef; CTextureRef textureRef;
TTextureType texture = null; TTextureType texture = null;
Task<Size> loader = null; Task<Size> loader;
// Make Get/Add cache atomic // Make Get/Add cache atomic
lock (_TextureCache) lock (_TextureCache)
{ {
textureRef = _GetFromCache(filePath); textureRef = _GetFromCache(filePath, out loader);
if (textureRef == null) if (textureRef == null)
{ {
Debug.Assert(loader == null);
Size invalidSize = new Size(-1, -1); Size invalidSize = new Size(-1, -1);
texture = _CreateTexture(invalidSize); texture = _CreateTexture(invalidSize);
// This cache entry has to be updated once the real origSize is available // This cache entry has to be updated once the real origSize is available
@@ -718,7 +720,9 @@ namespace Vocaluxe.Lib.Draw
if (texture == null) if (texture == null)
{ {
// Found in cache // Found in cache
_WaitForTextureLoaded(textureRef, filePath, true); CTextureRef tmp = textureRef; // Workaround for implicitly captured closure warning (false positive)
if (loader != null)
Task.Factory.StartNew(() => tmp.OrigSize = loader.Result);
return textureRef; return textureRef;
} }
@@ -736,42 +740,46 @@ namespace Vocaluxe.Lib.Draw
/// <returns></returns> /// <returns></returns>
private Size _LoadAndEnqueueBitmap(string filePath, CTextureRef textureRef) private Size _LoadAndEnqueueBitmap(string filePath, CTextureRef textureRef)
{ {
Size origSize;
Bitmap bmp = CHelper.LoadBitmap(filePath); Bitmap bmp = CHelper.LoadBitmap(filePath);
if (bmp == null) if (bmp == null)
{ {
RemoveTexture(ref textureRef); // Done asynchonously in the function RemoveTexture(ref textureRef); // Done asynchonously in the function
origSize = new Size(-1, -1);
}
else
{
origSize = bmp.GetSize();
textureRef.OrigSize = origSize;
// Update cache, use the same lock as in add/get cache methods
lock (_Textures) lock (_Textures)
{ {
STextureCacheEntry cacheEntry; _TextureCache.Remove(filePath);
if (_TextureCache.TryGetValue(filePath, out cacheEntry)) lock (_BitmapsLoading)
{ {
cacheEntry.OrigSize = origSize; _BitmapsLoading.Remove(filePath);
_TextureCache[filePath] = cacheEntry;
} }
} }
Size size = _GetNewTextureSize(bmp.GetSize()); return new Size(-1, -1);
if (!size.Equals(bmp.GetSize())) }
Size origSize = bmp.GetSize();
textureRef.OrigSize = origSize;
// Update cache, use the same lock as in add/get cache methods
lock (_Textures)
{
STextureCacheEntry cacheEntry;
if (_TextureCache.TryGetValue(filePath, out cacheEntry))
{ {
Bitmap bmp2 = bmp.Resize(size); cacheEntry.OrigSize = origSize;
bmp.Dispose(); _TextureCache[filePath] = cacheEntry;
bmp = bmp2;
} }
lock (_TextureQueue) lock (_BitmapsLoading)
{ {
_TextureQueue.Enqueue(new STextureQueue(textureRef, EQueueAction.Add, bmp)); _BitmapsLoading.Remove(filePath);
} }
} }
lock (_BitmapsLoading) Size size = _GetNewTextureSize(origSize);
if (!size.Equals(origSize))
{ {
_BitmapsLoading.Remove(filePath); Bitmap bmp2 = bmp.Resize(size);
bmp.Dispose();
bmp = bmp2;
}
lock (_TextureQueue)
{
_TextureQueue.Enqueue(new STextureQueue(textureRef, EQueueAction.Add, bmp));
} }
return origSize; return origSize;
} }
@@ -840,7 +848,16 @@ namespace Vocaluxe.Lib.Draw
return null; return null;
// If bitmap is not yet loaded, wait for it // If bitmap is not yet loaded, wait for it
if (!String.IsNullOrEmpty(texture.TexturePath)) if (!String.IsNullOrEmpty(texture.TexturePath))
_WaitForTextureLoaded(textureRef, texture.TexturePath, false); {
Task<Size> loader;
lock (_BitmapsLoading)
{
_BitmapsLoading.TryGetValue(texture.TexturePath, out loader);
}
if (loader != null)
textureRef.OrigSize = loader.Result;
}
Debug.Assert(textureRef.OrigSize.Width > 0);
return _GetTextureReference(textureRef.OrigSize, texture); return _GetTextureReference(textureRef.OrigSize, texture);
} }