Sunday, March 08, 2009

Memory Management of Windows SharePoint Services (WSS) Objects: Best Practices and SPDisposeCheck Tool

The Windows SharePoint Services (WSS) 3.0 object model, provides a set of classes to work with WSS data. These objects can be used to read and write data to the WSS store. The WSS object model contains many objects that implement the IDisposable interface, which primary use is to release unmanaged resources. The Dispose method of IDisposable is intended to be called by consumers of the object, directly or with the using statement, in order to release unmanaged resources.

Best Practices for Disposing WSS objects

Two common WSS objects that implement IDisposable are SPSite and SPWeb. SPSite represents a site collection, and SPWeb represents a single site. A common mistake is to use these objects without disposing unmanaged resources:

// BAD: SPSite and SPWeb will be leaked.
SPSite site = new SPSite("http://server");
SPWeb web = site.OpenWeb();
string title = web.Title;
string url = web.Url;

A better way to write the code above is:
// GOOD: No leaks.
using(SPSite site = new SPSite("http://server"))
{
using(SPWeb web = site.OpenWeb())
{
string title = web.Title;
string url = web.Url;
}
}
The code above was very simple, but you got the idea. Things start getting more complicated when you start combining calls into the same line. This makes harder to catch leaks, since you do not see the implicit referenced object. For example:
// BAD
SPWeb web = new SPSite("http://server").OpenWeb();
The line above, might make you thing that there is only one leak, and you will attempt to fix with the following:
// BAD
using (SPWeb web = new SPSite("http://server").OpenWeb())
{
...
}
The code above will dispose the SPWeb object that is returned by the OpenWeb method, but it will not dispose the SPSite that is created by the new operator. The solution is the same as the first problem:
// GOOD: No leaks.
using(SPSite site = new SPSite("http://server"))
using(SPWeb web = site.OpenWeb())
{
...
}
Working with SharePoint collections also need some attention. When using SPWebCollection objects, you will need to dispose any SPWeb object that is accessed using the [] indexer or with a foreach statement. The same applies for SPSiteCollection. The following code shows how to avoid leaks when using the SPSiteWeb object:

// GOOD: No leaks.
using (SPSite site = new SPSite("http://server"))
{
using (SPWeb web = siteCollection.OpenWeb())
{
foreach (SPWeb innerWeb in site.AllWebs)
{
try
{
// ...
}
finally
{
// Must dispose the collection item.
if(innerWeb != null)
innerWeb.Dispose();
}
}
}
}
A similar approach would be used if you were accessing collection items using the [] indexer. You would need to dispose each object returned by [].

The examples above just show you a few common cases of how to dispose WSS objects, just for you to get the idea. The following article provides detailed explanation of how to detect and proper dispose your SharePoint objects:
You can also look at Roger Lamb's article which complements the article above with lots of examples:SPDisposeCheck Tool

The SPDisposeCheck tool analyzes your assembly and detect if you are disposing WSS objects properly. This tool takes the path to a managed .DLL or .EXE, or the path to a directory containing many managed assemblies. It will recursively search for and analyze each managed module attempting to detect coding patterns based on best practice article above. The tool does not detect all possible unmanaged resources leaks, so you still have to learn the best practices and review your code.

You can install SPDisposeCheck from here. It is a console application, and you should add C:\Program Files\Microsoft\SharePoint Dispose Check to your path.

The usage is:

SPDisposeCheck -debug –xml

The output is a list of potential problems. If you have the PDB symbol file available, then the output will include additional source code information about the error. The -debug option adds additional information to the output. The -xml option outputs the errors to an XML file instead of text, however, the SPDisposeCheck site says that this option is unreliable, and they do not recommend to use this command on this release.

I run this tool with a WebPart that I recently created, and I got the following result:

