[PATCH 2/5] ntdll: Make wait/delay calls respect the timer resolution

Arkadiusz Hiler ahiler at codeweavers.com
Mon Aug 17 12:46:51 CDT 2020


NtDelayExecution() and all the calls using server_wait() are now rounding up
their wait times to the next multiply of set timer resolution.

Affected calls are:
 * NtDelayExecution()
 * NtWaitForSingleObject()
 * NtWaitForMultipleObjects()
 * NtSignalAndWaitForSingleObject()
 * NtCreateKeyedEvent()
 * NtWaitForKeyedEvent()
 * NtReleaseKeyedEvent()

Signed-off-by: Arkadiusz Hiler <ahiler at codeweavers.com>
---
 dlls/ntdll/tests/time.c        | 153 +++++++++++++++++++++++++++++++++
 dlls/ntdll/unix/server.c       |  16 +++-
 dlls/ntdll/unix/sync.c         |  29 +++++--
 dlls/ntdll/unix/unix_private.h |  10 +++
 4 files changed, 201 insertions(+), 7 deletions(-)

diff --git a/dlls/ntdll/tests/time.c b/dlls/ntdll/tests/time.c
index 6c800c8c4e..0f4096f708 100644
--- a/dlls/ntdll/tests/time.c
+++ b/dlls/ntdll/tests/time.c
@@ -37,6 +37,13 @@ static BOOL     (WINAPI *pRtlQueryUnbiasedInterruptTime)( ULONGLONG *time );
 
 static NTSTATUS (WINAPI *pNtQueryTimerResolution)( ULONG *min_res, ULONG *max_res, ULONG *current_res );
 static NTSTATUS (WINAPI *pNtSetTimerResolution)( ULONG res, BOOLEAN set, ULONG *current_res );
+static NTSTATUS (WINAPI *pNtWaitForSingleObject)(HANDLE, BOOLEAN, const LARGE_INTEGER *);
+static NTSTATUS (WINAPI *pNtWaitForMultipleObjects)(ULONG,const HANDLE*,BOOLEAN,BOOLEAN,const LARGE_INTEGER*);
+static NTSTATUS (WINAPI *pNtSignalAndWaitForSingleObject)( HANDLE signal, HANDLE wait,
+                                                           BOOLEAN alertable, const LARGE_INTEGER *timeout );
+static NTSTATUS (WINAPI *pNtCreateKeyedEvent)( HANDLE *, ACCESS_MASK, const OBJECT_ATTRIBUTES *, ULONG );
+static NTSTATUS (WINAPI *pNtWaitForKeyedEvent)( HANDLE, const void *, BOOLEAN, const LARGE_INTEGER * );
+static NTSTATUS (WINAPI *pNtReleaseKeyedEvent)( HANDLE, const void *, BOOLEAN, const LARGE_INTEGER * );
 
 static const int MonthLengths[2][12] =
 {
@@ -267,6 +274,46 @@ static void test_user_shared_data_time(void)
     todo_wine ok(changed >= 90, "tick count isn't updated after sleeping one millisecond (%d%% correct)\n", changed);
 }
 
