SetThreadpoolTimerEx 有时不调用回调

问题描述 投票:0回答:1

我遇到了一个罕见的问题,即 SetThreadpoolTimerEx 未按预期调用回调函数。

环境:

操作系统:Windows 10 22H2(内部版本 19045.5131)

Visual Studio:2022(版本 17.12.0)

SDK 和工具集:Windows SDK 10.0.19041.0,平台工具集 v143

问题:

根据 SetThreadpoolTimerEx 的文档(link):

如果定时器之前的状态是“set”,并且函数返回 FALSE,则回调正在进行或即将开始。

但是,在我的例子中,函数返回 FALSE,但回调没有被调用。这导致我的程序在等待回调信号完成时挂起。

要重现的 C++ 代码:

#include <iostream>
#include <atomic>
#include <chrono>
#include <Windows.h>

std::atomic<bool> callbackDone = false;

VOID(NTAPI callback)(
    _Inout_     PTP_CALLBACK_INSTANCE Instance,
    _Inout_opt_ PVOID                 Context,
    _Inout_     PTP_TIMER             Timer
    )
{
    printf("callback...\n");
    callbackDone = true;
}

int main() {
    bool canFail = false;

    while (true) {
        callbackDone = false;
        auto timer = CreateThreadpoolTimer(callback, nullptr, nullptr);

        LARGE_INTEGER relativeTime = {};

        relativeTime.QuadPart = 10000000 * -1;

        FILETIME relativeTimeFt = {};

        relativeTimeFt.dwLowDateTime = relativeTime.LowPart;
        relativeTimeFt.dwHighDateTime = static_cast<DWORD>(relativeTime.HighPart);

        auto setRes1 = SetThreadpoolTimerEx(timer, &relativeTimeFt, 0, 0);

        Sleep(999);

        auto setRes2 = SetThreadpoolTimerEx(timer, nullptr, 0, 0);

        if (setRes2 == TRUE) {
            FILETIME now = {};
            SetThreadpoolTimerEx(timer, &now, 0, 0);
        }
        else {
            if (!callbackDone) {
                WaitForThreadpoolTimerCallbacks(timer, FALSE);
                canFail = true;
            }
        }

        if (canFail) {
            printf("waiting for possibly failed timer\n");
        }

        auto startWaitTime = std::chrono::high_resolution_clock::now();
        auto waitMsgStep = std::chrono::seconds(10);
        auto nextMsgTime = waitMsgStep;
        while (!callbackDone) {
            SleepEx(10, TRUE);

            auto waitDuration = std::chrono::duration_cast<std::chrono::seconds>(
                std::chrono::high_resolution_clock::now() - startWaitTime
            );

            if (waitDuration > nextMsgTime) {
                printf("waiting for callback to finish for %d seconds...\n", static_cast<DWORD>(nextMsgTime.count()));
                nextMsgTime += waitMsgStep;
            }
        }

        if (canFail) {
            canFail = false;
            printf("timer is ok\n");
        }

        CloseThreadpoolTimer(timer);
    }
}

可能的输出:

enter image description here

问题:

尽管 SetThreadpoolTimerEx 返回 FALSE(根据文档,这表明回调正在进行或即将开始),但在极少数情况下,回调函数不会被调用。这会导致程序无限期地挂起在循环中等待callbackDone 变为true。

问题:

  1. SetThreadpoolTimerEx 是否存在回调的已知问题 即使函数返回 FALSE 也可能不会被调用?

  2. 我是否滥用了线程池计时器 API,从而可能导致 这种行为?

  3. 如何修改我的代码以确保回调始终 调用或正确处理没有调用的情况?

另外,来自Raymond Chen的博客

最后一行是有趣的:当您取消计时器或等待时 对象,线程池尝试召回任何挂起的回调,但是 有时回调已经走得太远而无法进行 回忆道。例如,回调可能已经在进行中。在 在这种情况下,Set...Ex 函数返回 FALSE 来告诉您您正在 还没有完成。必须先等待回调完成 一切终于完成了。

在我的例子中,SetThreadpoolTimerEx 返回 FALSE,我等待 10 秒或更长时间,但回调从未调用。

c++ windows multithreading winapi threadpool
1个回答
0
投票

是的,这是 Windows 错误。 23H2 22631.2861 中也存在最小值,我在那里重现它。

最小工作代码可以是:

