views:

944

answers:

4

I have an ASP.NET application which tracks statistics by creating and writing to custom performance counters. Occasionally, I see in the error logs that indicate that the counters have failed to open because they had already been used in the current process. I presume this is due to my .NET appdomain having been reset within the same w3wp.exe process. How can I avoid these errors and re-establish a connection to my performance counters when my app domain has been recycled?

Counter construction:

PerformanceCounter pc = new PerformanceCounter();
pc.CategoryName = category_name;
pc.CounterName = counter_name;
pc.ReadOnly = false;
pc.InstanceLifetime =
PerformanceCounterInstanceLifetime.Process;
pc.InstanceName = instance_name;

Counter usage:

pc.Increment()

[Update on 3/26/09] The error message received is:

Instance '_lm_w3svc_1_root_myapp' already exists with a lifetime of Process. It cannot be recreated or reused until it has been removed or until the process using it has exited. already exists with a lifetime of Process.

I tried to replicate the exception in a console application by initializing the performance counters and writing to one of them in a transient AppDomain. I then unload the AppDomain and do it again in a second Appdomain (same process). They both succeed. I'm unsure now exactly what the cause of this is, my assumption about AppDomain recycling in ASP.NET seems to be false.

A: 

I am no expert with custom counters, but based on the info you provided, I think it is worth a shot considering the possibility of some code trying to use the counters when the add domain is about to be recycled. Look for the use of the counter in anything related to dispose or destructor.

eglasius
Ok, what can I do about this? Is there any way to detect that the app domain is in the process of recycling and lock out perf counter usage? I don't think it's realistic to track down every piece of code that triggers a counter write and make sure it can't be called when a recycle pending.
jlew
@Jeremy try getting/looking the stack trace - also post it when you have it
eglasius
A: 

If you are creating your performance counters lazily, it might be a threading issue. After a process recycle, if two page hits occur at the same time (which wouldn't surprise me really) then your performance creation call could run multiple times. On one hand you can safely ignore this error. But if you want to elminate it, I suggest you wrap your performance counter code generation wit a lock statement such as

lock (this.lockObject)
{
 //Create performance counter
}
Ender
I wrote some test code that repeatedly creates and uses the exact same counter and instance name from 100 simultaneous threads, without hitting this condition. So far, I can't demonstrate that this is improper.
jlew
+2  A: 

IIRC, IIS will not make sure that your first AppDomain is closed before it starts the second, particularly when you are recyclying it manually or automatically. I believe that when a recycle is initiated, the second AppDomain is instantiated first, and once that succeeds, new incoming requests are directed towards it, and then IIS waits for the first AppDomain (the one being shut down) to finish process any requests it has.

The upshot is that there's an overlap where two AppDomains are in existence, both with the same value for instance_name.

However, not all is solved. I have corrected this problem in my code by including the process ID as part of the instance name. But it seems to have introduced another problem -- what I thought was a process-scoped performance counter never seems to go away without rebooting the computer. (That may be a bug on my part, so YMMV).

This is the routine I have for creating an instance name:

    private static string GetFriendlyInstanceName()
    {
        string friendlyName = AppDomain.CurrentDomain.FriendlyName;
        int dashPosition = friendlyName.IndexOf('-');
        if (dashPosition > 0)
        {
            friendlyName = friendlyName.Substring(0, dashPosition);
        }
        friendlyName = friendlyName.TrimStart('_');
        string processID = Process.GetCurrentProcess().Id.ToString();
        string processName = Process.GetCurrentProcess().ProcessName;
        string instanceName = processName + " " + processID + " " + friendlyName.Replace('/', '_').Trim('_').Trim();
        return instanceName;
    }
Alan McBee
Interesting, I will give that a shot. The problem you mention having introduced at the end, how did you notice that and what are the implications? Is it a resource leak, or does it cause other bad behavior?
jlew
Resource leak. The problem I had is that eventually you run out of slots for instances. They disappear when you reboot the computer. It's probably simply because I was failing to call RemoveInstance() when the process ended. I think in my situation, I'm dealing with a process that is getting killed rather than cleanly shut down.
Alan McBee
I'm pretty confident that I resolved my issue. I'm making sure that I call the RemoveInstance() method in the Dispose handler for the objects I have that wrap performance counters.
Alan McBee
A: 

I had a similar issue: Multi-Instance, Process LifeTime counters couldn't be created more than once within Visual Studio, but it was due to the fact that I had PerfMon open!

Took me a while to realise that.

Khash