+BOOL spin_the_thread = TRUE;
+
+DWORD WINAPI spinner_thread(void *arg)
+{
+    while (spin_the_thread)
+        Sleep(1);
+
+    return 0;
+}
+
+#define OBEYS_RESOLUTION(call) \
+        for (ULONGLONG _resolution = 30000; _resolution <= 90000; _resolution += 30000) \
+        {                                                                               \
+            ULONG _cur;                                                                 \
+            LARGE_INTEGER _counter[21];                                                 \
+            int _hits = 0;                                                              \
+            pNtSetTimerResolution(_resolution, TRUE, &_cur);                            \
+                                                                                        \
+            QueryPerformanceCounter(&_counter[0]);                                      \
+            for (int _i = 1; _i < ARRAYSIZE(_counter); _i++)                            \
+            {                                                                           \
+                (call);                                                                 \
+                QueryPerformanceCounter(&_counter[_i]);                                 \
+            }                                                                           \
+                                                                                        \
+            for (int _i = 1; _i < ARRAYSIZE(_counter); _i++)                            \
+            {                                                                           \
+                ULONGLONG _delta = _counter[_i].QuadPart - _counter[_i-1].QuadPart;     \
+                if (((_cur-20000) < _delta) && ((_cur+20000) > _delta))                  \
+                    _hits++;                                                            \
+            }                                                                           \
+                                                                                        \
+            pNtSetTimerResolution(_resolution, FALSE, &_cur);                           \
+                                                                                        \
+            ok(_hits >= ((8*ARRAYSIZE(_counter)-1)/10),                                 \
+               #call " hit the expected interval only %u times out of %u for res %u\n", \
+               _hits, ARRAYSIZE(_counter)-1, _resolution);                              \
+        }
+
+
 static void test_timer_resolution(void)
 {
     ULONG min, max, cur, def, cur2;
@@ -291,6 +338,105 @@ static void test_timer_resolution(void)
     ok(cur == def, "after unsetting we've got %u instead of the default %u\n", cur, def);
 
     ok(pNtSetTimerResolution(123456, FALSE, &cur) == STATUS_TIMER_RESOLUTION_NOT_SET, "we have unset resolution more times than one\n");
+
+    {
+        LARGE_INTEGER timeout;
+
+        timeout.QuadPart = -10000;
+        OBEYS_RESOLUTION(NtDelayExecution(TRUE, &timeout));
+        OBEYS_RESOLUTION(NtDelayExecution(FALSE, &timeout));
+
+        OBEYS_RESOLUTION(
+        {
+            NtQuerySystemTime(&timeout);
+            timeout.QuadPart += 10000;
+            NtDelayExecution(TRUE, &timeout);
+        });
+
+        OBEYS_RESOLUTION(
+        {
+            NtQuerySystemTime(&timeout);
+            timeout.QuadPart += 10000;
+            NtDelayExecution(FALSE, &timeout);
+        });
+    }
+
+    {
+        DWORD thread_id;
+        LARGE_INTEGER timeout;
+        HANDLE thread = CreateThread( NULL, 0, spinner_thread, NULL, 0, &thread_id );
+        HANDLE event = CreateEventA( NULL, TRUE, FALSE, NULL );
+
+        timeout.QuadPart = -10000;
+        OBEYS_RESOLUTION(pNtWaitForSingleObject( thread, TRUE, &timeout ));
+        OBEYS_RESOLUTION(pNtWaitForSingleObject( thread, FALSE, &timeout ));
+
+        OBEYS_RESOLUTION(
+        {
+            NtQuerySystemTime(&timeout);
+            timeout.QuadPart += 10000;
+            pNtWaitForSingleObject( thread, TRUE, &timeout );
+        });
+
+        OBEYS_RESOLUTION(
+        {
+            NtQuerySystemTime(&timeout);
+            timeout.QuadPart += 10000;
+            pNtWaitForSingleObject( thread, FALSE, &timeout );
+        });
+
+        /* XXX: saving some test time here on just the relative alertable variants */
+        timeout.QuadPart = -10000;
+        OBEYS_RESOLUTION(pNtWaitForMultipleObjects( 1, &thread, TRUE, TRUE, &timeout ));
+        OBEYS_RESOLUTION({
+            pNtSignalAndWaitForSingleObject( event, thread, TRUE, &timeout );
+            ResetEvent( event );
+        });
+
+        spin_the_thread = FALSE;
+        WaitForSingleObject( thread, 0 );
+    }
+
+    {
+        HANDLE event;
+        LARGE_INTEGER timeout;
+        timeout.QuadPart = -10000;
+
+        ok(pNtCreateKeyedEvent( &event, GENERIC_ALL, NULL, 0 ) == STATUS_SUCCESS, "failed to create keyed event\n");
+        OBEYS_RESOLUTION(pNtWaitForKeyedEvent( event, (void *) 2, FALSE, &timeout ));
+        OBEYS_RESOLUTION(pNtWaitForKeyedEvent( event, (void *) 2, TRUE, &timeout ));
+        OBEYS_RESOLUTION(pNtReleaseKeyedEvent( event, (void *) 2, FALSE, &timeout ));
+        OBEYS_RESOLUTION(pNtReleaseKeyedEvent( event, (void *) 2, TRUE, &timeout ));
+
+        OBEYS_RESOLUTION(
+        {
+            NtQuerySystemTime( &timeout );
+            timeout.QuadPart += 10000;
+            pNtWaitForKeyedEvent( event, (void *) 2, FALSE, &timeout );
+        });
+
+        OBEYS_RESOLUTION(
+        {
+            NtQuerySystemTime( &timeout );
+            timeout.QuadPart += 10000;
+            pNtWaitForKeyedEvent( event, (void *) 2, TRUE, &timeout );
+        });
+
+        OBEYS_RESOLUTION(
+        {
+            NtQuerySystemTime( &timeout );
+            timeout.QuadPart += 10000;
+            pNtReleaseKeyedEvent( event, (void *) 2, FALSE, &timeout );
+        });
+
+        OBEYS_RESOLUTION(
+        {
+            NtQuerySystemTime( &timeout );
+            timeout.QuadPart += 10000;
+            pNtReleaseKeyedEvent( event, (void *) 2, TRUE, &timeout );
+        });
+    }
+
 }
 
 START_TEST(time)
