views:

143

answers:

3

I have a web service method in which I create a particular type of object, use it for a few seconds, and then dispose it. Because of problems arising from multiple threads creating and using instances of this class at the same time, I need to restrict the method so that only one caller at a time ever has one of these objects.

To do this, I am creating a private static object:

private static object _lock = new object();

... and then inside the web service method I do this around the critical code:

lock (_lock)
{
    using (DangerousObject dob = new DangerousObject())
    {
        dob.MakeABigMess();
        dob.CleanItUp();
    }
}

I'm not sure this is working, though. Do I have this right? Will this code ensure that only one instance of DangerousObject is instantiated and in use at a time? Or does each caller get their own copy of _lock, rendering my code here laughable?

Update: from a link in Ben Voigt's answer, I think I need to do this instead:

Mutex m = new Mutex(false, @"Global\MyMutex");
m.WaitOne();
try
{
    using (DangerousObject dob = new DangerousObject())
    {
        dob.MakeABigMess();
        dob.CleanItUp();
    }
}
catch (Exception ex)
{
    // what could possibly go wrong?
}
finally
{
    m.ReleaseMutex();
}

This should function pretty much the same as my lock code, but it will instead use a global object (because of the "Global\" prefix in the mutex' name). I hope.

A: 

I would suggest either using a Singleton or a Factory pattern.

Joshua Smith
How about a Factory pattern that generates a Singleton?
MusiGenesis
Or even a Singleton Factory Factory. That way you could just generate a Singleton Factory whenever you need it.
Joshua Smith
That sounds perfect. I know how I could make it thread-safe now. :)
MusiGenesis
You kids behave ;)
Justin Johnson
+1  A: 

You could run into problems where many requests are made at once and client requests start timing out as they are all waiting on the lock. You could also run into issues in a webfarm scenario where multiple servers are handling the requests and so each has its own _lock. I would suggest using a queue instead. If each client comes in and adds a message to some queue, then a separate service can process the messages from the queue, one at a time, and the clients will not have to wait.

Matt Dearing
This was exactly why I didn't want to use a lock here, but potential timeouts were preferable to to rebooting the server twice a day. Ironically, the lock doesn't seem to have fixed my problem.
MusiGenesis
+4  A: 

It depends on the scope of the mess your object makes, of course.

There will be only one instance of _lock in each AppDomain. If your object expects exclusive access to a resource shared between AppDomains, such as a particular file, this might be trouble. At what level should shared access be allowed? By different processes? By different users? Never? That will help us find you a solution.

EDIT: Be afraid, be very afraid, if you're using static member variables to lock machine-global resources.

http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/0e570911-b88e-46be-96eb-a82f737dde5a.mspx?mfr=true

Applies to IIS 7 as well

http://technet.microsoft.com/en-us/library/cc735056(WS.10).aspx

EDIT: For an actual machine-wide lock, have a look at System.Threading.Mutex Use either the LOCAL\ or GLOBAL\ prefix depending on whether you want one mutex per user session or computer-wide.

EDIT: Use a try/finally to make sure the Mutex gets released.

Ben Voigt
Will ASP.Net ever use multiple AppDomains on the same machine for the same application?
SLaks
Did someone say same application? I didn't hear anyone say same application. I'm not that familiar with ASP.NET, but I think the host recycling feature does indeed fire up a new AppDomain or new process every N requests, during which time the old process may still be finishing earlier requests.
Ben Voigt
The scope of the mess is pretty spectacular, although it's not the source of my particular problem. Picture an ODBC.INI file with tens of thousands of entries, all Oracle Lite databases with GUID names. And a corresponding entry for each in the registry. Not to mention a folder filled with hundreds of blank databases (~200K each) each time the out of memory error shows back up.
MusiGenesis
@Ben: if what you say is true, maybe that's my problem. The first request locks the object, the next 19 block waiting for the first to finish, but then the next call gets a new AppDomain and isn't locked out of using the dangerous object.
MusiGenesis
I should mention that I'm trying to achieve absolutely no shared access at all - on a particular server, I want there to only be one instantiated object of this type at a time.
MusiGenesis
I *am* very afraid - I knew I was screwing this up.
MusiGenesis
@Ben: I added the try/catch/finally, but I'm even more afraid now. There are situations where Oracle Lite crashes the thread it's running on, such that the finally block never executes. From my reading on mutex, it seems to be unclear what actually happens to a global mutex like this in that situation, i.e. whether it gets freed up or not. I'm afraid it could essentially lock out all Oracle Lite access until a reboot.
MusiGenesis
@MusiGenesis: If you end up with a total process crash, such that the `finally` block of a thread that has acquired the mutex never executes and releases it, then the global mutex goes into `WAIT_ABANDONED` state. In .NET, any other threads waiting on that mutex will receive an `AbandonedMutexException`.
Aaronaught
@Aaronaught: ai-yi-yi. So I need another try/catch around `mx.WaitOne();`? I assume in the catch block I would try to grab the "Global\MyMutex" mutex again?
MusiGenesis
@MusiGenesis: Actually, what you'll probably need is a try-catch inside a `while` loop, and you'll definitely want to log the failure inside the `catch` (don't try to re-acquire the mutex in there - what if it fails a second time! exceptions inside exception handlers = evil.)
Aaronaught
@Aarotoomanyvowels: I assume you mean a while loop around a try/catch around mx.WaitOne()? Why would I want the loop? I figured the WaitOne line would block until either it gets the mutex or the AbandonedMutexException was thrown.
MusiGenesis
@MusiGenesis: Yes, indeed, `WaitOne` will block until the mutex is either acquired or abandoned - but what if several threads are competing for the mutex and they all keep crashing? You may get the `AbandonedMutexException` several times in a row. I wouldn't tell *everyone* to go to these lengths, but yours is a special case of defensive programming and what you probably want to do is keep attempting to reacquire the mutex until either you're successful or some retry limit is reached (in which case just report failure). So maybe a `for` loop, not a `while` loop, but same idea.
Aaronaught
That, and you may want to use the `Mutex.WaitOne(Int32)` overload which accepts a timeout, so you can report some useful apology to the client if the Mutex isn't available, and to help prevent "forced shutdown" that occurs because you were waiting on a Mutex when the shutdown request came in.
Ben Voigt