ПРОГРАММИРОВАНИЕ    НА    V I S U A L   C + +
РАССЫЛКА САЙТА       
RSDN.RU  

    Выпуск No. 79 от 9 июня 2002 г.

РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN , НА САЙТЕ КОТОРОГО ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ, РЕСУРСЫ, ПОЛНЫЙ АРХИВ ПРЕДЫДУЩИХ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ.

Здравствуйте, дорогие подписчики!


 ВОПРОС - ОТВЕТ 

Как определить, что приложение не отвечает?

Тестовое приложение Process Viewer
Тестовое приложение HUNGTEST

Когда-то, во времена Windows 3.1, все приложения имели одну общую очередь событий ввода. Если одно из приложений по каким-то причинам переставало обрабатывать сообщения, это легко могло привести к зависанию всей системы. Времена изменились, и теперь, в 32-битной и многопоточной Windows, каждый поток имеет свою очередь ввода. Однако, зависающие приложения не исчезли. Как отличить приложения, которые добросовестно обрабатывают сообщения от тех, что перестали реагировать на запросы пользователя, мы и обсудим в этой статье.

Мы рассмотрим два способра решения этой задачи. Один из них, с использованием функции SendMessageTimeout, - тот, который Microsoft рекомендует в своей базе знаний. Другой - тот, который на самом деле используют приложения Microsoft. В обоих случаях функция для определения зависших приложений будет иметь следующий прототип:


BOOL IsAppHung(
    IN HWND hWnd,      // идентификатор окна
    OUT PBOOL pbHung   // указатель на флаг
    );

Здесь, hWnd - идентификатор главного окна приложения, а pbHung - указатель на переменную типа BOOL, которую функция устанавливает в TRUE, если приложение действительно не отвечает.

Использование функции SendMessageTimeout

Функция SendMessageTimeout посылает сообщение указанному окну. Функция примечательна тем, что в случае, если окно принадлежит другому потоку, она не возвращает управления до тех пор, пока окно не обработает сообщение, либо не истечет указанный интервал времени. Кроме того, если указан флаг SMTO_ABORTIFHUNG, и похоже, что вызываемое приложение зависло, функция возвращает управление сразу же, не дожидаясь, когда истечет таймаут. Именно это свойство SendMessageTimeout позволяет использовать ее для определения зависших приложений.


BOOL IsAppHung_SMTO(
    IN HWND hWnd,
    OUT PBOOL pbHung
    )
{
    _ASSERTE(pbHung != NULL);

    *pbHung = FALSE;

    if (!IsWindow(hWnd))
        return SetLastError(ERROR_INVALID_PARAMETER), FALSE;

    DWORD_PTR dwResult;
    if (!SendMessageTimeout(hWnd, WM_NULL, 0, 0, 
                            SMTO_ABORTIFHUNG|SMTO_BLOCK, 500,
                            &dwResult))
        *pbHung = TRUE;

    return TRUE;
}

Мы посылаем сообщение WM_NULL главному окну проверяемого приложения. Если это сообщение будет успешно доставлено окну, то окно его просто проигнорирует, если же SendMessageTimeout вернет ошибку, мы считаем, что приложение зависло.

Использование недокументированных функций IsHungAppWindow и IsHungThread

Как уже было замечено, программы Microsoft, в частности Windows NT Task Manager и Windows 95 Task List не используют SendMessageTimeout для определения зависших приложений. Вместо этого они пользуются двумя недокументированными функциями.

В Windows NT/2000 библиотека USER32.DLL экспортирует функцию IsHungAppWindow, прототип которой приведен ниже. Task Manager использует эту функцию, чтобы обнаружить приложения, которые перестали отвечать на сообщения.


BOOL WINAPI IsHungAppWindow(
    IN HWND hWnd          // идентификатор окна
    );

В Windows 9x/Me USER32.DLL экспортирует аналогичную функцию под названием IsHungThread, которая отличается от своего NT-собрата тем, что принимает не идентификатор окна, а идентификатор потока.


BOOL WINAPI IsHungThread(
    IN DWORD dwThreadId   // идентификатор потока
    );

