Как обойти дедлок в ntdll?
От: Basil2 Россия https://starostin.msk.ru
Дата: 03.02.20 16:37
Оценка:
Ситуация:

Есть наш код и сторонняя либа, оба С++.

Мы обращаемся к либе в потоке 1. Она, в свою очередь, сама создает поток 2 и делит с ним данные. Когда наш код в потоке 1 дергает код либы, тот приостанавливает поток 2, делает что-то с данными, и вновь разрешает выполнение потоку 2. Типа синхронизация, дешево и сердито.

Проблема:

Иногда поток 2 выделяет память. Это происходит через функцию рантайма __lock(). Если поток приостанавливают в этот момент, то он так и остается в лочке. Затем приостановивший его тред сам пытается выделить память, попадает в ту же __lock() и всё виснет намертво.

Вопрос:

Как этого можно избежать? Доступ к коду либы есть. Но сделать либе нормальную синхронизацию не представляется возможным, там много обмена данными и еще куча слипов по коду. Поэтому нельзя просто выкинуть SuspendThread. Отказаться от выделения памяти в потоках тоже нельзя, т.к. скриптовый язык и там куча этих выделений.

Единственное, что я вижу как решение — SuspendThread используется всего в двух используемых нами функциях. То есть если бы был способ проверить, что __lock() встала на лочку, можно было бы попробовать обождать с саспендом, пока та не освободиться.

Есть ли такой способ?

Может есть более удачные решения?
Проект Ребенок8020 — пошаговый гайд как сделать, вырастить и воспитать ребенка.
Re: Как обойти дедлок в ntdll?
От: Videoman Россия https://hts.tv/
Дата: 03.02.20 20:05
Оценка: +3
Здравствуйте, Basil2, Вы писали:

B>Мы обращаемся к либе в потоке 1. Она, в свою очередь, сама создает поток 2 и делит с ним данные. Когда наш код в потоке 1 дергает код либы, тот приостанавливает поток 2, делает что-то с данными, и вновь разрешает выполнение потоку 2. Типа синхронизация, дешево и сердито.


Вот тут уже "ахтунг". Такой подход не обеспечивает синхронизацию совсем. Suspend-ом вы останавливаете код в произвольной точке, а не четко на критической секции, следовательно, у вас гонка по данным, есть Suspend или нет. Все остальное это следствие отсутствия синхронизации, и lock это только частный случай.
Re: Как обойти дедлок в ntdll?
От: kov_serg Россия  
Дата: 03.02.20 20:32
Оценка:
Здравствуйте, Basil2, Вы писали:

B>Ситуация:


B>Есть наш код и сторонняя либа, оба С++.


B>Мы обращаемся к либе в потоке 1. Она, в свою очередь, сама создает поток 2 и делит с ним данные. Когда наш код в потоке 1 дергает код либы, тот приостанавливает поток 2, делает что-то с данными, и вновь разрешает выполнение потоку 2. Типа синхронизация, дешево и сердито.

Очень сердито

B>т.к. скриптовый язык

Какой?

B>Есть ли такой способ?

Вызывайте suspend внутри lock

B>Может есть более удачные решения?

навалом
Re: Как обойти дедлок в ntdll?
От: Pzz Россия https://github.com/alexpevzner
Дата: 03.02.20 20:58
Оценка: 6 (1)
Здравствуйте, Basil2, Вы писали:

B>Как этого можно избежать? Доступ к коду либы есть. Но сделать либе нормальную синхронизацию не представляется возможным, там много обмена данными и еще куча слипов по коду. Поэтому нельзя просто выкинуть SuspendThread. Отказаться от выделения памяти в потоках тоже нельзя, т.к. скриптовый язык и там куча этих выделений.


А что делает поток 2, когда он ничего не делает?
Re: Как обойти дедлок в ntdll?
От: RedApe Беларусь  
Дата: 04.02.20 07:32
Оценка:
Здравствуйте, Basil2, Вы писали:

B>Может есть более удачные решения?


Если я правильно понял, что происходит, и если позволяет устройство библиотеки, то можно делать так:

останавливать поток 2 не через SuspendThread, а через внутреннюю проверку, выполняемую в потоке 2. Т.е. там время от времени проверяется состояние некой переменной, и если она установлена, то производится остановка. Первый поток должен после отдачи команды на остановку (установки переменной) проверять, остановлен ли поток 2 на самом деле.

Судя по использованию SyspendThread, ты ничего не знаешь про std::mutext, std::lock_guard и std::conditional_variable, а это нужно в этом случае знать.

std::mutex mutex;

// команда остановить поток
bool stop_thread;
std::conditional_variable suspend; 

// поток действительно остановлен
bool thread_is_stopped;
std::conditional_variable resume; 

// поток 1.

{   // останавливаем поток 2
    std::lock_guard lock(mutex);
    stop_thread = true;

    // ожидаем сообщения об остановке потока 2
    while(!thread_is_stopeed)
        suspend.wait(lock);
}
// ...
// здесь поток 2 гарантированно остановлен
// делаем всё что нужно
// ...

{   // запускаем поток 2
    std::lock_guard lock(mutex);
    stop_thread = false;
    resume.notify_one();
}

// поток 2.

while(true)  // какой-то цикл обработки сообщений
{
    // проверяем нет ли команды на остановку
    {
        std::lock_gruard lock(mutex);
        if (stop_thread)
        {
        // сообщаем, что поток остановлен
            thread_is_stopped = true;
            suspend.notify_one();

            // ожидаем команды на запуск
            while(stop_thread)
               resume.wait(lock);
        }
    }
}
--
RedApe
Отредактировано 04.02.2020 7:35 RedApe . Предыдущая версия .
Re[2]: Как обойти дедлок в ntdll?
От: Basil2 Россия https://starostin.msk.ru
Дата: 04.02.20 12:33
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Очень сердито

+100.

B>>т.к. скриптовый язык

_>Какой?
AutoHotKey

B>>Есть ли такой способ?

_>Вызывайте suspend внутри lock
В смысле с защитой в виде мьютекса? Но где их тогда расставить в потоке два — везде?
Если же вы про __lock(), это это функция ntdll.

B>>Может есть более удачные решения?

_>навалом
Например?
Проект Ребенок8020 — пошаговый гайд как сделать, вырастить и воспитать ребенка.
Re[2]: Как обойти дедлок в ntdll?
От: Basil2 Россия https://starostin.msk.ru
Дата: 04.02.20 12:41
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Здравствуйте, Basil2, Вы писали:


B>>Как этого можно избежать? Доступ к коду либы есть. Но сделать либе нормальную синхронизацию не представляется возможным, там много обмена данными и еще куча слипов по коду. Поэтому нельзя просто выкинуть SuspendThread. Отказаться от выделения памяти в потоках тоже нельзя, т.к. скриптовый язык и там куча этих выделений.


Pzz>А что делает поток 2, когда он ничего не делает?


Посапывает Он крутится в самописном цикле обработки внутренних сообщений.

Да, это же мысль! Воткнуть туда точку синхронизации, чтобы приостанавливать поток только между сообщениями; обычной лочкой вместо саспенда. Спасибо!
Проект Ребенок8020 — пошаговый гайд как сделать, вырастить и воспитать ребенка.
Re[2]: Как обойти дедлок в ntdll?
От: Basil2 Россия https://starostin.msk.ru
Дата: 04.02.20 13:07
Оценка:
Здравствуйте, RedApe, Вы писали:

RA>Если я правильно понял, что происходит, и если позволяет устройство библиотеки, то можно делать так:

RA>останавливать поток 2 не через SuspendThread, а через внутреннюю проверку, выполняемую в потоке 2. Т.е. там время от времени проверяется состояние некой переменной, и если она установлена, то производится остановка.

