1

My team is working on a Unity project in C#. In some places we call System.GC.Collect() and System.GC.WaitForPendingFinalizers() (only in tests, not in production code. Why we do that is irrelevant to my question, but explained below in case someone is interested). This used to work just fine on Unity 2018.1, but seems to have stopped working consistently (i.e. it seems to sometimes work, but not always) ever since we upgraded to Unity 2019.1. We're using Mono on macOS, and have tried with the newly introduced feature of incremental GC both on and off, without noticeable difference.

Does anyone know of anything which changed between those Unity versions which would explain this? And does anyone know of some other way to force garbage collection and wait for pending finalizers which does work on Unity 2019?

Things I tried to no avail: calling GC.Collect() and GC.WaitForPendingFinalizers() multiple times, sleeping for long amounts of time after calling them, allocating large amounts of memory (not large enough, I guess, but then again I also didn't want to push it).

Note: we call Collect() and WaitForPendingFinalizers() in succession; I don't know which one of the two is the one not actually doing what is expected of it (or whether it's both), because only both combined have the observable effect we're testing for. Ideas that would help in narrowing down the problem will also be appreciated.

Why we need this

We have a class whose instances may, depending on circumstances, be long-lived, and at some point in their life cycle store large amounts of data, which they later on no longer need. In order to allow for the memory to be freed, when the data is no longer needed the field which references it is assigned null.

I have no reason to believe this isn't working properly, but the problem is with the unit test for this functionality, which used to work just fine on Unity 2018.1, but has been failing intermittently ever since we migrated our project to Unity 2019.1.

Sample code

The following is a (much simplified!) example which demonstrates the issue: the test consistently passes on Unity 2018.1 but not on Unity 2019.1. I tried to make it as minimal as possible. I don't know why, but I couldn't make it work without using a List (a similar version with a single object instead of a List<object> passed consistently also on Unity 2019.1) - this may or may not be part of the issue.

using System;
using System.Collections.Generic;

using NUnit.Framework;

public class MyClass {
    List<object> someData = new List<object>();

    public void RegisterData(object data) => someData.Add(data);

    public void ForgetData() => someData = null;
}

[TestFixture]
public class MyClassTest {
    class ObservablyFinalizable {
        readonly Action onFinalize;

        public ObservablyFinalizable(Action onFinalize) {
            this.onFinalize = onFinalize;
        }

        ~ObservablyFinalizable() {
            onFinalize();
        }
    }

    MyClass instance;
    bool isFinalized;

    [SetUp]
    public void SetUp() {
        instance = new MyClass();
        isFinalized = false;
    }

    [Test]
    public void ShouldForgetData() {
        WhenRegisteringObservablyFinalizableData();
        WhenForgettingData();
        WhenGarbageCollected();

        ThenFinalized();
    }

    void WhenRegisteringObservablyFinalizableData() =>
        instance.RegisterData(new ObservablyFinalizable(() => isFinalized = true));

    void WhenForgettingData() => instance.ForgetData();

    static void WhenGarbageCollected() {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

    void ThenFinalized() => Assert.That(isFinalized);
}
Tom
  • 4,910
  • 5
  • 33
  • 48
  • Probably related: [Is it correct to use `GC.Collect(); GC.WaitForPendingFinalizers();`?](https://stackoverflow.com/q/12265598/7111561) – derHugo Jul 01 '19 at 06:50
  • Thanks, @derHugo. It doesn't seem related to me: the concern there seems to be performance, whereas my reason for triggering collection is not performance at all: it's to verify the correct behaviour of the code, and I only do it in tests (which seems acceptable practice, see https://ericlippert.com/2015/05/18/when-everything-you-know-is-wrong-part-one/ - _Myth: Calling `GC.Collect()` causes finalizers to run._, for example). Moreover, my question is about the transition from Unity 2018.1 to Unity 2019.1: the given code passes consistently on the former, but fails intermittently on the latter. – Tom Jul 02 '19 at 07:31

0 Answers0