-
Notifications
You must be signed in to change notification settings - Fork 5k
win: high-resolution event-loop timer via waitable timer + IOCP #34834
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+41
−2
Merged
Changes from 11 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
940a6d6
win: high-resolution event-loop timer via waitable timer + IOCP
robobun 3d9b065
fix POBJECT_ATTRIBUTES -> PVOID in winapi.h typedef
robobun 4a5a6b6
win-hrtimer: move uv__hrtimer_init after all fallible uv_loop_init steps
robobun 2345ba5
address review: narrow timer access mask, trim comments, filter stder…
robobun f83da13
win-hrtimer: 100ms arm threshold, TRUE for RemoveSignaledPacket, drop…
robobun 01a0783
win-hrtimer: skip arm when cancel returns STATUS_PENDING
robobun 700ba53
win-hrtimer: read AlreadySignaled from associate and use timeout=0 wh…
robobun 1f98201
test: trim setTimeout(1) quantization test comment to three lines
robobun 950ad9f
win-hrtimer: drop timeBeginPeriod(1) fallback
robobun 6a019a0
win-hrtimer: use explicit timeout bounds instead of unsigned-wrap trick
robobun 2757b1f
win-hrtimer: drop process-level probe; check pNt* directly in uv__hrt…
robobun a5031b5
win-hrtimer: extract uv__hrtimer_close to pair with uv__hrtimer_init
robobun c2e758e
win-hrtimer: drop the 100ms arm threshold
robobun dfd3363
libuv: bump to oven-sh/libuv@9687330 (absorbs win-hrtimer patch)
robobun 5e5f31a
Merge origin/main; keep libuv at oven-sh/libuv@9687330 (hrtimer)
robobun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| High-resolution event-loop timeouts on Windows. | ||
|
|
||
| GetQueuedCompletionStatusEx's ms timeout rounds to the system clock tick | ||
| (~15.6ms by default), so a 1ms uv_timer fires ~15ms late unless another | ||
| process has raised the tick rate. On Win10 1803+, arm a | ||
| CREATE_WAITABLE_TIMER_HIGH_RESOLUTION waitable timer for the deadline and | ||
| associate it with the loop's IOCP via NtAssociateWaitCompletionPacket; the | ||
| kernel posts a NULL-overlapped completion when it fires, which the dequeue | ||
| path already treats as a pure wakeup. GQCS itself then blocks with INFINITE. | ||
| On older Windows the probe fails and uv__poll keeps the plain GQCS ms wait. | ||
| --- 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) | ||
| --- 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; | ||
| --- 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); | ||
| --- a/src/win/core.c | ||
| +++ b/src/win/core.c | ||
| @@ -224,6 +224,35 @@ | ||
| } | ||
|
|
||
|
|
||
| +/* 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; | ||
| +} | ||
| + | ||
| + | ||
| int uv_loop_init(uv_loop_t* loop) { | ||
| uv__loop_internal_fields_t* lfields; | ||
| struct heap* timer_heap; | ||
| @@ -300,6 +329,8 @@ | ||
| if (err) | ||
| goto fail_async_init; | ||
|
|
||
| + uv__hrtimer_init(lfields); | ||
| + | ||
| return 0; | ||
|
|
||
| fail_async_init: | ||
| @@ -367,6 +398,12 @@ | ||
| loop->timer_heap = NULL; | ||
|
|
||
| lfields = uv__get_internal_fields(loop); | ||
| + if (lfields->hrtimer_pkt != NULL) { | ||
| + pNtCancelWaitCompletionPacket(lfields->hrtimer_pkt, FALSE); | ||
| + CloseHandle(lfields->hrtimer_pkt); | ||
| + } | ||
| + if (lfields->hrtimer != NULL) | ||
| + CloseHandle(lfields->hrtimer); | ||
| uv_mutex_destroy(&lfields->loop_metrics.lock); | ||
| uv__free(lfields); | ||
| loop->internal_fields = NULL; | ||
| @@ -463,6 +500,34 @@ | ||
| */ | ||
| 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. | ||
| + * >100ms deadlines don't need sub-tick precision; skip the 3 syscalls. */ | ||
| + if (timeout - 1 < 100 && lfields->hrtimer != NULL) { | ||
|
Check warning on line 179 in patches/libuv/win-hrtimer.patch
|
||
|
robobun marked this conversation as resolved.
Outdated
|
||
| + 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), | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.