On Wed, 22 Oct 2008 07:31:16 -0700, Ben Voigt [C++ MVP]
<rb*@nospam.nos pamwrote:
What if you instead saved the IAsyncResult returned by BeginConnect into
a
static variable? Perhaps that is what is being collected (though
presumably
the same one gets passed to the completion routine)?
Nope. Not that it should have made a difference anyway, but I went ahead
and tried that and it had no effect.
[...]
>So, at the very least, for this code example, it seems to me that the
GC is prematurely collecting the objects. If your real-world
scenario also keeps the objects in static members, then the same bug
would be affecting you.
I think the BCL should be keeping these objects alive completely
independent
of the user's references from static members. After all, the only living
reference could be in the State parameter to BeginConnect.
I _think_ I agree. But, keep in mind that the framework only has to keep
a reference to the Socket instance as long as the asynchronous operation
hasn't completed. And there's no _requirement_ that the calling code pass
the Socket instance, directly or indirectly, as part of the call to
BeginConnect() (i.e. via the "state" argument).
So the only reference might be the one implicit in the asynchronous
operation itself, and of course once the callback has called EndConnect(),
with no more uses of the IAsyncResult argument to the callback,
technically any data referenced by that object is no longer reachable.
In other words, it's a variation on the "you need to call GC.KeepAlive()"
problem, in that the JIT compiler has detected that the variable is
actually unused past a certain point in the method, and so can be
collected.
Now, in this particular case, that _shouldn't_ apply. Not only is the
reference stored in a static variable, it's continued to be referenced in
the method. First directly by being passed to the NetworkStream
constructor, and then indirectly by the NetworkStream itself.
But note that in either case, we're relying on the static variable. The
"_socket" static variable _ought_ to be sufficient here, but if there's a
bug that causes it not to be, there's no reason to think that bug won't
also affect the "_stream" variable. If the JIT compiler is deciding that
these static variables are unreachable for some reason, then once the
"_stream" variable has been assigned, that's the last use in the method,
and the bug might (appears to) lead to the GC collecting the objects.
>Of course, if in your real-world scenario, these objects wind up
referenced by instance members that are in a class instance that
itself becomes unreachable due to the use of the thread, then _that_
would be expected behavior. The fix there would be, of course, to
keep a reference to the instance storing the socket and stream
instances, so that they remain reachable and uncollected.
In that case, presumably the completion routine would be a instance
method,
so the AsyncCallback delegate passed to BeginConnect would reference the
instance and (should) keep it alive.
That depends. I agree that if the completion routine were an instance
method, the fact that the code would be executing in the instance method
should be sufficient to keep the object from being collected, as long as
instance members continued to be used in the execution path (for example,
calling an instance method named "Send()" and in that method using an
instance member field named "_stream").
But there's no requirement that the callback method be an instance method,
and I suppose one could create code in which there's a bug where the
instance containing the references to the objects winds up unreachable.
How that could would ever be expected to work, I don't know. In that
hypothetical case, without a reference to the instance, it's difficult to
see how the non-instance Send() method would be able to expect to get at
the _stream instance field. I may be writing about a hypothetical
scenario that is a practical impossibility. :)
In any case, it's probably more useful to talk about the case at hand, in
which objects being referenced by static member fields are somehow being
garbage collected, even though they ought not to be. I don't have a good
answer for that, thus my belief that this is a bug in the framework
somewhere (probably the JIT compiler, but I have so little experience
debugging .NET at that level, I can't say for sure).
For me, the bottom line here is that the code that was posted looks like
it _should_ work fine. I am in fact able to get it to work fine by
keeping the Thread alive long enough for the operation to complete, which
suggests that something about the termination of the Thread is causing the
objects to become invalid.
Note that I don't even know for sure that this is a GC issue. That's just
my supposition, based on the behavior. It _looks_ like a GC issue. But
frankly, the lifetime of a Thread shouldn't really be affecting the
lifetime of objects not specifically tied to that Thread anyway. So if
the static variables are being collected, not only is that a bug, but the
fact that it's being done so just because a Thread exited seems also to be
a bug to me.
It's very odd.
Pete