Why you should not use System.Drawing from ASP.NET applications

by jask2002 23. March 2012 07:22

Probably you would heard it many times and it is quite evident from the MSDN article  http://msdn.microsoft.com/en-us/library/system.drawing.aspx

“Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions.”

Today I’ll try to explain WHY it is not supported through the windbg dump analysis.

My customer reported that they are seeing High Memory usage corresponding to total hang of the ASP.NET application hosted on w3wp.exe. Only way to get rid of this issue is to recycle App Pool or issue IISRESET. I asked them to capture multiple manual hang dumps using Debug Diag 1.2 .

Looking through the dumps I figured out that the process size was 6.5 GB in approx 18 hours of uptime. Next stop was to see which region is taking the max memory.

(!address –summary) output

-------------------- Usage SUMMARY --------------------------

    TotSize (      KB)   Pct(Tots) Pct(Busy)   Usage

   3cd29e000 (15944312) : 00.19%    82.87%    : RegionUsageIsVAD

   7fb69bad000 (8570695348) : 99.78%    00.00%    : RegionUsageFree

   12615000 (  301140) : 00.00%    01.57%    : RegionUsageImage

    9a80000 (  158208) : 00.00%    00.82%    : RegionUsageStack

     26a000 (    2472) : 00.00%    00.01%    : RegionUsageTeb

   acea3000 ( 2833036) : 00.03%    14.73%    : RegionUsageHeap

 

RegionUsageIsVAD [ .Net Virtual allocation] is taking up 15 GB!!

 

Top memory grabber from .Net allocations [!dumpheap –stat]

0x0000064278436728 1,365,494  129,817,288 System.String

0x00000642b7a208b8  562,649  130,534,568 System.Data.DataColumn

0x0000064278424518  749,367  348,121,248 System.Object[]

0x000006427842dce0 7,001,358 448,086,912 System.Threading.ReaderWriterLock

Total 19,448,679 objects, Total size: 1,994,659,064

 

We had around 7 million objects of type System.Threading.ReaderWriterLock. Why these are not cleaned up? That forced me to look thorough the output of !finalizequeue

 

Look at the numbers of objects ready for finalization on two of the heap out of 24

Heap 0

generation 0 has 0 finalizable objects (000000048e6d5e30->000000048e6d5e30)

generation 1 has 407 finalizable objects (000000048e6d5178->000000048e6d5e30)

generation 2 has 2599 finalizable objects (000000048e6d0040->000000048e6d5178)

Ready for finalization 807203 objects (000000048e6d5e30->000000048ecfe748)

------------------------------

Heap 1

generation 0 has 0 finalizable objects (000000048b164e08->000000048b164e08)

generation 1 has 0 finalizable objects (000000048b164e08->000000048b164e08)

generation 2 has 2489 finalizable objects (000000048b160040->000000048b164e08)

Ready for finalization 697519 objects (000000048b164e08->000000048b6b7380)

 

Now is the right time to see what's are only finalizer thread doing ?

 

0:115> !threads

ThreadCount: 272

       ID OSID        ThreadOBJ     State   GC     GC Alloc Context                  Domain           Count APT Exception

  34    1  650 0000000000172e50   1808221 Enabled  0000000000000000:0000000000000000 000000000a61c400     1 MTA (Threadpool Worker)

61    2 15a4 00000000001bf670      b220 Enabled  0000000000000000:0000000000000000 000000000a61c400     0 MTA (Finalizer)