VOID NTAPI OnTimer(
                   _Inout_     PTP_CALLBACK_INSTANCE /*Instance*/,
                   _Inout_opt_ PVOID                 Context,
                   _Inout_     PTP_TIMER             /*Timer*/
                   )
{
    *(bool*)Context = true;
}

void Test01()
{
    bool callbackDone = false;
    if (PTP_TIMER Timer = CreateThreadpoolTimer(OnTimer, &callbackDone, 0))
    {
        LARGE_INTEGER DueTime = { (ULONG)-1000000, -1 }; // 100 ms
        SetThreadpoolTimerEx(Timer, (PFILETIME)&DueTime, 0, 0);

        Sleep(100);

        if (!SetThreadpoolTimerEx(Timer, 0, 0, 0))
        {
            if (!callbackDone)
            {
                WaitForThreadpoolTimerCallbacks(Timer, FALSE);

                if (!callbackDone)
                {
                    __debugbreak();
                }

                Sleep(2000);

                if (!callbackDone)
                {
                    __debugbreak();
                }
            }
        }

        CloseThreadpoolTimer(Timer);
    }
}

SetThreadpoolTimerEx
返回
TppCancelTimer
的结果 - 那么计时器是否被取消。如果计时器之前已设置但未取消 - 则必须执行它。调用
WaitForThreadpoolTimerCallbacks
后 -
callbackDone
必须已设置。但这里存在实现中的问题:如果我们在计时器已触发时调用
SetThreadpoolTimerEx(Timer, 0, 0, 0)
,但用户回调尚未调用 - 比如说在
TppSingleTimerExpiration
点(这是内部函数,未导出) -
SetThreadpoolTimerEx
(
TppCancelTimer
)返回 false,但在这种情况下不会调用用户回调。我们可以在
TppSingleTimerExpiration
上设置钩子并延迟它的执行,以重现 Raice:

void (NTAPI *TppSingleTimerExpiration)(PTP_TIMER Timer, PSRWLOCK SRWLock, BOOLEAN b);

HANDLE _G_hEvent = CreateEvent(0, 0, 0, 0);

void Hook_TppSingleTimerExpiration(PTP_TIMER Timer, PSRWLOCK SRWLock, BOOLEAN b)
{
    DbgPrint("Hook_TppSingleTimerExpiration(%p) [0]\n", Timer);

    SetEvent(_G_hEvent);
    Sleep(2000);

    DbgPrint("Hook_TppSingleTimerExpiration(%p) [1]\n", Timer);

    DetourTransactionBegin();
    DetourDetach((void**)&TppSingleTimerExpiration, Hook_TppSingleTimerExpiration);
    DetourTransactionCommit();

    TppSingleTimerExpiration(Timer, SRWLock, b);

    DbgPrint("Hook_TppSingleTimerExpiration(%p) [2]\n", Timer);
}

VOID NTAPI OnTimer(
                   _Inout_     PTP_CALLBACK_INSTANCE /*Instance*/,
                   _Inout_opt_ PVOID                 Context,
                   _Inout_     PTP_TIMER             /*Timer*/
                   )
{
    *(bool*)Context = true;
}

void Test02()
{
    if (_G_hEvent = CreateEvent(0, 0, 0, 0))
    {
        // !! need look for real address on target system
        (ULONG_PTR&)TppSingleTimerExpiration = 0x00007FFCB0FA081C;
        DetourTransactionBegin();
        DetourAttach((void**)&TppSingleTimerExpiration, Hook_TppSingleTimerExpiration);
        DetourTransactionCommit();

        bool callbackDone = false;
        if (PTP_TIMER Timer = CreateThreadpoolTimer(OnTimer, &callbackDone, 0))
        {
            LARGE_INTEGER DueTime = { (ULONG)-1000000, -1 }; // 100 ms
            SetThreadpoolTimerEx(Timer, (PFILETIME)&DueTime, 0, 0);

            WaitForSingleObject(_G_hEvent, INFINITE);

            if (!SetThreadpoolTimerEx(Timer, 0, 0, 0))
            {
                if (!callbackDone)
                {
                    WaitForThreadpoolTimerCallbacks(Timer, FALSE);

                    if (!callbackDone)
                    {
                        __debugbreak();
                    }

                    Sleep(2000);

                    if (!callbackDone)
                    {
                        __debugbreak();
                    }
                }
            }

            CloseThreadpoolTimer(Timer);
        }

        CloseHandle(_G_hEvent);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.