Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Need to beat the GC and have object destroyed once it goes out of scope

I have several sections of code that I need to protect with a Mutex. The problem is that the code looks something like this:

lock(mylockobject) {
  if(!foo())
    throw new MyException("foo failed");
  if(!bar())
    throw new MyException("bar failed");
}

Using lock, it works as I'd like, but now I need to use a mutex. The obvious problem here is that if I acquire the mutex and foo() or bar() fails, I would have to explicity release the mutex before throwing each exception.

In C++, I would take advantage of the scope of an object created on the stack, and would lock the mutex in the object's constructor, and then release it in the destructor. With .NET's garbage collection, I didn't think this would work. I wrote a test app and confirmed that if I do something like this:

public class AutoMutex
{
  private Mutex _mutex;
  public AutoMutex(Mutex mutex)
  {
     _mutex = mutex;
     _mutex.WaitOne();
  }

  ~AutoMutex()
  {
    _mutex.ReleaseMutex();
  }
}

and then have code like this:

// some code here...
Mutex my_mutex = new Mutex(false, "MyMutex");
{ // scoping like I would do in C++
  AutoMutex test = new AutoMutex(my_mutex);
  test = null;
}

The destructor (finalizer?) doesn't get called until much later.

Google hasn't yet pointed me in the right direction, but I'm still working on it... Please let me know how you might solve this little problem, if it's even possible.

like image 246
Dave Avatar asked Nov 26 '22 21:11

Dave


2 Answers

Couple points.

1) The thing you want to search for is "the disposable pattern". Be very careful to implement it correctly. Of course, Mutex already implements the disposable pattern, so its not clear to me why you'd want to make your own, but still, it's good to learn about.

See this question for some additional thoughts on whether it is wise to use the disposable pattern as though it were RAII:

Is it abusive to use IDisposable and "using" as a means for getting "scoped behavior" for exception safety?

2) Try-finally also has the semantics you want. Of course a "using" block is just a syntactic sugar for try-finally.

3) Are you sure you want to release the mutex when something throws? Are you sure you want to throw inside a protected region?

This is a bad code smell for the following reason.

Why do you have a mutex in the first place? Usually because the pattern goes like this:

  • state is consistent but stale
  • lock access to the state
  • make the state inconsistent
  • make the state consistent
  • unlock access to the state
  • state is now consistent and fresh

Consider what happens when you throw an exception before "make the state consistent". You unlock access to the state, which is now inconsistent and stale.

It might be a better idea to keep the lock. Yes, that means risking deadlocks, but at least your program isn't operating on garbage, stale, inconsistent state.

It is a horrid, horrid thing to throw an exception from inside a lock-protected region and you should avoid doing so whenever possible. An exception thrown from inside a lock makes you have to choose between two awful things: either you get deadlocks, or you get crazy crashes and unreproducible behaviour when your program manipulates inconsistent state.

The pattern you really ought to be implementing is:

  • state is consistent but stale
  • lock access to the state
  • make the state inconsistent
  • make the state consistent
  • if an exception occurs, roll back to the stale, consistent state
  • unlock access to the state
  • state is now consistent and, if there was no exception, fresh

That's the much safer alternative, but writing code that does transactions like that is tough. No one said multithreading was easy.

like image 111
Eric Lippert Avatar answered Dec 05 '22 07:12

Eric Lippert


In order to provide scoping, you can make your AutoMutex implement IDisposable and use it like this:

using(new AutoMutex(.....))
{
  if(!foo())
    throw new MyException("foo failed");
  if(!bar())
    throw new MyException("bar failed");
}    

In your implementation of IDisposable.Dispose(), release the mutex.

like image 32
Marcel Gosselin Avatar answered Dec 05 '22 09:12

Marcel Gosselin