From bfcfd97ffa77453d932c57eb9521bd37bea55b27 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Mon, 20 Jul 2026 23:18:18 +0000 Subject: [PATCH] win: high-resolution poll timeouts via waitable timer + IOCP GetQueuedCompletionStatusEx's ms timeout rounds to the system clock tick (~15.6 ms by default), so a 1 ms uv_timer fires ~15 ms late on an idle Windows box unless another process has raised the tick rate. On Win10 1803+ / Server 2019+, arm a CREATE_WAITABLE_TIMER_HIGH_RESOLUTION waitable timer for the poll deadline and associate it with the loop's IOCP via NtAssociateWaitCompletionPacket. When it fires the kernel posts a completion with lpOverlapped == NULL, which the dequeue loop already treats as a pure wakeup, and GQCS blocks with INFINITE so its coarse ms timeout never applies. This is the same approach Go's runtime uses (golang/go#44343). On older Windows the HIGH_RESOLUTION flag or the Nt* function pointers are absent; hrtimer stays NULL and uv__poll keeps the plain GQCS ms wait, so behavior there is unchanged. Measured on Windows Server 2019 (idle): setTimeout(cb, 1): 15.52 ms -> 1.41 ms setTimeout(cb, 5): 15.62 ms -> 5.24 ms setInterval(16): 28 ms -> 16.4 ms --- src/uv-common.h | 4 +++ src/win/core.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ src/win/winapi.c | 15 +++++++++++ src/win/winapi.h | 30 +++++++++++++++++++++ 4 files changed, 118 insertions(+) diff --git a/src/uv-common.h b/src/uv-common.h index b9a8e976eef..49dec3818ab 100644 --- a/src/uv-common.h +++ b/src/uv-common.h @@ -437,6 +437,10 @@ struct uv__loop_internal_fields_s { struct uv__iou iou; void* inv; /* used by uv__platform_invalidate_fd() */ #endif /* __linux__ */ +#ifdef _WIN32 + void* hrtimer; /* CREATE_WAITABLE_TIMER_HIGH_RESOLUTION or NULL */ + void* hrtimer_pkt; /* NtCreateWaitCompletionPacket handle or NULL */ +#endif /* _WIN32 */ }; #if defined(_WIN32) diff --git a/src/win/core.c b/src/win/core.c index 317238fd229..076f4c03456 100644 --- a/src/win/core.c +++ b/src/win/core.c @@ -224,6 +224,45 @@ static void uv__init(void) { } +/* Per-loop: create a high-res waitable timer + wait-completion packet so + * uv__poll can wake at sub-ms precision (see the hrtimer arm there). On + * pre-Win10-1803 either the Nt* pointers or the HIGH_RESOLUTION flag are + * absent; hrtimer stays NULL and uv__poll keeps its plain GQCS ms wait. */ +static void uv__hrtimer_init(uv__loop_internal_fields_t* lfields) { + HANDLE pkt; + if (pNtCreateWaitCompletionPacket == NULL || + pNtAssociateWaitCompletionPacket == NULL || + pNtCancelWaitCompletionPacket == NULL) + return; + lfields->hrtimer = CreateWaitableTimerExW( + NULL, + NULL, + CREATE_WAITABLE_TIMER_MANUAL_RESET | + CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, + SYNCHRONIZE | TIMER_QUERY_STATE | TIMER_MODIFY_STATE); + if (lfields->hrtimer == NULL) + return; + pkt = NULL; + if (!NT_SUCCESS(pNtCreateWaitCompletionPacket(&pkt, GENERIC_ALL, NULL)) || + pkt == NULL) { + CloseHandle(lfields->hrtimer); + lfields->hrtimer = NULL; + return; + } + lfields->hrtimer_pkt = pkt; +} + + +static void uv__hrtimer_close(uv__loop_internal_fields_t* lfields) { + if (lfields->hrtimer_pkt != NULL) { + pNtCancelWaitCompletionPacket(lfields->hrtimer_pkt, FALSE); + CloseHandle(lfields->hrtimer_pkt); + } + if (lfields->hrtimer != NULL) + CloseHandle(lfields->hrtimer); +} + + int uv_loop_init(uv_loop_t* loop) { uv__loop_internal_fields_t* lfields; struct heap* timer_heap; @@ -300,6 +339,8 @@ int uv_loop_init(uv_loop_t* loop) { if (err) goto fail_async_init; + uv__hrtimer_init(lfields); + return 0; fail_async_init: @@ -367,6 +408,7 @@ void uv__loop_close(uv_loop_t* loop) { loop->timer_heap = NULL; lfields = uv__get_internal_fields(loop); + uv__hrtimer_close(lfields); uv_mutex_destroy(&lfields->loop_metrics.lock); uv__free(lfields); loop->internal_fields = NULL; @@ -463,6 +505,33 @@ static void uv__poll(uv_loop_t* loop, DWORD timeout) { */ lfields->current_timeout = timeout; + /* Arm the high-res waitable timer and associate it with this loop's IOCP; + * it posts a NULL-overlapped completion (already treated as a pure wakeup + * below) so GQCS can block with INFINITE and skip its ~15.6ms-tick ms wait. */ + if (timeout != 0 && timeout != INFINITE && lfields->hrtimer != NULL) { + LARGE_INTEGER due; + BOOLEAN signaled; + due.QuadPart = -(LONGLONG) timeout * 10000; /* relative, 100ns units */ + signaled = FALSE; + /* STATUS_PENDING => packet is mid-delivery; re-associate would fail, + * so skip the arm this round and let the GQCS ms timeout apply. */ + if (pNtCancelWaitCompletionPacket(lfields->hrtimer_pkt, TRUE) + != STATUS_PENDING && + SetWaitableTimer(lfields->hrtimer, &due, 0, NULL, NULL, FALSE) && + NT_SUCCESS(pNtAssociateWaitCompletionPacket(lfields->hrtimer_pkt, + loop->iocp, + lfields->hrtimer, + NULL, + NULL, + 0, + 0, + &signaled))) { + /* AlreadySignaled => timer fired between SetWaitableTimer and the + * associate; the packet is already queued so GQCS won't block. */ + timeout = signaled ? 0 : INFINITE; + } + } + success = GetQueuedCompletionStatusEx(loop->iocp, overlappeds, ARRAY_SIZE(overlappeds), diff --git a/src/win/winapi.c b/src/win/winapi.c index 7ed08dd2dc8..ea1b836e51e 100644 --- a/src/win/winapi.c +++ b/src/win/winapi.c @@ -35,6 +35,9 @@ sNtQueryVolumeInformationFile pNtQueryVolumeInformationFile; sNtQueryDirectoryFile pNtQueryDirectoryFile; sNtQuerySystemInformation pNtQuerySystemInformation; sNtQueryInformationProcess pNtQueryInformationProcess; +sNtCreateWaitCompletionPacket pNtCreateWaitCompletionPacket; +sNtAssociateWaitCompletionPacket pNtAssociateWaitCompletionPacket; +sNtCancelWaitCompletionPacket pNtCancelWaitCompletionPacket; /* Powrprof.dll function pointer */ sPowerRegisterSuspendResumeNotification pPowerRegisterSuspendResumeNotification; @@ -70,6 +73,9 @@ void uv__winapi_init(void) { sNtQueryDirectoryFile pNtQueryDirectoryFile; sNtQuerySystemInformation pNtQuerySystemInformation; sNtQueryInformationProcess pNtQueryInformationProcess; + sNtCreateWaitCompletionPacket pNtCreateWaitCompletionPacket; + sNtAssociateWaitCompletionPacket pNtAssociateWaitCompletionPacket; + sNtCancelWaitCompletionPacket pNtCancelWaitCompletionPacket; sPowerRegisterSuspendResumeNotification pPowerRegisterSuspendResumeNotification; sProcessPrng pProcessPrng; sSetWinEventHook pSetWinEventHook; @@ -133,6 +139,15 @@ void uv__winapi_init(void) { uv_fatal_error(GetLastError(), "GetProcAddress"); } + u.proc = GetProcAddress(ntdll_module, "NtCreateWaitCompletionPacket"); + pNtCreateWaitCompletionPacket = u.pNtCreateWaitCompletionPacket; + + u.proc = GetProcAddress(ntdll_module, "NtAssociateWaitCompletionPacket"); + pNtAssociateWaitCompletionPacket = u.pNtAssociateWaitCompletionPacket; + + u.proc = GetProcAddress(ntdll_module, "NtCancelWaitCompletionPacket"); + pNtCancelWaitCompletionPacket = u.pNtCancelWaitCompletionPacket; + powrprof_module = LoadLibraryExA("powrprof.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); diff --git a/src/win/winapi.h b/src/win/winapi.h index a7e1b179fee..ce1e2500d03 100644 --- a/src/win/winapi.h +++ b/src/win/winapi.h @@ -4657,9 +4657,36 @@ typedef NTSTATUS (NTAPI *sNtQueryInformationProcess) ULONG Length, PULONG ReturnLength); +typedef NTSTATUS (NTAPI *sNtCreateWaitCompletionPacket) + (PHANDLE WaitCompletionPacketHandle, + ACCESS_MASK DesiredAccess, + PVOID ObjectAttributes); + +typedef NTSTATUS (NTAPI *sNtAssociateWaitCompletionPacket) + (HANDLE WaitCompletionPacketHandle, + HANDLE IoCompletionHandle, + HANDLE TargetObjectHandle, + PVOID KeyContext, + PVOID ApcContext, + NTSTATUS IoStatus, + ULONG_PTR IoStatusInformation, + PBOOLEAN AlreadySignaled); + +typedef NTSTATUS (NTAPI *sNtCancelWaitCompletionPacket) + (HANDLE WaitCompletionPacketHandle, + BOOLEAN RemoveSignaledPacket); + /* * Kernel32 headers */ +#ifndef CREATE_WAITABLE_TIMER_MANUAL_RESET +# define CREATE_WAITABLE_TIMER_MANUAL_RESET 0x00000001 +#endif + +#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION +# define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002 +#endif + #ifndef FILE_SKIP_COMPLETION_PORT_ON_SUCCESS # define FILE_SKIP_COMPLETION_PORT_ON_SUCCESS 0x1 #endif @@ -4813,6 +4840,9 @@ extern sNtQueryVolumeInformationFile pNtQueryVolumeInformationFile; extern sNtQueryDirectoryFile pNtQueryDirectoryFile; extern sNtQuerySystemInformation pNtQuerySystemInformation; extern sNtQueryInformationProcess pNtQueryInformationProcess; +extern sNtCreateWaitCompletionPacket pNtCreateWaitCompletionPacket; +extern sNtAssociateWaitCompletionPacket pNtAssociateWaitCompletionPacket; +extern sNtCancelWaitCompletionPacket pNtCancelWaitCompletionPacket; /* Powrprof.dll function pointer */ extern sPowerRegisterSuspendResumeNotification pPowerRegisterSuspendResumeNotification;