memory leak

Linq and Sharepoint SPSite.AllWebs not good friends.

Posted on

In my current project, we have a page on the root web of every site collection (18,000 site collections), every welcome page has 2 webparts which renders the last created sites.

We have 2 types of sites, jobs and opportunities.  This information in saved in the property bag under the key “WebProperty”.

The requirement is to show the last 5 created sites of each type.

This code is as I had it before.

private void LoadGridData()
        {
            try
            {
                String currentUrl = SPContext.Current.Site.Url;

                var jobInfoList = new List<JobInfo>();

                SPSecurity.RunWithElevatedPrivileges(delegate
                {
                    using (var clientSiteCollection = new SPSite(currentUrl))
                    {
                        foreach (
                            SPWeb web in
                                clientSiteCollection.AllWebs.Where(
                                    c =>
                                    c.AllProperties[Constants.WebProperties.General.WebTemplate] != null &&
                                    c.AllProperties[Constants.WebProperties.General.WebTemplate].ToString() ==
                                    Constants.WebTemplates.JobWebPropertyName).OrderByDescending(d => d.Created).Take(5)
                            )
                        {
                            if (web.DoesUserHavePermissions(SPContext.Current.Web.CurrentUser.LoginName,
                                SPBasePermissions.Open))
                            {
                                SPList jobInfoListSp = web.Lists.TryGetList(Constants.Lists.JobInfoName);
                                if (jobInfoListSp != null)
                                {
                                    if (jobInfoListSp.Items.Count > 0)
                                    {
                                        var value =
                                            new SPFieldUrlValue(
                                                jobInfoListSp.Items[0][Constants.FieldNames.Job.iPowerLink].ToString());

                                        jobInfoList.Add(new JobInfo
                                        {
                                            JobName =
                                                jobInfoListSp.Items[0][Constants.FieldNames.Job.JobName].ToString(),
                                            JobCode =
                                                jobInfoListSp.Items[0][Constants.FieldNames.Job.JobCode].ToString(),
                                            IPowerLink = value.Url,
                                            JobWebsite = web.Url,
                                            IsConfidential =
                                                HelperFunctions.ConvertToBoolean(
                                                    jobInfoListSp.Items[0][Constants.FieldNames.Job.Confidential]
                                                        .ToString())
                                        });
                                    }
                                }
                            }

                            web.Dispose();
                        }
                    }
                });

                _lastCreatedJobsGrid.DataSource = jobInfoList;
                _lastCreatedJobsGrid.DataBind();
            }
            catch (Exception ex)
            {
                LoggingService.LogError(LoggingCategory.Job, ex);
            }
        }


Well, you can see there is a Dispose() inside the foreach, that is not the problem, the problem is the AllWebs.

So, I dig deep into it and I found the following link : http://solutionizing.net/2009/01/05/linq-for-spwebcollection-revisited-assafeenumerable/

So, I replaced the AllWebs with AllWebs.AsSafeEnumerable().

Then, the memory leak went away.

But guess what, this code introduced another problem.

53b416d1-1497-4b40-beb5-cd261180ece8 Stack trace:   
 at Microsoft.SharePoint.SPWeb.get_Created()    
 at xx.SP.DMS.WebParts.WebParts.LastCreatedJobs.LastCreatedJobs.<>c__DisplayClass8.<LoadGridData>b__7(SPWeb d)    
 at System.Linq.EnumerableSorter`2.ComputeKeys(TElement[] elements, Int32 count)    
 at System.Linq.EnumerableSorter`1.Sort(TElement[] elements, Int32 count)    
 at System.Linq.OrderedEnumerable`1.<GetEnumerator>d__0.MoveNext()    
 at System.Linq.Enumerable.<TakeIterator>d__3a`1.MoveNext()    
 at xx.SP.DMS.WebParts.WebParts.LastCreatedJobs.LastCreatedJobs.<>c__DisplayClass8.<LoadGridData>b__5()    
 at Microsoft.SharePoint.SPSecurity.<>c__DisplayClass5.<RunWithElevatedPrivileges>b__3()    
 at Microsoft.SharePoint.Utilities.SecurityContext.RunAsProcess(CodeToRunElevated secureCode)    
 at Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(WaitCallback secureCode, Object param)    
 at Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(CodeToRunElevated secureCode)    
 at xx.SP.DMS.WebParts.WebParts.LastCreatedJobs.LastCreatedJobs.LoadGridData()    
 at xx.SP.DMS.WebParts.WebParts.LastCreatedJobs.LastCreatedJobs.OnPreRender(EventArgs e)    
 at System.Web.UI.Control.PreRenderRecursiveInternal()    
 at System.Web.UI.Control.PreRenderRecursiveInternal()    
 at System.Web.UI.Control.PreRenderRecursiveInternal()    
 at System.Web.UI.Control.PreRenderRecursiveInternal()    
 at System.Web.UI.Control.PreRenderRecursiveInternal()    
 at System.Web.UI.Control.PreRenderRecursiveInternal()    
 at System.Web.UI.Control.PreRenderRecursiveInternal()    
 at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)    
 at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)    
 at System.Web.UI.Page.ProcessRequest()    
 at System.Web.UI.Page.ProcessRequest(HttpContext context)    
 at Microsoft.SharePoint.Publishing.TemplateRedirectionPage.ProcessRequest(HttpContext context)    
 at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()    
 at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)    
 at System.Web.HttpApplication.PipelineStepManager.ResumeSteps(Exception error)    
 at System.Web.HttpApplication.BeginProcessRequestNotification(HttpContext context, AsyncCallback cb)    
 at System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context)    
 at System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)    
 at System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)    
 at System.Web.Hosting.UnsafeIISMethods.MgdIndicateCompletion(IntPtr pHandler, RequestNotificationStatus& notificationStatus)    
 at System.Web.Hosting.UnsafeIISMethods.MgdIndicateCompletion(IntPtr pHandler, RequestNotificationStatus& notificationStatus)    
 at System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)    
 at System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr rootedObjectsPointer, IntPtr nativeRequestContext, IntPtr moduleData, Int32 flags)

When I debugged line by line, my catches were no hit, but then I saw that my new method AsSafeEnumerable(), didnt have a catch. just a finally, so I guessed there was an exception there, just not caught.

My good friends here helped me to find the problem, apparently the .Where from Linq and the .Take, are not “Safe” with SPWeb objects, so extension methods had to be done to overcome this error.

See original question here:
http://stackoverflow.com/questions/21756403/error-on-system-linq-enumerable-takeiteratord-3a1-movenext

I fixed the problem by creation these 2 methods.

public static IEnumerable<TSource> SafeTake<TSource>(
    this IEnumerable<TSource> source,
    int count
) where TSource : IDisposable
{
    foreach(var item in source)
    {
        if(--count >= 0)
        {
            yield return item;
        }
        else if (item != null)
        {
            item.Dispose();
        }
    }
}

public static IEnumerable<TSource> SafeWhere<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate
) where TSource: IDisposable
{
    return source.SelectMany(x => {
        var result = predicate(x);
        if(!result && x != null) x.Dispose();
        return Enumerable.Repeat(x, result ? 1 : 0);
    });
}

And then of course replacing the Where with SafeWher and the Take with SafeTake


foreach (var web in clientSiteCollection.AllWebs.SafeWhere(
c =>
c.AllProperties[Constants.WebProperties.General.WebTemplate] != null &&
c.AllProperties[Constants.WebProperties.General.WebTemplate].ToString() ==
Constants.WebTemplates.JobWebPropertyName).OrderByDescending(d => d.Created).SafeTake(5)
)