@@ -305,8 +451,15 @@ START_TEST(time)
     pRtlQueryDynamicTimeZoneInformation =
         (void *)GetProcAddress(mod, "RtlQueryDynamicTimeZoneInformation");
     pRtlQueryUnbiasedInterruptTime = (void *)GetProcAddress(mod, "RtlQueryUnbiasedInterruptTime");
+
     pNtQueryTimerResolution = (void *)GetProcAddress(mod, "NtQueryTimerResolution");
     pNtSetTimerResolution = (void *)GetProcAddress(mod, "NtSetTimerResolution");
+    pNtWaitForSingleObject = (void *)GetProcAddress(mod, "NtWaitForSingleObject");
+    pNtWaitForMultipleObjects = (void *)GetProcAddress(mod, "NtWaitForMultipleObjects");
+    pNtSignalAndWaitForSingleObject = (void *)GetProcAddress(mod, "NtSignalAndWaitForSingleObject");
+    pNtCreateKeyedEvent = (void *)GetProcAddress(mod, "NtCreateKeyedEvent");
+    pNtWaitForKeyedEvent = (void *)GetProcAddress(mod, "NtWaitForKeyedEvent");
+    pNtReleaseKeyedEvent = (void *)GetProcAddress(mod, "NtReleaseKeyedEvent");
 
     if (pRtlTimeToTimeFields && pRtlTimeFieldsToTime)
         test_pRtlTimeToTimeFields();
