0
votes

I have an application that stores large lists in cache. To prevent several users to fill this cache at the same time, I am using a Mutex. However, I get occasional errors:

System.Threading.AbandonedMutexException: The wait completed due to an abandoned mutex.

You would argue that I forgot to release the Mutex but the release is in a Finally block so it should always release it. How can the code below cause a AbandonedMutexException? The 'mutex.ReleaseMutex' is supposed to be fired always.

    public static List<Objects.SearchListItem> GetActiveCwPropertyListItemsByCwPropertyAndCulture(CwProperty cwProperty, string cultureCode)
{
    var cacheKey = "GetActiveCwPropertyListItemsByCwPropertyAndCulture_" + cwProperty.Guid + "_" + cultureCode;
    var mutex = new Mutex(true, cacheKey);
    mutex.WaitOne();
    try
    {
        var cachedValue = HttpRuntime.Cache[cacheKey];
        if (cachedValue != null) return (List<Objects.SearchListItem>)cachedValue;
        var value = new List<Objects.SearchListItem>();
        var dr = new CwDbAccess(System.Data.CommandType.StoredProcedure, "CwPropertyListItemPart_LoadByCwProperty");
        try
        {
            dr.ExecuteReader();
            while (dr.MyReader.Read())
            {
                value.Add(new Objects.SearchListItem
                {
                    Name = Utils.ProperCase(dr.MyReader["Name"].ToString()),
                    Key = dr.MyReader["CwPropertyListItem_Guid"].ToString(),
                    IsAlias = false
                });
            }
        }
        catch (Exception ex)
        {
            CwLogging.LogException("Caching.ListItems.GetActiveCwPropertyListItemsByCwPropertyAndCulture", ex, ExceptionLevel.Urgent);
        }
        finally
        {
            dr.Close();
        }
        var keys = new List<string>();
        AddDependency("CwSiteClusterKey_" + cwProperty.CachedCwEntity.CwSiteClusterGuid, keys);
        AddDependency("CwPropertyKey_" + cwProperty.Guid, keys);
        HttpRuntime.Cache.Insert(cacheKey, value, new CacheDependency(null, keys.ToArray()), Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
        return value;
    }
    catch
    {
        throw;
    }
    finally
    {
        mutex.ReleaseMutex();
    }
}

The full stacktrace is:

System.Threading.AbandonedMutexException: The wait completed due to an abandoned mutex. at System.Threading.WaitHandle.ThrowAbandonedMutexException() at System.Threading.WaitHandle.InternalWaitOne(SafeHandle waitableSafeHandle, Int64 millisecondsTimeout, Boolean hasThreadAffinity, Boolean exitContext) at System.Threading.WaitHandle.WaitOne(Int32 millisecondsTimeout, Boolean exitContext) at Common.Caching.ListItems.GetActiveCwPropertyListItemsByCwPropertyAndCulture(CwProperty cwProperty, String cultureCode) at Model.SearchFacet.GetAllSearchListItems(CwContext cwContext) at Common.Search.SqlFacets.RetrieveSqlFacetsByGroup(CwEntity cwEntity, NameValueCollection storedRequestForm, SqlFacetsContainer sqlFacets, SearchFacetGroupCollection groups, SearchTemplate searchTemplate, CwContext cwContext) at Common.Search.SqlFacets.RetrieveSqlFacets(CwEntity cwEntity, NameValueCollection storedRequestForm, CwContext cwContext, SearchResultViewType searchResultViewType) at Common.Caching.SqlFacets.GetSqlFacets(String hash, NameValueCollection storedRequestForm, CwEntity cwEntity, CwContext cwContext, SearchResultViewType searchResultViewType) at Compareware_WebApp.Ui.Pages.SearchPage.AppendSearchAndResults(StringBuilder sb) in C:\Projects\Compareware\Compareware.WebApp\Ui\Pages\SearchPage.cs:line 454 at Compareware_WebApp.Ui.Pages.SearchPage.get_MainContent() in C:\Projects\Compareware\Compareware.WebApp\Ui\Pages\SearchPage.cs:line 310 at ASP.default_aspx.__RenderContent2(HtmlTextWriter __w, Control parameterContainer) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at ASP.masters_site_master.__RenderForm1(HtmlTextWriter __w, Control parameterContainer) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.HtmlControls.HtmlForm.RenderChildren(HtmlTextWriter writer) at System.Web.UI.HtmlControls.HtmlContainerControl.Render(HtmlTextWriter writer) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.HtmlControls.HtmlForm.RenderControl(HtmlTextWriter writer) at ASP.masters_site_master.__RenderBody(HtmlTextWriter __w, Control parameterContainer) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.HtmlControls.HtmlContainerControl.Render(HtmlTextWriter writer) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at ASP.masters_site_master.__Render__control1(HtmlTextWriter __w, Control parameterContainer) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.Page.Render(HtmlTextWriter writer) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

1
Going to need a MCVE. There isn't enough information to debug this. Something, somewhere must not be releasing the mutex.Liam
Your mutex should be static really. I don't think you should create a Mutex on every call, it also smells wrong to then flag the mutex ownership to true everytime. What if two threads create two mutexes at the same time, who's the owner? These are just vague observations TBFLiam
Can you provide the full stack trace?John Kim
John, I added the stacktrace.Peter de Bruijn
From what I can see, as @Liam said there is no reason for you to be creating a new mutex every time you call this function. What you are trying to protect is the cache and there isn't multiple instances of this cache. You should instead use a single mutex instance.John Kim

1 Answers

1
votes

You should be using a single mutex instead of declaring a new mutex for every function call. Perhaps declare a static mutex in the scope of the class this function is in, and wait/release as needed. Check the example on this page :

https://docs.microsoft.com/en-us/dotnet/api/system.threading.mutex?view=netframework-4.7.2