Очевидная утечка памяти в AsyncLocal <> - PullRequest
1 голос
/ 22 сентября 2019

У меня есть огромный набор тестов сборки CI, более 3000 тестов.Для параллельного запуска тестов я использую AsyncLocal<T> объекты.Проблема в том, что с тех пор, как я начал это делать, сборочный бот CI завершается примерно на 75%, а затем разбомбляется, потому что ему не хватает памяти.Нет другого виновника, на которого можно было бы указать пальцем, кроме недавно представленных AsyncLocal<T> объектов, которые используются для хранения чего-либо от логических значений до сложных объектов.

Я предполагаю, что каким-то образом злоупотребляю системой, иесть некоторый «правильный» способ использования AsyncLocal<T>, чтобы его значения собирались мусором после завершения текущего контекста.

Может быть полезно знать, что до того, как я обнаружил существование AsyncLocal<T>, я пыталсяреализовать эту функциональность самостоятельно, используя свой собственный класс, называемый Parallelizable<T>.Это работало с использованием TestExecutionContext.CurrentContext.CurrentTest.FullName в NUnit, но было медленным, как патока, и затем я копался в базовом коде и обнаружил, что он использует AsyncLocal<>, поэтому я решил вырезать мою реализацию и изменить Parallelizable<T>, чтобы она была оберткой вокругAsyncLocal<T>, чтобы я мог применить минимальное насилие к моим существующим тестам.И вот тогда я начал выходить из строя памяти.

Как правильно обеспечить, чтобы эти значения были GC'd?

Вот мой код для Parallelizable<T>, FWIW:

    public class Parallelizable<T>
    {
        private readonly AsyncLocal<T> _asyncLocal = new AsyncLocal<T>();
        private readonly AsyncLocal<bool> _valueSet = new AsyncLocal<bool>();
        private readonly T _defaultValue;
        private readonly Func<T> _defaultFunc;

        public Parallelizable()
        {
            _valueSet.Value = false;
        }

        public Parallelizable(T defaultValue)
        {
            _defaultValue = defaultValue;
        }

        public Parallelizable(Func<T> defaultFunc)
        {
            _defaultFunc = defaultFunc;
        }

        public T Value
        {
            get
            {
                if (!_valueSet.Value)
                {
                    _asyncLocal.Value = _defaultFunc != null ? _defaultFunc.Invoke() : _defaultValue;
                    _valueSet.Value = true;
                }

                return _asyncLocal.Value;
            }
            set
            {
                _asyncLocal.Value = value;
                _valueSet.Value = true;
            }
        }
    }

ОБНОВЛЕНИЕ: Я обошел проблему, найдя место в базовом классе моего тестового прибора, которое наиболее интенсивно использовало AsyncLocal<> для крупных объектов и внутри TearDown()Метод удостоверился, что удалил и установил значение свойств AsyncLocal равным null.Это решило мою проблему.

Однако вопрос остается открытым: почему, когда сами объекты AsyncLocal<> были GC'd, все их значения также не получали GC'd?Кажется, это ошибка в реализации AsyncLocal<>, не так ли?

...