From dfddb41e7f04a3b609e333dcc1dc534dfcacb96b Mon Sep 17 00:00:00 2001 From: Reinar Date: Tue, 31 Dec 2024 11:41:50 +0100 Subject: [PATCH] High-res timers for Windows with IOCP support --- include/asio/detail/config.hpp | 11 +++ .../asio/detail/impl/win_iocp_io_context.ipp | 98 +++++++++++++++++++ include/asio/detail/win_iocp_io_context.hpp | 22 +++++ 3 files changed, 131 insertions(+) diff --git a/include/asio/detail/config.hpp b/include/asio/detail/config.hpp index 3c4c8e46c9..12f750bdec 100644 --- a/include/asio/detail/config.hpp +++ b/include/asio/detail/config.hpp @@ -809,6 +809,17 @@ # endif // defined(ASIO_WINDOWS) || defined(__CYGWIN__) #endif // !defined(ASIO_HAS_IOCP) +#if !defined(ASIO_HAS_IOCP_HIRES_TIMERS) +# if defined(ASIO_HAS_IOCP) +# include +# if defined(NTDDI_VERSION) && (NTDDI_VERSION >= NTDDI_WIN10_RS4) +# if defined(ASIO_ENABLE_IOCP_HIRES_TIMERS) +# define ASIO_HAS_IOCP_HIRES_TIMERS 1 +# endif // defined(ASIO_ENABLE_IOCP_HIRES_TIMERS) +# endif // defined(NTDDI_VERSION) && (NTDDI_VERSION >= NTDDI_WIN10_RS4) +# endif // defined(ASIO_HAS_IOCP) +#endif // !defined(ASIO_HAS_IOCP_HIRES_TIMERS) + // On POSIX (and POSIX-like) platforms we need to include unistd.h in order to // get access to the various platform feature macros, e.g. to be able to test // for threads support. diff --git a/include/asio/detail/impl/win_iocp_io_context.ipp b/include/asio/detail/impl/win_iocp_io_context.ipp index 192d98172f..bd776535d2 100644 --- a/include/asio/detail/impl/win_iocp_io_context.ipp +++ b/include/asio/detail/impl/win_iocp_io_context.ipp @@ -30,6 +30,10 @@ #include "asio/detail/push_options.hpp" +#if defined(ASIO_HAS_IOCP_HIRES_TIMERS) +#include +#endif // defined(ASIO_HAS_IOCP_HIRES_TIMERS) + namespace asio { namespace detail { @@ -59,6 +63,7 @@ struct win_iocp_io_context::work_finished_on_block_exit win_iocp_io_context* io_context_; }; +#if !defined(ASIO_HAS_IOCP_HIRES_TIMERS) struct win_iocp_io_context::timer_thread_function { void operator()() @@ -77,6 +82,7 @@ struct win_iocp_io_context::timer_thread_function win_iocp_io_context* io_context_; }; +#endif // !defined(ASIO_HAS_IOCP_HIRES_TIMERS) win_iocp_io_context::win_iocp_io_context( asio::execution_context& ctx, bool own_thread) @@ -103,6 +109,51 @@ win_iocp_io_context::win_iocp_io_context( asio::detail::throw_error(ec, "iocp"); } +#if defined(ASIO_HAS_IOCP_HIRES_TIMERS) + if (FARPROC nt_create_wait_completion_packet_ptr = ::GetProcAddress( + ::GetModuleHandleA("NTDLL"), "NtCreateWaitCompletionPacket")) + { + NtCreateWaitCompletionPacket_ = + reinterpret_cast( + reinterpret_cast(nt_create_wait_completion_packet_ptr)); + } + else + { + DWORD last_error = ::GetLastError(); + asio::error_code ec(last_error, + asio::error::get_system_category()); + asio::detail::throw_error(ec, "timer"); + } + + if (FARPROC nt_associate_wait_completion_packet_ptr = ::GetProcAddress( + ::GetModuleHandleA("NTDLL"), "NtAssociateWaitCompletionPacket")) { + NtAssociateWaitCompletionPacket_ = + reinterpret_cast( + reinterpret_cast(nt_associate_wait_completion_packet_ptr)); + } + else + { + DWORD last_error = ::GetLastError(); + asio::error_code ec(last_error, + asio::error::get_system_category()); + asio::detail::throw_error(ec, "timer"); + } + + if (FARPROC rtl_nt_status_to_dos_error_ptr = ::GetProcAddress( + ::GetModuleHandleA("NTDLL"), "RtlNtStatusToDosError")) { + RtlNtStatusToDosError_ = + reinterpret_cast( + reinterpret_cast(rtl_nt_status_to_dos_error_ptr)); + } + else + { + DWORD last_error = ::GetLastError(); + asio::error_code ec(last_error, + asio::error::get_system_category()); + asio::detail::throw_error(ec, "timer"); + } +#endif // defined(ASIO_HAS_IOCP_HIRES_TIMERS) + if (own_thread) { ::InterlockedIncrement(&outstanding_work_); @@ -149,12 +200,14 @@ void win_iocp_io_context::shutdown() { ::InterlockedExchange(&shutdown_, 1); +#if !defined(ASIO_HAS_IOCP_HIRES_TIMERS) if (timer_thread_.joinable()) { LARGE_INTEGER timeout; timeout.QuadPart = 1; ::SetWaitableTimer(waitable_timer_.handle, &timeout, 1, 0, 0, FALSE); } +#endif // !defined(ASIO_HAS_IOCP_HIRES_TIMERS) if (thread_.joinable()) { @@ -192,7 +245,9 @@ void win_iocp_io_context::shutdown() } } +#if !defined(ASIO_HAS_IOCP_HIRES_TIMERS) timer_thread_.join(); +#endif // !defined(ASIO_HAS_IOCP_HIRES_TIMERS) } asio::error_code win_iocp_io_context::register_handle( @@ -520,6 +575,10 @@ size_t win_iocp_io_context::do_one(DWORD msec, { // We have been woken up to try to acquire responsibility for dispatching // timers and completed operations. +#if defined(ASIO_HAS_IOCP_HIRES_TIMERS) + mutex::scoped_lock lock(dispatch_mutex_); + ::InterlockedExchange(&dispatch_required_, 1); +#endif // defined(ASIO_HAS_IOCP_HIRES_TIMERS) } else { @@ -575,9 +634,26 @@ void win_iocp_io_context::do_add_timer_queue(timer_queue_base& queue) timer_queues_.insert(&queue); +#if defined(ASIO_HAS_IOCP_HIRES_TIMERS) + if (!iocp_wait_handle_.handle) + { + NTSTATUS status = NtCreateWaitCompletionPacket_(&iocp_wait_handle_.handle, GENERIC_ALL, 0); + if (!NT_SUCCESS(status) || (iocp_wait_handle_.handle == 0)) { + DWORD win32_error = RtlNtStatusToDosError_(status); + asio::error_code ec(win32_error, asio::error::get_system_category()); + asio::detail::throw_error(ec, "timer"); + } + } +#endif // defined(ASIO_HAS_IOCP_HIRES_TIMERS) + if (!waitable_timer_.handle) { +#if defined(ASIO_HAS_IOCP_HIRES_TIMERS) + waitable_timer_.handle = ::CreateWaitableTimerExW( + 0, 0, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, SYNCHRONIZE | TIMER_MODIFY_STATE); +#else waitable_timer_.handle = ::CreateWaitableTimer(0, FALSE, 0); +#endif // defined(ASIO_HAS_IOCP_HIRES_TIMERS) if (waitable_timer_.handle == 0) { DWORD last_error = ::GetLastError(); @@ -591,13 +667,25 @@ void win_iocp_io_context::do_add_timer_queue(timer_queue_base& queue) timeout.QuadPart *= 10; ::SetWaitableTimer(waitable_timer_.handle, &timeout, max_timeout_msec, 0, 0, FALSE); +#if defined(ASIO_HAS_IOCP_HIRES_TIMERS) + NTSTATUS status = NtAssociateWaitCompletionPacket_(iocp_wait_handle_.handle, iocp_.handle, + waitable_timer_.handle, (PVOID)wake_for_dispatch, + 0, 0, 0, 0); + if (!NT_SUCCESS(status)) { + DWORD win32_error = RtlNtStatusToDosError_(status); + asio::error_code ec(win32_error, asio::error::get_system_category()); + asio::detail::throw_error(ec, "timer"); + } +#endif // defined(ASIO_HAS_IOCP_HIRES_TIMERS) } +#if !defined(ASIO_HAS_IOCP_HIRES_TIMERS) if (!timer_thread_.joinable()) { timer_thread_function thread_function = { this }; timer_thread_ = thread(thread_function, 65536); } +#endif // !defined(ASIO_HAS_IOCP_HIRES_TIMERS) } void win_iocp_io_context::do_remove_timer_queue(timer_queue_base& queue) @@ -609,8 +697,11 @@ void win_iocp_io_context::do_remove_timer_queue(timer_queue_base& queue) void win_iocp_io_context::update_timeout() { + +#if !defined(ASIO_HAS_IOCP_HIRES_TIMERS) if (timer_thread_.joinable()) { +#endif // !defined(ASIO_HAS_IOCP_HIRES_TIMERS) // There's no point updating the waitable timer if the new timeout period // exceeds the maximum timeout. In that case, we might as well wait for the // existing period of the timer to expire. @@ -622,8 +713,15 @@ void win_iocp_io_context::update_timeout() timeout.QuadPart *= 10; ::SetWaitableTimer(waitable_timer_.handle, &timeout, max_timeout_msec, 0, 0, FALSE); +#if defined(ASIO_HAS_IOCP_HIRES_TIMERS) + NtAssociateWaitCompletionPacket_(iocp_wait_handle_.handle, iocp_.handle, + waitable_timer_.handle, + (PVOID)wake_for_dispatch, 0, 0, 0, 0); +#endif // defined(ASIO_HAS_IOCP_HIRES_TIMERS) } +#if !defined(ASIO_HAS_IOCP_HIRES_TIMERS) } +#endif // !defined(ASIO_HAS_IOCP_HIRES_TIMERS) } } // namespace detail diff --git a/include/asio/detail/win_iocp_io_context.hpp b/include/asio/detail/win_iocp_io_context.hpp index c370fd8a6b..3785a0342e 100644 --- a/include/asio/detail/win_iocp_io_context.hpp +++ b/include/asio/detail/win_iocp_io_context.hpp @@ -224,6 +224,18 @@ class win_iocp_io_context typedef ULONG_PTR ulong_ptr_t; #endif // defined(WINVER) && (WINVER < 0x0500) +#if defined(ASIO_HAS_IOCP_HIRES_TIMERS) + typedef LONG(NTAPI* NtCreateWaitCompletionPacket_fn)(PHANDLE, ACCESS_MASK, PVOID); + + typedef LONG(NTAPI* NtAssociateWaitCompletionPacket_fn)( + HANDLE WaitCompletionPacketHandle, HANDLE IoCompletionHandle, + HANDLE TargetObjectHandle, PVOID KeyContext, + PVOID ApcContext, LONG IoStatus, + ULONG_PTR IoStatusInformation, PBOOLEAN AlreadySignaled); + + typedef ULONG(NTAPI* RtlNtStatusToDosError_fn)(LONG Status); +#endif // defined(ASIO_HAS_IOCP_HIRES_TIMERS) + // Dequeues at most one operation from the I/O completion port, and then // executes it. Returns the number of operations that were dequeued (i.e. // either 0 or 1). @@ -303,16 +315,26 @@ class win_iocp_io_context struct thread_function; friend struct thread_function; +#if !defined(ASIO_HAS_IOCP_HIRES_TIMERS) // Function object for processing timeouts in a background thread. struct timer_thread_function; friend struct timer_thread_function; // Background thread used for processing timeouts. asio::detail::thread timer_thread_; +#endif // !defined(ASIO_HAS_IOCP_HIRES_TIMERS) // A waitable timer object used for waiting for timeouts. auto_handle waitable_timer_; +#if defined(ASIO_HAS_IOCP_HIRES_TIMERS) + auto_handle iocp_wait_handle_; + + NtCreateWaitCompletionPacket_fn NtCreateWaitCompletionPacket_; + NtAssociateWaitCompletionPacket_fn NtAssociateWaitCompletionPacket_; + RtlNtStatusToDosError_fn RtlNtStatusToDosError_; +#endif // defined(ASIO_HAS_IOCP_HIRES_TIMERS) + // Non-zero if timers or completed operations need to be dispatched. long dispatch_required_;