От этого решения меня останавливало то, что неизвестно в каком месте кода проверять эту переменную. Если везде, где идет обмен данными между потоками, так это десятки мест! Я не хочу так капитально править в чужой библиотеке... Я так понимаю, сама идея с SuspendThread именно поэтому у авторов и возникла — типа одним махом всё сразу.

RA>Первый поток должен после отдачи команды на остановку (установки переменной) проверять, остановлен ли поток 2 на самом деле.


Почему кстати переменной? Мьютекса наверное? Или намекаете, что его не хватит и нужна будет условная переменная?

RA>Судя по использованию SyspendThread, ты ничего не знаешь про std::mutext, std::lock_guard и std::conditional_variable, а это нужно в этом случае знать.


Я?! Это авторы библиотеки не знают.


RA>
RA>// команда остановить поток
RA>bool stop_thread;
RA>


Здесь точно нужна переменная? Разве статуса лочки не достаточно?
Проект Ребенок8020 — пошаговый гайд как сделать, вырастить и воспитать ребенка.
Re[3]: Как обойти дедлок в ntdll?
От: kov_serg Россия  
Дата: 04.02.20 13:13
Оценка:
Здравствуйте, Basil2, Вы писали:

B>>>т.к. скриптовый язык

_>>Какой?
B>AutoHotKey
Этот https://github.com/Lexikos/AutoHotkey_L

B>>>Есть ли такой способ?

_>>Вызывайте suspend внутри lock
B>В смысле с защитой в виде мьютекса? Но где их тогда расставить в потоке два — везде?
B>Если же вы про __lock(), это это функция ntdll.
Его же кто-то вызывает. Где stack tarace?

B>>>Может есть более удачные решения?

_>>навалом
B>Например?
Нужно больше деталей иначе будет слишком не в тему.
Re[4]: Как обойти дедлок в ntdll?
От: Basil2 Россия https://starostin.msk.ru
Дата: 04.02.20 14:00
Оценка:
Здравствуйте, kov_serg, Вы писали:

B>>AutoHotKey

_>Этот https://github.com/Lexikos/AutoHotkey_L
Нет, этот: https://github.com/HotKeyIt/ahkdll/

B>>>>Есть ли такой способ?

_>>>Вызывайте suspend внутри lock
B>>В смысле с защитой в виде мьютекса? Но где их тогда расставить в потоке два — везде?
B>>Если же вы про __lock(), это это функция ntdll.
_>Его же кто-то вызывает. Где stack tarace?
Его все вызывают Это же выделение памяти! (если вы про __lock())
Если вы про suspend, то он в двух местах вызывается, и это внушает надежду что это можно как-то обыграть.
Проект Ребенок8020 — пошаговый гайд как сделать, вырастить и воспитать ребенка.
Re[3]: Как обойти дедлок в ntdll?
От: Pzz Россия https://github.com/alexpevzner
Дата: 04.02.20 15:35
Оценка: +6
Здравствуйте, Basil2, Вы писали:

B>Да, это же мысль! Воткнуть туда точку синхронизации, чтобы приостанавливать поток только между сообщениями; обычной лочкой вместо саспенда. Спасибо!


Suspend, это вообще такая штука, которую я не представляю, как можно применить в мирных целях в своем собственном процессе.
Re: Как обойти дедлок в ntdll?
От: Lonely Dog Россия  
Дата: 05.02.20 06:47
Оценка:
Здравствуйте, Basil2, Вы писали:

B>Ситуация:


B>Есть наш код и сторонняя либа, оба С++.


B>Мы обращаемся к либе в потоке 1. Она, в свою очередь, сама создает поток 2 и делит с ним данные. Когда наш код в потоке 1 дергает код либы, тот приостанавливает поток 2, делает что-то с данными, и вновь разрешает выполнение потоку 2. Типа синхронизация, дешево и сердито.


B>Проблема:


B>Иногда поток 2 выделяет память. Это происходит через функцию рантайма __lock(). Если поток приостанавливают в этот момент, то он так и остается в лочке. Затем приостановивший его тред сам пытается выделить память, попадает в ту же __lock() и всё виснет намертво.


B>Вопрос:


B>Как этого можно избежать? Доступ к коду либы есть. Но сделать либе нормальную синхронизацию не представляется возможным, там много обмена данными и еще куча слипов по коду. Поэтому нельзя просто выкинуть SuspendThread. Отказаться от выделения памяти в потоках тоже нельзя, т.к. скриптовый язык и там куча этих выделений.


B>Единственное, что я вижу как решение — SuspendThread используется всего в двух используемых нами функциях. То есть если бы был способ проверить, что __lock() встала на лочку, можно было бы попробовать обождать с саспендом, пока та не освободиться.


B>Есть ли такой способ?


B>Может есть более удачные решения?

А нельзя перехватить SuspendThread и __lock проверять? Скажем, в __lock выставлять флаг, в suspend его проверять, если выставлен, то немного подождать и снова проверить. когда флаг сброшен, может вызывать suspend. Хотя тут наверное возможна гонка. Хм...
Re[3]: Как обойти дедлок в ntdll?
От: RedApe Беларусь  
Дата: 05.02.20 07:15
Оценка:
Здравствуйте, Basil2, Вы писали:

RA>>Первый поток должен после отдачи команды на остановку (установки переменной) проверять, остановлен ли поток 2 на самом деле.


B>Почему кстати переменной? Мьютекса наверное? Или намекаете, что его не хватит и нужна будет условная переменная?

B>Здесь точно нужна переменная? Разве статуса лочки не достаточно?

То решение, которое я предложил, заменяет собой SuspendThread. Т.е. те участки кода "замораживают" второй поток и "размораживают" его, но делают это именно тогда, когда второй поток не держит никаких блокировок. Во время этой заморозки через conditional_variable никакие блокировки долго не держатся, а значит ничего не будет дедлока.

Твоя проблема в том, что SuspendThread использовать категорически нельзя.

Мой код достаточно вставить _вместо_ SuspendThread в потоке 1, и где-то в цикле обработки сообщений в потоке 2. Всё. Весь остальной доступ к совместным ресурсам работает как раньше (или не работает, не знаю, как там всё сделано)
--
RedApe
Re[2]: Как обойти дедлок в ntdll?
От: Basil2 Россия https://starostin.msk.ru
Дата: 05.02.20 19:23
Оценка:
Здравствуйте, Lonely Dog, Вы писали:

B>>Может есть более удачные решения?

LD>А нельзя перехватить SuspendThread и __lock проверять? Скажем, в __lock выставлять флаг, в suspend его проверять, если выставлен, то немного подождать и снова проверить. когда флаг сброшен, может вызывать suspend. Хотя тут наверное возможна гонка. Хм...

У меня была такая мысль. Но __lock — это функция из ntdll! Максимум что можно, если у нее именованный мьютекс, попробовать докопаться до него. Но это недокументированно и грязно...
Проект Ребенок8020 — пошаговый гайд как сделать, вырастить и воспитать ребенка.
Re[4]: Как обойти дедлок в ntdll?
От: Basil2 Россия https://starostin.msk.ru
Дата: 05.02.20 19:28
Оценка:
Здравствуйте, RedApe, Вы писали:

B>>Почему кстати переменной? Мьютекса наверное? Или намекаете, что его не хватит и нужна будет условная переменная?

B>>Здесь точно нужна переменная? Разве статуса лочки не достаточно?

RA>Твоя проблема в том, что SuspendThread использовать категорически нельзя.


Это да.

RA>Мой код достаточно вставить _вместо_ SuspendThread в потоке 1, и где-то в цикле обработки сообщений в потоке 2. Всё. Весь остальной доступ к совместным ресурсам работает как раньше (или не работает, не знаю, как там всё сделано)


