[PATCH v2 0/2] MR9875: Implement InterruptTimeBias and use it in RtlQueryUnbiasedInterruptTime.
After https://gitlab.winehq.org/wine/wine/-/merge_requests/8916 `RtlQueryUnbiasedInterruptTime` now includes suspend time as well when it shouldn't. This fixes this and also allows other components like winevulkan to use `RtlQueryUnbiasedInterruptTime`, if that kind of tick count is necessary there. Since calculating the bias is relatively expensive, this is done together with the timezone bias update every second in such a way that it can only monotonically increase. -- v2: server: Calculate and store InterruptTimeBias in user shared data. https://gitlab.winehq.org/wine/wine/-/merge_requests/9875
From: Marc-Aurel Zent <mzent@codeweavers.com> --- dlls/ntdll/time.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlls/ntdll/time.c b/dlls/ntdll/time.c index 105a6cf5bc8..187e258c098 100644 --- a/dlls/ntdll/time.c +++ b/dlls/ntdll/time.c @@ -476,7 +476,7 @@ BOOL WINAPI RtlQueryUnbiasedInterruptTime(ULONGLONG *time) low = user_shared_data->InterruptTime.LowPart; } while (high != user_shared_data->InterruptTime.High2Time); - /* FIXME: should probably subtract InterruptTimeBias */ *time = (ULONGLONG)high << 32 | low; + *time -= user_shared_data->InterruptTimeBias; return TRUE; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9875
From: Marc-Aurel Zent <mzent@codeweavers.com> --- server/fd.c | 15 ++++++++++++++- server/request.c | 18 +++++++++++------- server/request.h | 4 ++-- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/server/fd.c b/server/fd.c index f70bec354a3..905f11efb7c 100644 --- a/server/fd.c +++ b/server/fd.c @@ -351,6 +351,7 @@ static struct list abs_timeout_list = LIST_INIT(abs_timeout_list); /* sorted abs static struct list rel_timeout_list = LIST_INIT(rel_timeout_list); /* sorted relative timeouts list */ timeout_t current_time; timeout_t monotonic_time; +static timeout_t current_suspend_bias; struct _KUSER_SHARED_DATA *user_shared_data = NULL; static const timeout_t user_shared_data_timeout = 16 * 10000; @@ -377,6 +378,15 @@ static void atomic_store_long(volatile LONG *ptr, LONG value) #endif } +static void atomic_store_ulonglong(volatile ULONGLONG *ptr, ULONGLONG value) +{ +#if defined(__i386__) || defined(__x86_64__) + *ptr = value; +#else + __atomic_store_n(ptr, value, __ATOMIC_SEQ_CST); +#endif +} + static void set_user_shared_data_time(void) { timeout_t tick_count = monotonic_time / 10000; @@ -409,6 +419,9 @@ static void set_user_shared_data_time(void) atomic_store_ulong(&user_shared_data->InterruptTime.LowPart, monotonic_time); atomic_store_long(&user_shared_data->InterruptTime.High1Time, monotonic_time >> 32); + if (current_suspend_bias > user_shared_data->InterruptTimeBias + 10000) + atomic_store_ulonglong(&user_shared_data->InterruptTimeBias, current_suspend_bias); + atomic_store_long(&user_shared_data->TickCount.High2Time, tick_count >> 32); atomic_store_ulong(&user_shared_data->TickCount.LowPart, tick_count); atomic_store_long(&user_shared_data->TickCount.High1Time, tick_count >> 32); @@ -421,7 +434,7 @@ void set_current_time(void) struct timeval now; gettimeofday( &now, NULL ); current_time = (timeout_t)now.tv_sec * TICKS_PER_SEC + now.tv_usec * 10 + ticks_1601_to_1970; - monotonic_time = monotonic_counter(); + monotonic_time = monotonic_counter( ¤t_suspend_bias ); if (user_shared_data) set_user_shared_data_time(); } diff --git a/server/request.c b/server/request.c index 432a5918892..239e93ebf60 100644 --- a/server/request.c +++ b/server/request.c @@ -505,24 +505,28 @@ int send_client_fd( struct process *process, int fd, obj_handle_t handle ) return -1; } -/* return a monotonic time counter */ -timeout_t monotonic_counter(void) +/* return a monotonic time counter and optional suspend bias */ +timeout_t monotonic_counter( timeout_t *bias ) { + timeout_t counter, unbiased_counter = 0; #ifdef __APPLE__ static mach_timebase_info_data_t timebase; if (!timebase.denom) mach_timebase_info( &timebase ); - return mach_continuous_time() * timebase.numer / timebase.denom / 100; + unbiased_counter = mach_absolute_time() * timebase.numer / timebase.denom / 100; + counter = mach_continuous_time() * timebase.numer / timebase.denom / 100; #elif defined(HAVE_CLOCK_GETTIME) struct timespec ts; + if (!clock_gettime( CLOCK_MONOTONIC, &ts )) + counter = unbiased_counter = (timeout_t)ts.tv_sec * TICKS_PER_SEC + ts.tv_nsec / 100; #ifdef CLOCK_BOOTTIME if (!clock_gettime( CLOCK_BOOTTIME, &ts )) - return (timeout_t)ts.tv_sec * TICKS_PER_SEC + ts.tv_nsec / 100; + counter = (timeout_t)ts.tv_sec * TICKS_PER_SEC + ts.tv_nsec / 100; #endif - if (!clock_gettime( CLOCK_MONOTONIC, &ts )) - return (timeout_t)ts.tv_sec * TICKS_PER_SEC + ts.tv_nsec / 100; #endif - return current_time - server_start_time; + if (!unbiased_counter) counter = unbiased_counter = current_time - server_start_time; + if (bias) *bias = counter - unbiased_counter; + return counter; } static void master_socket_dump( struct object *obj, int verbose ) diff --git a/server/request.h b/server/request.h index 13254d967ed..2a082c874b6 100644 --- a/server/request.h +++ b/server/request.h @@ -54,7 +54,7 @@ extern int receive_fd( struct process *process ); extern int send_client_fd( struct process *process, int fd, obj_handle_t handle ); extern void read_request( struct thread *thread ); extern void write_reply( struct thread *thread ); -extern timeout_t monotonic_counter(void); +extern timeout_t monotonic_counter( timeout_t *bias ); extern void open_master_socket(void); extern void close_master_socket( timeout_t timeout ); extern void shutdown_master_socket(void); @@ -69,7 +69,7 @@ extern void trace_reply( enum request req, const union generic_reply *reply ); /* get current tick count to return to client */ static inline unsigned int get_tick_count(void) { - return monotonic_counter() / 10000; + return monotonic_counter( NULL ) / 10000; } /* get the request vararg data */ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9875
v2: Calculates the current suspend bias on every `set_current_time` refresh, so it is never stale and reliable. Also adds a threshold of 1ms before increasing InterruptTimeBias monotonically to remove any random noise in the calculation (which is usually in the sub-microsecond range, but I wanted to be on the safe side here). -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9875#note_126682
On Tue Jan 13 01:14:50 2026 +0000, Etaash Mathamsetty wrote:
Of note is also that on macOS tick counts have included suspend ticks for quite a while so the winevulkan issue this is currently causing has been present there for years (and it would be nice if it could be fixed as well). Calibrated timestamps is not implemented for mac os I see, thanks for clarifying.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9875#note_126683
Also, there are audio backends which could potentially also got the time mismatch now, and might need the same adjustment too.
Audio backends do not mix the NT tick source with their own implementation, so this is a non-issue AFAICT. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9875#note_126685
Rémi Bernon (@rbernon) commented about server/request.c:
+ timeout_t counter, unbiased_counter = 0; #ifdef __APPLE__ static mach_timebase_info_data_t timebase;
if (!timebase.denom) mach_timebase_info( &timebase ); - return mach_continuous_time() * timebase.numer / timebase.denom / 100; + unbiased_counter = mach_absolute_time() * timebase.numer / timebase.denom / 100; + counter = mach_continuous_time() * timebase.numer / timebase.denom / 100; #elif defined(HAVE_CLOCK_GETTIME) struct timespec ts; + if (!clock_gettime( CLOCK_MONOTONIC, &ts )) + counter = unbiased_counter = (timeout_t)ts.tv_sec * TICKS_PER_SEC + ts.tv_nsec / 100; #ifdef CLOCK_BOOTTIME if (!clock_gettime( CLOCK_BOOTTIME, &ts )) - return (timeout_t)ts.tv_sec * TICKS_PER_SEC + ts.tv_nsec / 100; + counter = (timeout_t)ts.tv_sec * TICKS_PER_SEC + ts.tv_nsec / 100; If system is suspended / resumed right between these two calls the bias gets completely off. I don't think we can make any cross-clock measurement reliable in user space, we need to use a consistent clock source across every measurement and this means Vulkan will need to have BOOTTIME clock source if we want to use BOOTTIME clock source for QPC.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9875#note_126693
On Tue Jan 13 15:20:36 2026 +0000, Rémi Bernon wrote:
If system is suspended / resumed right between these two calls the bias gets completely off. I don't think we can make any cross-clock measurement reliable in user space, we need to use a consistent clock source across every measurement and this means Vulkan will need to have BOOTTIME clock source if we want to use BOOTTIME clock source for QPC. Note as well that this here doesn't prevent other processes from executing and reading stale bias values from USD after system is resumed, which would end up with stale Vulkan timestamp values while calling QPC would return already updated timestamps.
If we really want to implement this in user space we will have to implement system suspend / resume hooks to make sure wineserver executes first and update these USD data, before any other Wine process resumes and executes, which seems tricky if not straight impossible depending on the platforms. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9875#note_126694
participants (3)
-
Marc-Aurel Zent -
Marc-Aurel Zent (@mzent) -
Rémi Bernon (@rbernon)