mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Merge remote-tracking branch 'origin/fix/windows-sleeper-timers' into local/base-fixes
This commit is contained in:
@@ -26,6 +26,10 @@ if(BUILD_TESTING)
|
||||
# Windows UTF-8 text boundary (issue #371): the helpers legacy Windows
|
||||
# relies on, and — with the shipped manifest — a UTF-8 process code page.
|
||||
if(WIN32)
|
||||
add_executable(host_time_threads_test tests/test_host_time_threads.c src/host_time.c)
|
||||
target_include_directories(host_time_threads_test PRIVATE include)
|
||||
add_test(NAME host_time_threads_test COMMAND host_time_threads_test)
|
||||
set_tests_properties(host_time_threads_test PROPERTIES TIMEOUT 15)
|
||||
add_executable(win_utf8_helpers_test tests/test_win_utf8.cpp)
|
||||
target_include_directories(win_utf8_helpers_test PRIVATE include)
|
||||
add_test(NAME win_utf8_helpers_test COMMAND win_utf8_helpers_test)
|
||||
|
||||
+40
-19
@@ -21,31 +21,49 @@
|
||||
#endif
|
||||
|
||||
static LARGE_INTEGER s_qpc_freq;
|
||||
static int s_qpc_freq_init;
|
||||
static HANDLE s_sleep_timer;
|
||||
static int s_sleep_timer_init;
|
||||
static INIT_ONCE s_time_once = INIT_ONCE_STATIC_INIT;
|
||||
static DWORD s_sleep_timer_slot = FLS_OUT_OF_INDEXES;
|
||||
|
||||
static void sleep_timer_ensure(void)
|
||||
static void CALLBACK sleep_timer_close(void *timer)
|
||||
{
|
||||
if (s_sleep_timer_init)
|
||||
return;
|
||||
s_sleep_timer_init = 1;
|
||||
s_sleep_timer =
|
||||
if (timer) CloseHandle((HANDLE)timer);
|
||||
}
|
||||
|
||||
static BOOL CALLBACK time_initialize(PINIT_ONCE once, PVOID parameter, PVOID *context)
|
||||
{
|
||||
(void)once; (void)parameter; (void)context;
|
||||
if (!QueryPerformanceFrequency(&s_qpc_freq)) s_qpc_freq.QuadPart = 0;
|
||||
/* An auto-reset timer has one pending deadline and wakes one waiter.
|
||||
* Sharing it lets a render/present worker cancel the guest pacer's wait.
|
||||
* FLS also releases the handle when its thread/guest fiber is destroyed. */
|
||||
s_sleep_timer_slot = FlsAlloc(sleep_timer_close);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static HANDLE sleep_timer_get(void)
|
||||
{
|
||||
HANDLE timer;
|
||||
if (!InitOnceExecuteOnce(&s_time_once, time_initialize, NULL, NULL) ||
|
||||
s_sleep_timer_slot == FLS_OUT_OF_INDEXES)
|
||||
return NULL;
|
||||
timer = (HANDLE)FlsGetValue(s_sleep_timer_slot);
|
||||
if (timer) return timer;
|
||||
timer =
|
||||
CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
|
||||
TIMER_ALL_ACCESS);
|
||||
if (!s_sleep_timer)
|
||||
s_sleep_timer = CreateWaitableTimerW(NULL, FALSE, NULL);
|
||||
if (!timer) timer = CreateWaitableTimerW(NULL, FALSE, NULL);
|
||||
if (timer && !FlsSetValue(s_sleep_timer_slot, timer)) {
|
||||
CloseHandle(timer);
|
||||
return NULL;
|
||||
}
|
||||
return timer;
|
||||
}
|
||||
|
||||
uint64_t psx_host_mono_ms(void)
|
||||
{
|
||||
LARGE_INTEGER counter;
|
||||
|
||||
if (!s_qpc_freq_init) {
|
||||
if (!QueryPerformanceFrequency(&s_qpc_freq))
|
||||
s_qpc_freq.QuadPart = 0;
|
||||
s_qpc_freq_init = 1;
|
||||
}
|
||||
InitOnceExecuteOnce(&s_time_once, time_initialize, NULL, NULL);
|
||||
if (s_qpc_freq.QuadPart == 0)
|
||||
return (uint64_t)GetTickCount64();
|
||||
if (!QueryPerformanceCounter(&counter))
|
||||
@@ -57,18 +75,21 @@ void psx_host_sleep_micros(unsigned usec)
|
||||
{
|
||||
LARGE_INTEGER due;
|
||||
LONGLONG hundred_ns;
|
||||
HANDLE timer;
|
||||
|
||||
if (usec == 0u)
|
||||
return;
|
||||
|
||||
sleep_timer_ensure();
|
||||
if (s_sleep_timer) {
|
||||
timer = sleep_timer_get();
|
||||
if (timer) {
|
||||
hundred_ns = -((LONGLONG)usec * 10LL);
|
||||
if (hundred_ns >= 0)
|
||||
hundred_ns = -1LL;
|
||||
due.QuadPart = hundred_ns;
|
||||
if (SetWaitableTimer(s_sleep_timer, &due, 0, NULL, NULL, FALSE)) {
|
||||
(void)WaitForSingleObject(s_sleep_timer, INFINITE);
|
||||
/* Bounded: a missed signal costs at most the sleep plus 2 ms, never
|
||||
* an indefinitely parked thread. */
|
||||
if (SetWaitableTimer(timer, &due, 0, NULL, NULL, FALSE)) {
|
||||
(void)WaitForSingleObject(timer, (DWORD)(usec / 1000u) + 2u);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include "host_time.h"
|
||||
|
||||
static HANDLE start_event;
|
||||
static volatile LONG errors;
|
||||
static DWORD WINAPI sleeper(void *arg) {
|
||||
(void)arg;
|
||||
WaitForSingleObject(start_event, INFINITE);
|
||||
uint64_t previous = psx_host_mono_ms();
|
||||
for (unsigned i=0; i<250; ++i) {
|
||||
psx_host_sleep_micros(1000);
|
||||
uint64_t now = psx_host_mono_ms();
|
||||
if (now < previous) InterlockedIncrement(&errors);
|
||||
previous = now;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static int run_wave(void) {
|
||||
HANDLE threads[8];
|
||||
start_event = CreateEventW(NULL, TRUE, FALSE, NULL);
|
||||
if (!start_event) return 2;
|
||||
for (unsigned i=0; i<8; ++i) {
|
||||
threads[i] = CreateThread(NULL, 0, sleeper, NULL, 0, NULL);
|
||||
if (!threads[i]) return 2;
|
||||
}
|
||||
SetEvent(start_event);
|
||||
DWORD result = WaitForMultipleObjects(8, threads, TRUE, 10000);
|
||||
if (result != WAIT_OBJECT_0) {
|
||||
puts("FAIL: concurrent sleepers lost a wakeup");
|
||||
return 1;
|
||||
}
|
||||
for (unsigned i=0; i<8; ++i) CloseHandle(threads[i]);
|
||||
CloseHandle(start_event);
|
||||
return errors ? 1 : 0;
|
||||
}
|
||||
int main(void) {
|
||||
/* The cold wave still tests concurrent InitOnce initialization. Measure
|
||||
* handle growth only after process/CRT/Windows lazy initialization, then
|
||||
* repeat whole thread lifetimes: a leaked caller timer grows every wave. */
|
||||
int result = run_wave();
|
||||
if (result) return result;
|
||||
DWORD before, after;
|
||||
if (!GetProcessHandleCount(GetCurrentProcess(), &before)) return 2;
|
||||
for (int wave = 0; wave < 3; ++wave) {
|
||||
result = run_wave();
|
||||
if (result) return result;
|
||||
}
|
||||
if (!GetProcessHandleCount(GetCurrentProcess(), &after) || after > before) {
|
||||
printf("FAIL: sleeper timer handles leaked after thread exit: before=%lu after=%lu\n", before, after);
|
||||
return 1;
|
||||
}
|
||||
if (errors) return 1;
|
||||
puts("host time concurrent sleepers: PASS");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user