1 Filter Instance Per Application Pool in IIS 6.0?

Dec 10, 2007 at 2:46 AM
Hi Tiago,

First, thanks for taking the time to create this module. It works great and is very easy to use. In my scenario, I use a Filter module to look up user application access information in an LDAP store and inject the access information into the HTTP headers for other web applications to access. I keep a cache in memory with my module so I only have to do this look up once. On my website I have three application pools, and from what I can tell, IIS (or maybe the Filter.NET ISAPI filter??) is creating one instance of my filter module per application pool. While it's not a huge problem, it does cause some inconsistencies in my cache, because a user could be cached in one app pool and visit a part of my website run under another app pool and not be cached there. Is there anyway to make it so there is only ever one instance of my filter module created? Thanks!

Jeff
Coordinator
Dec 10, 2007 at 10:24 AM
Hi Jeff,

thanks for the support, and I'm glad you find it useful.

Your current issue is by design and not specific to Filter.NET. Every ISAPI Filter is a native windows DLL and as such runs inside a process which, in IIS dialect, is an AppPool. Every AppPool takes a copy of the dll and runs it inside its address space and private heap(s) and that is the main reason why there is no data sharing between both AppPools. The cache in memory you're creating lives inside the process (AppPool) it was created in. Any other AppPool will have its own private cache only visible to itself.

Best way going forward is to let Filter.NET work as-is, because each AppPool will have its own cache and will be updated as the http requests flow to it. The caches will not share any data since they are in-process. Care must be taken for cache size since, in your design, each process (AppPool) will have its own in-memory cache, but if the data is small I would not worry too much. Just keep an eye on how much it grows and the number of expected users.

Finally, if you want to share the data of all caches you can go the database route instead of in-memory, but that would defeat the whole purpose of what you're doing, right? :)

Hope it helps
Tiago Halm
Dec 12, 2007 at 2:40 PM
Thanks for the reply, Tiago. I figured as much, but I wanted to get an authorative response on my question.

Here's another question for you. When I run a stress test on my website that users my Filter.Net module, the stress test hits the site with about 70 requests per second, for about 20 minutes. While response time is great, I noticed that the w3p.exe process for two of my app pools grow to almost 400MB of memory. My cache that I maintain with my filter.net module only has two objects in it, so that's not the problem. Once the test ends, the worker processes don't release any memory. It stays at 400MB. Does IIS grab memory and just hold on to it, thinking it might need it later (sort of like SQL Server)? The memory footprint doesn't decrease until I reset IIS.

Thanks,

Jeff
Coordinator
Dec 12, 2007 at 5:55 PM
Jeff,

That is a good question. My memory stress tests were not as extensive as yours, but you might want to try any of these steps:

  • Remove Filter.NET and redo the stress test. Check if the memory grows the same way.
  • After stress testing with Filter.NET (and get those 400MB), do an iisdump.exe. Should be interesting to look into the dump via WinDbg + SOS and see which objects take more memory.

On another note, check which type of objects you set in your session cache. I wonder if those objects, for any reason, do not get collected by GC (something visible while inspecting the dump via WinDbg). However, if this was the case, you would get neverending memory growth during your stress tests and not stop at 400MB. Did the memory growth stop at 400MB because you stopped the test?

Do remember that ASP.NET does grow memory (if large objects are allocated) and that memory stays allocated because its more efficient to allocate native memory once and manage it inside the CLR. Its less costly ...

Let me know what you get.

Tiago Halm
Dec 16, 2007 at 7:54 PM
Edited Dec 16, 2007 at 7:55 PM
Hi Tiago,

I have done some investigating using WinDbg. I did find a memory leak in my code because I was allocating an object on every request that I wasn't using. I fixed that, but that was only responsible for a small amount of memory. Less than 1k. Memory growth only stops because my stress test stops. If I restart the test, it keeps growing. The only way memory ever gets released is if I recycle the app pool or reset IIS. Unfortunately I can't really run the web stress test without Filter.Net or my website will not function.

Using WinDbg I also got the memory allocation for the KodeIT namespace. I copied it below. This is after the test has completed.

Statistics:
MT Count TotalSize Class Name
01ed3664 1 12 KodeIT.Web.Hosting.ProxyInstance
0181334c 1 12 KodeIT.Web.Hosting.ProxySetup
01ed4368 1 24 System.Collections.Generic.List`1[KodeIT.Web.InstanceEvents, KodeIT.Web]
01ed84a0 1 32 System.EventHandler`1[KodeIT.Web.ContextEventArgs, KodeIT.Web]
01ed82dc 1 32 System.EventHandler`1[KodeIT.Web.PreProcHeadersEventArgs, KodeIT.Web]
01ed61a8 3 36 KodeIT.Web.Configuration.ErrorDetail
01ed4104 1 56 KodeIT.Web.InstanceEvents
01ed596c 1 60 KodeIT.Web.Configuration.HttpFilterElement
01ed5664 1 64 KodeIT.Web.Configuration.HttpFiltersSection
01ed83d4 8 96 KodeIT.Web.ContextEventArgs
01ed57c4 1 96 KodeIT.Web.Configuration.HttpFilterCollection
01ed7fc4 8 224 KodeIT.Web.ContextEvent
01ed9554 8 416 KodeIT.Web.FilterSession
01ed7e6c 66 792 KodeIT.Web.PreProcHeadersEventArgs
01ed98c4 74 1184 KodeIT.Web.ServerVariables
01ed97dc 66 1320 KodeIT.Web.HttpHeaders
01ed96ec 74 1776 KodeIT.Web.NativeContext
01ed81d4 66 2376 KodeIT.Web.RequestHeadersEvent
Total 382 objects

I'm assuming the size field is in bytes (I'm not too familiar with WinDbg). So the biggest chunk of memory there is only 2.3k, which is insignificant. Basically what I am doing is listening on the OnPreProcHeaders event, and then injecting application access information into the header, either by querying an LDAP source (first time) or my in-memory cache (any time after that). Is the above heap dump for the KodeIT namespace what you would expect?

If you are familiar with WinDbg, could you tell me how to inspect the unmanaged memory space for the Filter.Net ISAPI filter? I have a memory dump saved that I can look at.

Thanks for the help.

Jeff
Coordinator
Dec 18, 2007 at 1:14 PM
Hey Jeff,

Its a good initial analysis and the best way forward is to understand where the issue may be, if the Filter.NET native code, the Filter.NET managed code or the Web Application managed code. I also noticed you said before that only a couple of AppPools grow in memory. How many AppPools do you have? If you only experience this issue in 2 of them, how about the other AppPools? I assume Filter.NET (if installed global scope) is also running on those other AppPools, although they do not grow in memory.

Next steps should be look into native memory leaks and managed memory leaks. Some useful links I found:


On another note, would it be possible to get a basic sample of your code (without specifics or proprietary info) verify that the memory leak still occurs and send it to me? I'd like to perform some tests and be able to look more closely on the issue.

Let me know of any outcome.

cheers
Tiago Halm
Jan 2, 2008 at 12:43 PM
Hi Tiago,

I apologize for the lateness of my reply. Busy holiday season and all that. Anyway, I used some of the articles you referenced above and I found that the increase in memory was due to a lot of managed objects being allocated in my filter, and then surviving garbage collections. So I changed the way I was creating objects, and ran my stress test. It still took up a lot of memory (about 200MB), but the important part was after I ran the test, I ran it again and no more memory was allocated for the second test. It stayed around 200MB. So it looks like IIS was re-using memory that it previously allocated. So I think all is well now. Thanks for your help!

Jeff