Bug 17593 - WeakReference tracks dead object
Summary: WeakReference tracks dead object
Status: RESOLVED ANSWERED
Alias: None
Product: Runtime
Classification: Mono
Component: GC ()
Version: unspecified
Hardware: PC Mac OS
: --- normal
Target Milestone: ---
Assignee: Rodrigo Kumpera
URL:
Depends on:
Blocks:
 
Reported: 2014-02-04 07:29 UTC by Marek Safar
Modified: 2016-04-14 05:51 UTC (History)
5 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on GitHub or Developer Community with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED ANSWERED

Description Marek Safar 2014-02-04 07:29:31 UTC
using System;

public class C
{
}

class X
{
	static WeakReference  wr;

	public static void Main ()
	{
		GC.Collect();
		GC.WaitForPendingFinalizers();
		Allocate ();
		GC.Collect();
		GC.WaitForPendingFinalizers();

		Console.WriteLine (wr.IsAlive);
		return;
	}

	static void Allocate ()
	{
		wr = new WeakReference (new C (), false);
	}
}

Mono: True
.NET: False
Comment 1 Paolo Molaro 2014-02-04 09:45:03 UTC
This is the usual case of conservatively scanning the stack and/or not dealing with dead local vars, so some objects may be kept alive, seemingly at random.
As an example, with two differently compiled mono binaries, in one I get False, in the other I get True.
Comment 2 Jonathan Pryor 2014-02-04 10:25:54 UTC
Because of the conservative stack scanning, when I last chatted with Kumpera about this he recommended creating the WeakReference from a different thread, e.g. changing Allocate() to:

  static void Allocate ()
  {
    var t = new Thread (() => wr = new WeakReference (new C (), false));
    t.Start ();
    t.Join ();
  }

This should make the test more reliable, as the 'C' instance won't have been allocated on the main thread, and the thread that did allocate is gone, so the conservative stack walking shouldn't be an issue.
Comment 3 Rodrigo Kumpera 2014-02-17 12:49:53 UTC
This must work with precise stack scanning.
Comment 4 peter 2015-06-18 04:37:19 UTC
Will this be fixed at some point? We are encountering this problem in tests of real production code.

I have tried enabling precise stack scanning, but either i am doing it wrong, or it doesn't help. I am setting MONO_GC_PARAMS as stack-mark=precise in monodevelop.

Below are some of the tests i have been running to understand the problem. All of them pass in VS2012/4.5, the comments indicate which ones fail running in monodevelop (compiled as Release).

--------------------------------------

using NUnit.Framework;
using System;
using System.Threading;

namespace MonoTest
{
  [TestFixture ()]
  public class GarbageCollectionTest
  {

    public class Wrapper{
      private WeakReference _ref;
      public Wrapper(){
        _ref = new WeakReference(new object());
      }

      public Wrapper( object obj ){
        _ref = new WeakReference( obj );
      }

      public object Object{get{ return _ref.Target;}}
      public object IsAlive{get{ return _ref.IsAlive;}}
    }

    /// <summary>
    /// This passes.
    /// </summary>
    [Test ()]
    public void WrappedWeakReference ()
    {
      Wrapper d = new Wrapper ();
      Assert.That (d.IsAlive, Is.True);
      GC.Collect ();
      Assert.That (d.IsAlive, Is.False);
    }

    /// <summary>
    /// This fails even with 
    /// MONO_GC_PARAMS set as stack-mark=precise
    /// </summary>
    [Test ()]
    public void WrappedFromLocalObject ()
    {
      var obj = new object ();
      Wrapper d = new Wrapper (obj);
      Assert.That (d.IsAlive, Is.True);
      obj = null;
      GC.Collect ();
      Assert.That (d.IsAlive, Is.False);
    }
      
    /// <summary>
    /// This fails even with 
    /// MONO_GC_PARAMS set as stack-mark=precise
    /// </summary>
    [Test ()]
    public void LocalWeakReference ()
    {
      var r = new WeakReference(new object());
      Assert.That (r.IsAlive, Is.True);
      GC.Collect ();
      Assert.That (r.IsAlive, Is.False);
    }

    /// <summary>
    /// This fails even with 
    /// MONO_GC_PARAMS set as stack-mark=precise
    /// </summary>
    [Test ()]
    public void LocalRefFromOtherThreadObject ()
    {
      var r = new WeakReference (CreateObjectOnThread ());
      Assert.That (r.IsAlive, Is.True);
      GC.Collect ();
      Assert.That (r.IsAlive, Is.False);
    }

    /// <summary>
    /// Passes.
    /// </summary>
    [Test ()]
    public void OtherThreadRef ()
    {
      var r = CreateWeakRefOnThread();
      Assert.That (r.IsAlive, Is.True);
      GC.Collect ();
      Assert.That (r.IsAlive, Is.False);
    }

    public object CreateObjectOnThread(){
      object obj = null;

      var t = new Thread (() => obj = new WeakReference (new object (), false));
      t.Start ();
      t.Join ();
      return obj;
    }

    public WeakReference CreateWeakRefOnThread(){
      WeakReference obj = null;

      var t = new Thread (() => obj = new WeakReference (new object (), false));
      t.Start ();
      t.Join ();
      return obj;
    }
  }
}
Comment 5 Rodrigo Kumpera 2015-06-19 02:47:07 UTC
You tests verify the same conditions, creating the object on a different thread doesn't address it if you manipulate it in a thread where it will be live.

In LocalRefFromOtherThreadObject, you're manipulating the object from the other thread on the current thread, which voids the usefulness of that. It just happens that you're using a pair of WeakReference objects, which is not relevant to this test.

Addressing this issue is our roadmap, though I can't when will it be fixed.
Comment 6 peter 2015-06-19 03:00:03 UTC
I realize that i am probably testing the same scenarios, but based on previous comments here, i got the impression that the precise stack scan should have solved the problem? As I mention above, this does not appear to be the case (or i failed to set it up correctly).

Is there anywhere where i can find detailed information about this GC behavior? Based on what i have read so far, i was surprised that the WrappedWeakReference passed, for example.

I thought that i might try to work around this issue by restructuring our code, but in order to do so i have to understand the behavior better( i am not to eager to allocate them in a separate thread though).

Thank you for the info regarding the roadmap, though i am sorry to hear that there isn´t any plan on fixing it.
Comment 7 Rodrigo Kumpera 2015-06-19 18:39:53 UTC
Precise stack scanning currently can't be enabled, so it's no surprise it doesn't change the behavior.

What it means is that it's not possible to predict when a value on the stack will be considered dead and when it won't.
That's the gist of it.
Comment 8 Rodrigo Kumpera 2016-04-14 05:51:54 UTC
Closing as the issue was explained.