Мне интересно с точки зрения кода. У тебя три сущности: мьютекс, условная переменная и просто переменная. Вопрос, все ли три нужны? Я навскидку решение с SuspendThread заменил одним единственным мьютексом. Делаю один scoped lock внутри цикла с сообщениями, и еще один вместо SuspendThread. Что-то упускаю?
Проект Ребенок8020 — пошаговый гайд как сделать, вырастить и воспитать ребенка.
Re[5]: Как обойти дедлок в ntdll?
От: RedApe Беларусь  
Дата: 05.02.20 20:28
Оценка: +1
Здравствуйте, Basil2, Вы писали:

B>Мне интересно с точки зрения кода. У тебя три сущности: мьютекс, условная переменная и просто переменная. Вопрос, все ли три нужны? Я навскидку решение с SuspendThread заменил одним единственным мьютексом. Делаю один scoped lock внутри цикла с сообщениями, и еще один вместо SuspendThread. Что-то упускаю?


Хм.

Правильно ли я тебя понимаю, что почти всё время, пока работает поток №2, он держит мьютекс захваченным. На короткое мгновение в конце/начале цикла мьютекс отпускается, и в этот момент первый поток получает шанс его захватить, изменить какие-то общие переменные и вернуть мьютекс на место?

Да, наверно мой вариант действительно переусложнён.
--
RedApe
Re: Как обойти дедлок в ntdll?
От: Vain Россия google.ru
Дата: 06.02.20 04:15
Оценка:
Здравствуйте, Basil2, Вы писали:

B>Иногда поток 2 выделяет память. Это происходит через функцию рантайма __lock(). Если поток приостанавливают в этот момент, то он так и остается в лочке. Затем приостановивший его тред сам пытается выделить память, попадает в ту же __lock() и всё виснет намертво.

Почему бы просто не делегировать выделение и освобождение памяти одному потоку, т.е. первому? Придётся написать клинап памяти от второго потока, но это и так надо делать после остановки потоков.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Отредактировано 08.02.2020 13:36 Vain . Предыдущая версия .
Re[6]: Как обойти дедлок в ntdll?
От: Basil2 Россия https://starostin.msk.ru
Дата: 06.02.20 04:42
Оценка:
Здравствуйте, RedApe, Вы писали:

RA>Правильно ли я тебя понимаю, что почти всё время, пока работает поток №2, он держит мьютекс захваченным. На короткое мгновение в конце/начале цикла мьютекс отпускается, и в этот момент первый поток получает шанс его захватить, изменить какие-то общие переменные и вернуть мьютекс на место?


Именно так.
Проект Ребенок8020 — пошаговый гайд как сделать, вырастить и воспитать ребенка.
Re[3]: Как обойти дедлок в ntdll?
От: Pavel Dvorkin Россия  
Дата: 07.02.20 16:20
Оценка:
Здравствуйте, Basil2, Вы писали:

B>Если же вы про __lock(), это это функция ntdll.


Ты уверен ? Я никогда не слышал про такую функцию в ntdll, и ее имя внушает сомнения, что она выделяет память. Можно ссылку на ее описание ?
With best regards
Pavel Dvorkin
Re[4]: Как обойти дедлок в ntdll?
От: Basil2 Россия https://starostin.msk.ru
Дата: 11.02.20 15:00
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

B>>Если же вы про __lock(), это это функция ntdll.

PD>Ты уверен ? Я никогда не слышал про такую функцию в ntdll, и ее имя внушает сомнения, что она выделяет память.
Я тоже никогда не слышал.

PD>Можно ссылку на ее описание ?

Если б оно было, я бы наверное не спрашивал

Просто эта функция участвует в цепочке при просмотре Call Stack, и грузится она из ntdll. Вот и всё, что я про нее знаю...
Проект Ребенок8020 — пошаговый гайд как сделать, вырастить и воспитать ребенка.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.