Вооруженные этими знаниями, мы можем написать другую функцию для определения зависших приложений:


BOOL IsAppHung_Undoc(
    IN HWND hWnd,
    OUT PBOOL pbHung
    )
{
    if (!IsWindow(hWnd))
        return SetLastError(ERROR_INVALID_PARAMETER), FALSE;
    
    OSVERSIONINFO osvi;
    osvi.dwOSVersionInfoSize = sizeof(osvi);

    // определяем версию операционной системы
    GetVersionEx(&osvi);

    // получаем описатель USER32.DLL
    HINSTANCE hUser = GetModuleHandle(_T("user32.dll"));
    _ASSERTE(hUser != NULL);

    if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)
    {
        BOOL (WINAPI * _IsHungAppWindow)(HWND);

        // находим функцию IsHungAppWindow
        *(FARPROC *)&_IsHungAppWindow =
            GetProcAddress(hUser, "IsHungAppWindow");
        if (_IsHungAppWindow == NULL)
            return SetLastError(ERROR_PROC_NOT_FOUND), FALSE;

        // вызываем IsHungAppWindow
        *pbHung = _IsHungAppWindow(hWnd);
    }
    else
    {
        DWORD dwThreadId = GetWindowThreadProcessId(hWnd, NULL);

        BOOL (WINAPI * _IsHungThread)(DWORD);

        // находим функцию IsHungThread
        *(FARPROC *)&_IsHungThread =
            GetProcAddress(hUser, "IsHungThread");
        if (_IsHungThread == NULL)
            return SetLastError(ERROR_PROC_NOT_FOUND), FALSE;

        // вызываем IsHungThread
        *pbHung = _IsHungThread(dwThreadId);
    }

    return TRUE;
}

Новая функция начинает свою работу с определения версии Windows. Если программа выполняется на Windows NT, то она находит и вызывает функцию IsHungAppWindow. При работе на Windows 9x, функция сперва определяет идентификатор потока, которому принадлежит указанное окно, а затем вызывает IsHungThread. Все очень просто.

А что делать в Windows NT в случае отсутствия у процесса окна? В таком случае общего решения нет. Все зависит от того, что вкладывается в понятие "не отвечает". Для интерактивных процессов тут все просто - не обрабатывает сообщения, значит "не отвечает". Но если речь идет о серверном процессе, то так просто уже не получится. Например, для Web-сервера, если не откликается на HTTP-запрос, значит, "не отвечает". Для других видов процессов другая семантика понятия "не отвечает".

Если есть контроль над исходным кодом процесса, то проще всего реализовать watchdog counter - переменную в реестре, которая периодически увеличивается в реестре процессом. Если счетчик работает, значит, еще жив, если нет - убить и перезапустить.

Если доступа к исходникам нет, то надо разбираться в логике процесса, например, для Web-сервера, можно посылать запрос HEAD на главную страницу. Получили ответ, значит, еще жив, если нет - убить и перезапустить.

Заключение

Таким образом, мы рассмотрели два способа для определения приложений, которые перестали отвечать на запросы пользователей. В процессе тестирования я обнаружил, что функция SendMessageTimeout для только что повисшего приложения возвращает управление с небольшой задержкой, хотя и сообщает о том, что приложение зависло. Через некоторое время, когда система убеждается, что приложение действительно повисло, она начинает возвращать управление сразу. IsHungAppWindow и IsHungThread не имеют этого недостатка.

Чтобы проверить описанные в этой статье функции, вы можете воспользоваться тестовым приложением Process Viewer. В качестве зависающего приложения можно использовать небольшую программу HUNGTEST, которая также прилагается к этой статье.

Cсылки
  1. Q231844 HOWTO: Detect If an Application Has Stopped Responding, Microsoft Knowledge Base.
  2. How the Windows Task Manager determines when the application is not responding?, Ashot Oganesyan.


Это все на сегодня. Пока!

Алекс Jenter   jenter@rsdn.ru
Duisburg, 2002.    Публикуемые в рассылке материалы принадлежат сайту RSDN.

| Предыдущие выпуски     | Статистика рассылки