Unexpected release of named mutex

  • Thread starter Thread starter carl.clawson
  • Start date Start date
C

carl.clawson

I have a mutex that releases while I am still holding a reference to
it! Maybe this is "garbage collection 101" and this old C++ programmer
just needs to get used to it...

Here's a boiled-down snippet of VB I used in order to implement a
single-instance app. (I can't use the application framework's single-
instance feature because it chokes on my form.)

Sub Main
Dim goodToGo As Boolean = False
Dim singleInstance As Threading.Mutex = Nothing
Try
singleInstance = New Threading.Mutex(True,
Application.ProductName & ".SingleInstance", goodToGo)
If Not goodToGo Then
' (find the existing instance and activate it)
End
End If
MainForm.ShowDialog()
Catch ex As Exception
' (log errors)
Finally
' doesn't work if you remove the next line
If goodToGo Then singleInstance.ReleaseMutex()
End Try
End Sub

This works, but if I take out the call to ReleaseMutex in the Finally
block I can them start as many instances as I want. The mutex releases
all by itself shortly after being acquired. This was highly
unexpected.

Silly me, thinking that I could keep a reference to an object by, uh,
keeping a reference to an object.

It appears that the garbage collector is outsmarting me by realizing
that the mutex object is never again being referenced and killing it.

I couldn't demonstrate this with a small test program. This is a
moderate sized app. A couple dozen forms, maybe two hundred source
files total.

(And yes, I know it's bad practice to not explicitly release a mutex
but one normally assumes that program exit cleans stuff like that up.)

Any other interpretations of what's going on here?

Thank you and be warned.
-- Carl
 
I have a mutex that releases while I am still holding a reference to
it!

You're not holding a reference to it though. You have a variable which
isn't used any more, and the garbage collector no longer considers that
to be a root reference.

Silly me, thinking that I could keep a reference to an object by, uh,
keeping a reference to an object.

It appears that the garbage collector is outsmarting me by realizing
that the mutex object is never again being referenced and killing it.

Exactly.

This is precisely what GC.KeepAlive is for :)
I couldn't demonstrate this with a small test program.

Were you trying it in the debugger by any chance? That has slightly
different semantics. It's easy enough to show in a test app otherwise.
(I don't have time this minute, but I can produce one later on if you
want.)
 
I have a mutex that releases while I am still holding a reference to
it! Maybe this is "garbage collection 101" and this old C++ programmer
just needs to get used to it...

<snip>

Oh, and for a scarier version of this: an object can be garbage
collected *while a method is still running "inside" it* - if the GC can
prove it's not going to look at any variables any more.

Can have nasty effects in some weird cases.
 
This is precisely what GC.KeepAlive is for :)

...

Were you trying it in the debugger by any chance? That has slightly
different semantics. It's easy enough to show in a test app otherwise.
(I don't have time this minute, but I can produce one later on if you
want.)

Thanks Jon. I've only been programming .NET for 4 years so I still
have a lot to learn.

This was not done in the debugger. I did a little test app and checked
it outside the debugger. Actually, it's not very long so here it is:

Sub main()
Dim isGood As Boolean
Dim singleRun As New Threading.Mutex(True, "zzSingleRun",
isGood)
If isGood Then
Dim ret As DialogResult = Form1.ShowDialog
Else
'(find existing instance and activate)
End If
End Sub

I even put a button on my form to call GC.Collect and it still works
no matter how hard I push the button. (Now `fess up: GC.Collect
doesn't really do anything, does it? Like those Door Close buttons on
elevators, isn't it?) The only difference I see besides the size of
the app is the lack of the try/catch block.

-- Carl
 
[...]
I even put a button on my form to call GC.Collect and it still works
no matter how hard I push the button. (Now `fess up: GC.Collect
doesn't really do anything, does it? Like those Door Close buttons on
elevators, isn't it?) The only difference I see besides the size of
the app is the lack of the try/catch block.

GC.Collect() only immediately releases objects that don't have
finalizers. An object like Mutex has a finalizer that needs to be run
before the object can actually be collected. In this case, the object
won't actually be released until the finalizer thread runs. You can wait
for this to happen by calling GC.WaitForPendingFinalizers() after you call
GC.Collect().

It's _possible_ that there's something different about the simpler example
that causes the variable to be treated as still in use, but I think it's
more likely that the finalizer just hasn't run yet. It's not only tricky
sometimes to ensure that your objects aren't collected when you don't want
them to be, it can sometimes be tricky to ensure that your objects _are_
collected when you _do_ want them to be.

In any case, the fix that Jon explained is in fact what you need to do in
your original example, assuming you don't just want to release the Mutex
instance explicitly later (why that might be isn't clear to me though).
You can put a call to GC.KeepAlive() at the end of the Main method to
ensure that it won't be collected prematurel.y

Pete
 
Thanks Jon. I've only been programming .NET for 4 years so I still
have a lot to learn.

This was not done in the debugger. I did a little test app and checked
it outside the debugger. Actually, it's not very long so here it is:

Sub main()
Dim isGood As Boolean
Dim singleRun As New Threading.Mutex(True, "zzSingleRun",
isGood)
If isGood Then
Dim ret As DialogResult = Form1.ShowDialog
Else
'(find existing instance and activate)
End If
End Sub

I even put a button on my form to call GC.Collect and it still works
no matter how hard I push the button. (Now `fess up: GC.Collect
doesn't really do anything, does it? Like those Door Close buttons on
elevators, isn't it?) The only difference I see besides the size of
the app is the lack of the try/catch block.

I suspect if you also had GC.WaitForPendingFinalizers in there, you'd
see it.

Here's my sample app - no need for multiple processes, mutexes etc.

using System;

class Foo
{
~Foo()
{
Console.WriteLine("Foo finalizer called");
}
}

class Test
{
static void Main()
{
Foo f = new Foo();
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine ("End of Main");
}
}


The output is:
Foo finalizer called
End of Main

This shows that the instance of "Foo" is being finalized before the end
of Main.
 
...
Here's my sample app - no need for multiple processes, mutexes etc.
...
--
Jon Skeet - <[email protected]>http://www.pobox.com/~skeet  Blog:http://www.msmvps.com/jon.skeet
World class .NET training in the UK:http://iterativetraining.co.uk- Hide quoted text -

- Show quoted text -

Thanks Jon (& Peter) for the help. I confirmed your example code and
also that my test app behaves the same as my real app if I call
WaitForPendingFinalizers. So I'll just stick that ReleaseMutex at the
end and be happy.

-- Carl
 
Back
Top