Now looking at the finalizer thread which is (Thread# 61)

 

0:061> k

00000000`0a76f188 00000000`77efb9b6 ntdll!ZwWaitForSingleObject+0xa

00000000`0a76f190 00000000`77efba20 ntdll!RtlpWaitOnCriticalSection+0x240

00000000`0a76f210 000007ff`764b7410 ntdll!RtlEnterCriticalSection+0xa9

00000000`0a76f240 00000642`7f601837 GdiPlus!GdipDeleteFont+0x20

00000000`0a76f270 00000642`75c437ae mscorwks!DoNDirectCall__PatchGetThreadCall+0x7b

00000000`0a76f310 00000642`75c4d478 System_Drawing_ni!DomainNeutralILStubClass.IL_STUB(System.Runtime.InteropServices.HandleRef)+0x5e

00000000`0a76f3d0 00000642`75c42b19 System_Drawing_ni!System.Drawing.Font.Dispose(Boolean)+0xf8

00000000`0a76f470 00000642`7f601f66 System_Drawing_ni!System.Drawing.Font.Finalize()+0x19

00000000`0a76f4b0 00000642`7f4ba7c1 mscorwks!FastCallFinalizeWorker+0x6

00000000`0a76f4e0 00000642`7f4baa0c mscorwks!FastCallFinalize+0xb1

00000000`0a76f540 00000642`7f4bf504 mscorwks!MethodTable::CallFinalizer+0xfc

00000000`0a76f580 00000642`7f52c27b mscorwks!SVR::CallFinalizer+0x84

00000000`0a76f5e0 00000642`7f52c01b mscorwks!SVR::DoOneFinalization+0xdb

00000000`0a76f6b0 00000642`7f5bb3ab mscorwks!SVR::FinalizeAllObjects+0x9b

00000000`0a76f770 00000642`7f448058 mscorwks!SVR::FinalizeAllObjects_Wrapper+0x1b

00000000`0a76f7a0 00000642`7f5591cd mscorwks!ManagedThreadBase_DispatchInner+0x2c

00000000`0a76f7f0 00000642`7f43b22d mscorwks!ManagedThreadBase_DispatchMiddle+0x9d

00000000`0a76f8c0 00000642`7f585009 mscorwks!ManagedThreadBase_DispatchOuter+0x31

00000000`0a76f900 00000642`7f44225b mscorwks!ManagedThreadBase_DispatchInCorrectAD+0x15

00000000`0a76f930 00000642`7f847531 mscorwks!Thread::DoADCallBack+0x12f

 

It is trying to clean Drawing object and waiting on Critical section  which is owned by Thread 115

 

Looking at all Locked threads in the dumps

0:115> !critlist

CritSec at 000007ff`76666728   Owned by thread 115

  Waiting Threads: 33 34 55 61 64 67 68 69 70 71 75 76 77 78 79 80 81 82 83 84 86 87 88 89 91 95 96 97 98 99 100 102 104 105 106 107 114 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 166 167 168 169 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 267 268 269 270 271 272 273 274 275 276 277 278 280 281 282 283 284 285 286 287 288 289 290 291 293 294 295 296 297 298 300

Looking at thread 115 it is waiting for GC to complete. But GC is waiting for Finalizer thread to return back

That’s the deadlock !!!

 

A finalize method should never perform a blocking operation, the reason for this is that we only have one finalize thread in a .net process, and if we block it we won't be able to get rid of any objects that need finalization. This of course leads to high memory consumption, but also to a large number of garbage collections since we will reach the limits that cause us to GC over and over again because we never get rid of any data. Seems pretty much like what we are running into in our scenario.

How to avoid this deadlock  ?

Short term solution:

Best option is to explicitly call Dispose/Close on EACH Drawing object  as soon as done with it, so that you does not depend on single Finalizer thread to clean/dispose up and thus avoid problem for leaking/orphaned critical sections and avoiding deadlock.

From MSDN Article :

http://msdn.microsoft.com/en-s/library/ms998512.aspx

  • How to clean up resources

Release resources as soon as you have finished with them. Use finally blocks or the C# using statement to make sure that resources are released even if an exception occurs. Make sure that you call Dispose (or Close) on any disposable object that implements the IDisposable interface. Use finalizers on classes that hold on to unmanaged resources across client calls. Use the Dispose pattern to help ensure that you implement Dispose functionality and finalizers (if they are required) correctly and efficiently.

For more information, see "Finalize and Dispose Guidelines" and "Dispose Pattern" in Chapter 5, "Improving Managed Code Performance."

Next step would be start thinking about alternative for using System.Drawing

Some info in there from our product group :

http://weblogs.asp.net/bleroy/archive/2009/12/10/resizing-images-from-the-server-using-wpf-wic-instead-of-gdi.aspx

http://weblogs.asp.net/bleroy/archive/2010/01/21/server-side-resizing-with-wpf-now-with-jpg.aspx

http://weblogs.asp.net/bleroy/archive/2010/05/03/the-fastest-way-to-resize-images-from-asp-net-and-it-s-more-supported-ish.aspx

 

Happy Debugging Smile


PayPal — The safer, easier way to pay online. Has this post helped you? Saved you? If you'd like to show your appreciation. Please buy me a coffee or make a small contribution toward blog's maintenance(to keep it Ads free )

Tags: , , , ,

.Net | Debugging | GC

Comments (6) -

Y8
Y8 United States
4/6/2012 4:27:54 AM #

It’s really a great and helpful piece of info. I am glad that you shared this helpful info with us. Please keep us informed like this. Thanks for sharing.

http://www.y8ing.com/

Reply

.Net Training Indore
.Net Training Indore United States
8/6/2012 4:04:05 AM #

Your articles always have a lot of really up to date information. http://www.infocentroid.com/

Reply

Steve Insley
Steve Insley United Kingdom
2/12/2013 5:55:36 PM #

So, the crux of the matter is that if you code poorly, you'll get memory leaks.  That shouldn't be a surprise.  I would think that anyone using System.Drawing.* would know that you need to call Dispose() or use "using" blocks.  If you do, there are no issues with the deadlock you diagnosed.

Moral:  Clean up your memory folks.

Reply

Jas
Jas United States
2/14/2013 7:01:53 AM #

Sure Steve.

Thats what we want to educate people. This dump analysis was from real life application.
People do make these kind of mistakes.

Reply

Steve Insley
Steve Insley United Kingdom
2/14/2013 7:56:04 AM #

@Jas:  Yeah, absolutely.  Sorry, after re-reading my comment it seems a bit like I'm just saying "Duh!"

In fact, I came to the article while trying to solve the question of whether or not using System.Drawing in ASP.NET is *BAD* or not, not whether it is *supported* or not.  The short answer is - of course - that it isn't bad at all, IF you clear up after yourself properly.  I realise now that not everyone reading this article would know this, despite it being obvious to me.

In fact, I was looking into using WIC instead of the GDI stuff in System.Drawing, but interestingly, WIC isn't officially supported in ASP.NET either, despite the fact that MS' own sites like Bing and MSN use it extensively.  An article from the person that added the dire warning at the top of your post to the documentation said something like "we didn't test this scenario, so we added the warning rather than do the testing".  It's a "get out of jail free" card for MS when it comes to supporting a .NET package that has potential issues if used incorrectly.

I guess the general rule is - just because a package isn't supported, it doesn't mean you shouldn't use it.  It *does* mean that you need to pay attention and be careful what you're doing because MS are telling you "if you get this wrong, we're not going to support you for free".

Good article - it goes into a lot more detail than any other I've seen.

Regards.

Reply

Jas
Jas United States
2/14/2013 8:15:34 AM #

Thanks Steve.

Sure, if you would ever run into memory leak kind of problem. I would be happy to look into dumps for you : Free of Charge as a good gesture.

Reply

Pingbacks and trackbacks (1)+

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

About me

Hi there,

My name is  Jas and I'm currently working with Microsoft IIS/ASP.Net Escalation services.  Services

 

Tag cloud

Month List

RecentComments

Comment RSS

TextBox

 

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.