Month: February 2014

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)
)
 
Advertisements

How to know where you have a memory leak?

Posted on Updated on

All Sharepoint developers have seen sometimes this:

An SPRequest object was not disposed before the end of this thread.  To avoid wasting system resources, dispose of this object or its parent (such as an SPSite or SPWeb) as soon as you are done using it.  This object will now be disposed.

And normally if you are a good one, you know how to fix it in the code you developed.   But, what about if you did not develop the code and you are trying to troubleshoot performance problems and memory leak. By default the ULS Logs only shows the page, but it wont show the methods, of the page that have the memory leak.   You can activate this with the following script.

Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue [System.Reflection.Assembly]::LoadFile($Env:CommonProgramFiles+”Microsoft SharedWeb Server Extensions14ISAPIMicrosoft.SharePoint.dll”) | out-null # Get Content Service of the farm $contentService = [Microsoft.SharePoint.Administration.SPWebService]::ContentService # Display and change the setting of property “CollectSPRequestAllocationCallStacks” write-host “Current: ” $contentService.CollectSPRequestAllocationCallStacks $contentService.CollectSPRequestAllocationCallStacks = $true $contentService.Update() write-host ” New: ” $contentService.CollectSPRequestAllocationCallStacks