ID: SPDisposeCheckID_160
Module: CustomCalendar.dll
Method: CalendarGenerator.SharePointCalendarRepository.#ctor(System.String)
Statement: manager := web.{Microsoft.SharePoint.SPWeb}GetLimitedWebPartManager(pageUrl, 1)
Source: D:\Code\CustomCalendar\CustomCalendar\SharePointCalendarRepository.cs
Line: 40
Notes: Dispose/Close was not called on SPLimitedWebPartManager.Web
More Information: http://blogs.msdn.com/rogerla/archive/2008/02/12/sharepoint-2007-and-wss-3-0-dispose-patterns-by-example.aspx#SPDisposeCheckID_160
----------------------------------------------------------

Total Found: 1

Well, although I am embarrassed that it found a bug in my code, I am very glad that this tool works! In my code, I am calling GetLimitedWebPartManager which returns a SPLimitedWebPartManager. This returned reference also needs to be disposed.

The following table contains the possible errors, links to the detailed description, and a summary of the solution.

Error CodeSummary Solution
SPDisposeCheckID_110 If you create a SharePoint object with a new operator, ensure that the creating application disposes of it.
SPDisposeCheckID_120 You must dispose the SPWeb objects that are created by SharePoint methods that return other SPWeb objects (such as OpenWeb).
SPDisposeCheckID_130 You must dispose the SPSite object that is returned each time you access SPSite.AllWebs [] index operator.
SPDisposeCheckID_140 It is NOT necessary to dispose the SPWeb object returned by SPWeb.RootWeb. The dispose cleanup is handled automatically by the SharePoint framework.
SPDisposeCheckID_150 You must dispose the SPWeb object the is returned by calling SPSite.AllWebs.Add method.
SPDisposeCheckID_160 You must dispose the SPWeb object the is returned by the SPLimitedWebPartManager.Web property.
SPDisposeCheckID_170 It is NOT necessary to dispose the SPWeb object returned by SPWeb.ParentWeb. The dispose cleanup is handled automatically by the SharePoint framework.
SPDisposeCheckID_180 You must dispose all SPSite objects in the SPWeb.Webs property (SPWebCollection type).
SPDisposeCheckID_190 You must dispose the SPSite object that is created and returned by the SPWeb.Webs.Add method.
SPDisposeCheckID_200 You must dispose the SPSite object that is created and returned by the SPWebCollection.Add method.
SPDisposeCheckID_210 It is NOT necessary to dispose SPContext object returned from calling SPControl.GetContextSite and SPControl.GetContextWeb Methods, since they are managed by the SharePoint framework.
SPDisposeCheckID_220 It is NOT necessary to dispose SPContext objects on your code, since they are managed by the SharePoint framework.
SPDisposeCheckID_230 You must dispose the SPSite object that is returned each time you access SPSiteCollection [] index operator.
SPDisposeCheckID_240 You must dispose the any SPSite object returned from the SPSiteCollection.Add method.


The tool also allows you to ignore a reported issue. If you have investigated a reported issue and are satisfied that it does not represent a problem, then you can add the following attribute to the calling method:
[SPDisposeCheckIgnore(SPDisposeCheckID.SPDisposeCheckID_100, "Ignoring this error")]
To add this attribute to your method, you will need to grab the source code of the attribute class from the file:

C:\Program Files\Microsoft\SharePoint Dispose Check\SPDisposeExamplesSource.zip

One of things to consider, is to run SPDisposeCheck Tool as a post-build event of your assembly. If it reports any issue, then the build fails. Developers can then detect leak problems at build time and troubleshoot them as soon as possible.

Conclusion

The improper memory management of WSS objects can lead to a poor system performance, system crash, or users experiencing unexpected errors (timeouts, page not available), especially under heavy loads. To avoid these problems, developers need to follow best practices for disposing WSS objects, and also use tools, such as SPDisposeCheck, to check if their assemblies are properly disposing WSS objects.

References

1 comment:

Anonymous said...

Thank you Luis,

I used the bad habbit for while (spweb web = new spsite("URL").OpenWeb(); ), untill one day a client started to complain about the memory leak in the hosting server, so i changed my developmennt habbit and started working with USING blocks.

Have a nice day.