Мистика со значением переменной класса
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 09.09.19 15:25
Оценка: 5 (1)
Много лет назад, в KS-драйвере для portcls.sys (там используются объекты COM), сделал вот такой код:

  NonDelegatingRelease и деструктор
ULONG CUnknown::NonDelegatingRelease () {

  Assert (m_lRefCount > 0);
  LONG const R = InterlockedDecrement (&m_lRefCount);

  if (R == 0) {
    m_lRefCount++;
    delete this;
    return 0;
  }

  return ULONG (R);

}

CUnknown::~CUnknown () {
  Assert (m_lRefCount == 1);
}

NonDelegatingRelease я, по сути, слизал из примера stdunk.cpp в WDK:

  NonDelegatingRelease из stdunk.cpp
STDMETHODIMP_(ULONG) CUnknown::NonDelegatingRelease(void)
{
    ASSERT(m_lRefCount > 0);

    if (InterlockedDecrement(&m_lRefCount) == 0)
    {
        m_lRefCount++;
        delete this;
        return 0;
    }

    return ULONG(m_lRefCount); 
}

Зачем там инкремент счетчика перед удалением, я так и не понял — он встречается в некоторых реализациях IUnknown, с пояснением, что это якобы "reentrance protection", хотя непонятно, что и как оно может защитить. Тогда разбираься было некогда — оставил, как есть.

Все это прекрасно работало лет пятнадцать на разных процессорах, под виртуалками и без, а с месяц назад стал периодически срабатывать Assert (m_lRefCount == 1) в деструкторе, причем в двух разных драйверах, пусть и переделанных из общего кода. При этом драйверы падают в отладчик, где я всегда вижу единицу в m_lRefCount.

Release и деструктор всегда вызываются только из одного потока, никакого параллелизма там и близко нет. Смотрел в код — там нет никаких намеков на гонки, все строго последовательно.

Убрал инкремент (изменив, само собой, в assert единицу на нуль) — все, как и ожидалось, работает прекрасно. Но таки хочется понять, отчего мог срабатывать assert.

Компилятора я не менял, обновлений винды не ставил, виртуалки, в которых это проявлялось, тоже не трогал. Единственное, с чем могу хоть как-то связать — менял термопасту на процессоре (засохла напрочь), после этого ядра перестали сбрасывать частоту на средней/сильной загрузке, отчего немного изменились тайминги. Если бы при освобождении/удалении объектов имел место хоть какой-то параллелизм — сразу списал бы на него, но параллелизма там нет совсем.

Что это могло быть? Может, у кого возникали схожие ситуации?
release delete reference счетчик гонки race
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.