Почему нам нужно проверять IsIoPending, когда рабочий поток собирается выйти?

Из win32threadpool.cpp мы знаем, что перед тем, как рабочий поток завершит работу, проверив тайм-аут 20 секунд, он должен проверить, есть ли ожидающие операции ввода-вывода с помощью метода IsIoPending (), исходя из моего понимания:

1. Когда рабочий поток собирается выйти, он должен завершить свою работу и вернуться в пул потоков.

2. Исходя из приведенного выше 1, для выходящего потока не должно быть отложенных операций ввода-вывода.

Итак, мой вопрос в том, почему нам нужно проверять ожидающий ввод-вывод, когда поток умирает? Альтернатива, как мы можем смоделировать вышеупомянутое условие?

RetryWaitForWork:
if (!WorkerSemaphore->Wait(AppX::IsAppXProcess() ? WorkerTimeoutAppX : WorkerTimeout))
{
    if (!IsIoPending())
    {




while (true)
{
RetryRetire:
    DWORD result = RetiredWorkerSemaphore->Wait(AppX::IsAppXProcess() ? WorkerTimeoutAppX : WorkerTimeout, FALSE);
    _ASSERTE(WAIT_OBJECT_0 == result || WAIT_TIMEOUT == result);

    if (WAIT_OBJECT_0 == result)
    {
        foundWork = true;
        counts = WorkerCounter.GetCleanCounts();
        FireEtwThreadPoolWorkerThreadRetirementStop(counts.NumActive, counts.NumRetired, GetClrInstanceId());
        goto Work;
    }

    if (!IsIoPending())
    {

https://github.com/dotnet/coreclr/blob/master/src/vm/win32threadpool.cpp


person YuFeng Shen    schedule 18.06.2017    source источник
comment
Я не видел комментариев, не могли бы вы помочь вставить их сюда?   -  person YuFeng Shen    schedule 18.06.2017
comment
Вы сослались на комментарии? // Возвращает истину, если в потоке есть ожидающий io. ? Также какое значение для ввода-вывода - это кеш?   -  person YuFeng Shen    schedule 18.06.2017
comment
Возможно, я еще не все получил. а) Поток из пула потоков не умирает. Он используется повторно. б) Под словом «нам нужно проверить» вы имеете в виду, что .NET framework сделает это за вас. При использовании .NET framework ничего делать не нужно. в) мне кажется, что это проблема XY. Почему вы хотите имитировать ожидающие операции ввода-вывода? Для модульного тестирования?   -  person Thomas Weller    schedule 19.06.2017
comment
Я согласился с вами, что поток будет использоваться повторно, однако, если он простаивает более 20 секунд, он будет уничтожен, также будет уничтожен исходный код CLR (if (! WorkerSemaphore- ›Wait (AppX :: IsAppXProcess ()? WorkerTimeoutAppX: WorkerTimeout))) описал это, обратите внимание, WorkerTimeout составляет 20 секунд; наша проблема в том, что наши потоки простаивают более 10 часов, но не были уничтожены; у нас возникла проблема, описанная в этом потоке. stackoverflow.com/questions/44617106/   -  person YuFeng Shen    schedule 19.06.2017
comment
@CodyGray: если IO находится в кеше, результат доступен немедленно, не так ли? Почему тогда он вообще должен переходить в состояние ожидания? Или вы имеете в виду конвейер операций ввода-вывода, например на уровне жесткого диска? В этом случае я согласен с Джейсоном: почему поток должен его ждать, если работа сделана, а результат никогда не используется?   -  person Thomas Weller    schedule 19.06.2017
comment
Привет, эксперты, не могли бы вы помочь проверить эту проблему отладки CLR? stackoverflow.com/questions/44691232/   -  person YuFeng Shen    schedule 22.06.2017


Ответы (1)


Если вы выполните поиск IsIoPending (что является одним из первых действий, которые я делаю, когда сталкиваюсь с чем-то, что мне незнакомо), вы увидите, что чуть ниже он вызывается со следующим комментарием перед ним:

// We can't exit a thread that has pending I/O - we'll "retire" it instead.

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

Единственный оставшийся вопрос, я полагаю, почему бы и нет? Почему мы не можем выйти из потока, ожидающего ввода-вывода? Чтобы разобраться в этом, давайте посмотрим, что IsIoPending на самом деле делает. Поищите немного дальше по файлу, и вы найдете его реализация:

// Returns true if there is pending io on the thread.
BOOL ThreadpoolMgr::IsIoPending()
{
    CONTRACTL
    {
        NOTHROW;         
        MODE_ANY;
        GC_NOTRIGGER;
    }
    CONTRACTL_END;

#ifndef FEATURE_PAL
    int Status;
    ULONG IsIoPending;

    if (g_pufnNtQueryInformationThread)
    {
        Status =(int) (*g_pufnNtQueryInformationThread)(GetCurrentThread(),
                                          ThreadIsIoPending,
                                          &IsIoPending,
                                          sizeof(IsIoPending),
                                          NULL);


        if ((Status < 0) || IsIoPending)
            return TRUE;
        else
            return FALSE;
    }
    return TRUE;
#else
    return FALSE;
#endif // !FEATURE_PAL
}

Комментарий здесь ничего не говорит нам, кроме подтверждения того, что функция названа адекватно и что она делает то, что мы думаем! А как насчет его реализации?

Что ж, первое, что вы замечаете, это то, что большинство интересного ограничено #ifndef FEATURE_PAL условным тестом. Так что такое FEATURE_PAL? PAL расшифровывается как P latform A daptation L ayer, и это просто способ пометки кода, который может работать только в Windows. Если FEATURE_PAL определен, то структура компилируется для ОС отличной от Windows, поэтому код, специфичный для Windows, необходимо исключить. И это именно то, что вы видите здесь - когда FEATURE_FAL определен, эта IsIoPending функция просто возвращает FALSE. Только когда он работает поверх Windows (когда FEATURE_PAL не определен), он пытается проверить, ожидает ли ввод-вывод. Это довольно убедительно свидетельствует о том, что приведенный выше комментарий о невозможности выйти из потока, имеющего ожидающий ввод-вывод, относится к правилу операционной системы Windows.

Что произойдет, если мы работаем в Windows? Выполняется вызов (косвенно, через указатель глобальной функции) к функции Windows API _ 12_. Первый параметр передает дескриптор текущего потока (GetCurrentThread()), второй параметр запрашивает класс информации о потоке ThreadIsIoPending, а следующие два параметра позволяют функции заполнять ULONG переменную IsIoPending (они передают ее размер и указатель на нее) .

Если вы попытаетесь прочитать документацию для NtQueryInformationThread, вы увидите, что это внутренняя функция и что приложениям рекомендуется:

Используйте общедоступную функцию _18 _ вместо этого, чтобы получить эту информацию.

Исходный код .NET не следует этому совету, потому что эта функция (GetThreadIOPendingFlag) не была представлена ​​до Windows XP SP1, а .NET 4 (и, предположительно, более старые версии, для которых был написан этот код) необходимо было запускать в более ранних версиях Windows. . Поэтому они просто вызвали внутреннюю функцию, которая была доступна во всех поддерживаемых версиях Windows.

В любом случае, документация для GetThreadIOPendingFlag в значительной степени подтверждает, что он делает то, что мы подозреваем, что он делает: он возвращает истину, если поток имеет какие-либо ожидающие запросы ввода-вывода, или ложь в противном случае. Внутренняя функция, вызываемая реализацией .NET Framework, вернет ту же информацию.

И теперь, я думаю, мы вернулись к исходному вопросу: почему имеет значение, есть ли у потока ожидающие операции ввода-вывода? Почему нам нужно проверять это, прежде чем убивать? Что ж, в Windows запрос ввода-вывода, выданный потоком, неразрывно привязан к этому конкретному потоку. Невозможно передать владение этим запросом ввода-вывода или его результирующими данными другому потоку. Иными словами, IRP пользовательского режима не могут пережить поток, в котором они изначально были созданы. Если поток завершен, все ожидающие операции ввода-вывода будут бесцеремонно отменены и никогда не будут завершены. Таким образом, мы получаем правило, столь лаконично сформулированное в исходном комментарии: если поток имеет ожидающий ввод-вывод, он не может быть завершен (потому что тогда этот ввод-вывод никогда не будет выполнен и будет потерян навсегда).

Функция GetThreadIOPendingFlag (или NtQueryInformationThread с классом ThreadIsIoPending) просто проверяет активный список IRP для указанного потока, чтобы узнать, пуст он или нет. Если нет ожидающих запросов ввода-вывода, можно безопасно выйти из потока.

Существует множество причин, по которым рабочий поток может иметь ожидающие запросы ввода-вывода, но наиболее распространенная ситуация возникает, если перекрытый (асинхронный) запрос ввода-вывода был выдан потоком. В этом случае тайм-аут может истечь до получения сигнала о завершении ввода-вывода. Асинхронный ввод-вывод, зависящий от выдающего потока, является фундаментальным ограничением архитектуры Win32, и реализация пула потоков в .NET Framework учитывает это ограничение и учитывает его.

Возможно, эта проверка обычно возвращает false, но на всякий случай рекомендуется проверить ее явно. Это стандартные методы защитного программирования - очень важная практика для инфраструктуры, которая распространяется по всему миру, работает в широком спектре различных условий и должна быть максимально надежной.

person Cody Gray    schedule 19.06.2017
comment
Фактически, у нас есть проблема, описанная в этом stackoverflow.com/questions/44617106/, после сбоя сети количество рабочих потоков внезапно увеличилось до сотен и никогда уменьшилось, из! runaway 0x4 мы обнаружили, что этот поток живет более 10 часов и находится в состоянии clr! CLRSemaphore :: Wait + 0x8a, просто ожидая работы. Итак, единственная причина, по которой я могу понять, заключается в том, что за ними стоит ожидающий ввод-вывод, иначе эти потоки будут уничтожены после простоя 20 секунд, проблема в том, как доказать, что есть ожидающие ввода-вывода из дампа памяти? - person YuFeng Shen; 19.06.2017
comment
Также у меня есть загадка, поток также может обслуживать другой новый рабочий элемент, когда есть ожидающий ввод-вывод? из исходного кода мы можем видеть поток, ожидающий прихода нового рабочего элемента для его выполнения. - person YuFeng Shen; 20.06.2017
comment
Да, конечно, может. Если поток все равно должен оставаться в живых, потому что он ожидает ввода-вывода, почему бы не позволить ему выполнять другие задачи, пока он ждет? - person Cody Gray; 20.06.2017