diff --git a/dlls/ntdll/unix/server.c b/dlls/ntdll/unix/server.c
index d867a7fb46..17d125bbf6 100644
--- a/dlls/ntdll/unix/server.c
+++ b/dlls/ntdll/unix/server.c
@@ -672,11 +672,23 @@ unsigned int server_wait( const select_op_t *select_op, data_size_t size, UINT f
     user_apc_t apc;
 
     if (abs_timeout < 0)
+    {
+        LARGE_INTEGER since_server_start;
+
+        NtQueryPerformanceCounter( &since_server_start, NULL );
+        abs_timeout = -multiple_of_time_resolution( -abs_timeout );
+        abs_timeout -= since_server_start.QuadPart;
+    }
+    else
     {
         LARGE_INTEGER now;
 
-        NtQueryPerformanceCounter( &now, NULL );
-        abs_timeout -= now.QuadPart;
+        NtQuerySystemTime( &now );
+        if (abs_timeout > now.QuadPart)
+        {
+            abs_timeout = multiple_of_time_resolution( abs_timeout - now.QuadPart );
+            abs_timeout += now.QuadPart;
+        }
     }
 
     ret = server_select( select_op, size, flags, abs_timeout, NULL, NULL, &apc );
diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c
index eb93ac0d4b..8d4cc580eb 100644
--- a/dlls/ntdll/unix/sync.c
+++ b/dlls/ntdll/unix/sync.c
@@ -85,7 +85,7 @@ static ULONG timer_resolution_current = 0;
 static const ULONG timer_resolution_min = 156250; /* also the default */
 static const ULONG timer_resolution_max = 5000;
 
-static ULONG get_timer_resolution(void)
+ULONG get_timer_resolution(void)
 {
     return timer_resolution_current ? timer_resolution_current : timer_resolution_min;
 }
@@ -1341,10 +1341,17 @@ NTSTATUS WINAPI NtDelayExecution( BOOLEAN alertable, const LARGE_INTEGER *timeou
         LARGE_INTEGER now;
         timeout_t when, diff;
 
+        NtQuerySystemTime( &now );
+
         if ((when = timeout->QuadPart) < 0)
         {
-            NtQuerySystemTime( &now );
-            when = now.QuadPart - when;
+            when = multiple_of_time_resolution( -when );
+            when += now.QuadPart;
+        }
+        else if (when > now.QuadPart)
+        {
+            when = multiple_of_time_resolution( when - now.QuadPart );
+            when += now.QuadPart;
         }
 
         /* Note that we yield after establishing the desired timeout */
@@ -2782,11 +2789,23 @@ NTSTATUS WINAPI RtlWaitOnAddress( const void *addr, const void *cmp, SIZE_T size
     }
 
     if (abs_timeout < 0)
+    {
+        LARGE_INTEGER since_server_start;
+
+        NtQueryPerformanceCounter( &since_server_start, NULL );
+        abs_timeout = -multiple_of_time_resolution( -abs_timeout );
+        abs_timeout -= since_server_start.QuadPart;
+    }
+    else
     {
         LARGE_INTEGER now;
 
-        NtQueryPerformanceCounter( &now, NULL );
-        abs_timeout -= now.QuadPart;
+        NtQuerySystemTime( &now );
+        if (abs_timeout > now.QuadPart)
+        {
+            abs_timeout = multiple_of_time_resolution( abs_timeout - now.QuadPart );
+            abs_timeout += now.QuadPart;
+        }
     }
 
     select_op.keyed_event.op     = SELECT_KEYED_EVENT_WAIT;
diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h
index 397211957b..5b23c4e2aa 100644
--- a/dlls/ntdll/unix/unix_private.h
+++ b/dlls/ntdll/unix/unix_private.h
@@ -264,6 +264,10 @@ extern void WINAPI DECLSPEC_NORETURN call_user_exception_dispatcher( EXCEPTION_R
                                                                      NTSTATUS (WINAPI *dispatcher)(EXCEPTION_RECORD*,CONTEXT*) ) DECLSPEC_HIDDEN;
 extern void WINAPI DECLSPEC_NORETURN call_raise_user_exception_dispatcher( NTSTATUS (WINAPI *dispatcher)(void) ) DECLSPEC_HIDDEN;
 
+extern ULONG get_timer_resolution(void) DECLSPEC_HIDDEN;
+
+
+
 #define TICKSPERSEC 10000000
 #define SECS_1601_TO_1970  ((369 * 365 + 89) * (ULONGLONG)86400)
 #define TICKS_1601_TO_1970 (SECS_1601_TO_1970 * TICKSPERSEC)
@@ -291,6 +295,12 @@ static inline void *get_signal_stack(void)
     return (char *)NtCurrentTeb() + teb_size - teb_offset;
 }
 
+static inline timeout_t multiple_of_time_resolution(timeout_t timeout)
+{
+    ULONG res = get_timer_resolution();
+    return ((timeout + res - 1) / res) * res;
+}
+
 #ifndef _WIN64
 static inline TEB64 *NtCurrentTeb64(void) { return (TEB64 *)NtCurrentTeb()->GdiBatchCount; }
 #endif
-- 
2.28.0




More information about the wine-devel mailing list