Skip to content

Commit 0dfb982

Browse files
committed
Switch to waitable timers for sleep and timers
This improves accuracy compared to sleep. This make use of high resolution timers on modern windows machines to ensure timers as well as sleeps are close to 1 ms accurate, without raising the global tick interval. In theory it should allow more than 1ms accuracy however, it seem to generally be limited to around 0.6 ms in testing. Note. Requires windows 10.
1 parent e67c63c commit 0dfb982

2 files changed

Lines changed: 160 additions & 34 deletions

File tree

src/windows/osal.c

Lines changed: 157 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,29 @@
1414
********************************************************************/
1515

1616
#include "osal.h"
17+
#include "osal_log.h"
1718

18-
#define URESOLUTION 10
19+
#ifdef __GNUC__
20+
#define CC_THREAD_LOCAL __thread
21+
#elif defined(_MSC_VER)
22+
#define CC_THREAD_LOCAL __declspec (thread)
23+
#elif __STDC_VERSION__ >= 201112L
24+
#define CC_THREAD_LOCAL _Thread_local
25+
#else
26+
#error Cannot define CC_THREAD
27+
#endif
28+
29+
#define SCALE_100NS_PER_S 10000000ULL
30+
#define SCALE_100NS_PER_US 10ULL
31+
32+
typedef struct os_thread_state
33+
{
34+
HANDLE timer;
35+
void (*entry) (void * arg);
36+
void * arg;
37+
} os_thread_state_t;
38+
39+
static CC_THREAD_LOCAL os_thread_state_t * os_thread_state;
1940

2041
void * os_malloc (size_t size)
2142
{
@@ -50,9 +71,73 @@ void os_mutex_destroy (os_mutex_t * mutex)
5071
CloseHandle (mutex);
5172
}
5273

74+
static os_thread_state_t * os_thread_state_init (void (*entry) (void *), void * arg)
75+
{
76+
os_thread_state_t * state =
77+
(os_thread_state_t *)calloc (1, sizeof (os_thread_state_t));
78+
CC_ASSERT (state != NULL);
79+
80+
state->entry = entry;
81+
state->arg = arg;
82+
state->timer = CreateWaitableTimerExA (
83+
NULL,
84+
NULL,
85+
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
86+
TIMER_ALL_ACCESS);
87+
CC_ASSERT (state->timer != INVALID_HANDLE_VALUE);
88+
89+
return state;
90+
}
91+
92+
static os_thread_state_t * os_thread_state_get (void)
93+
{
94+
if (os_thread_state == NULL)
95+
{
96+
os_thread_state = os_thread_state_init (NULL, NULL);
97+
}
98+
return os_thread_state;
99+
}
100+
101+
static void os_thread_state_free (os_thread_state_t * state)
102+
{
103+
CloseHandle (state->timer);
104+
free (state);
105+
}
106+
107+
static void os_internal_sleep (uint64_t delay_100_ns)
108+
{
109+
LARGE_INTEGER ft;
110+
BOOL res;
111+
DWORD event;
112+
os_thread_state_t * state;
113+
114+
state = os_thread_state_get();
115+
116+
CC_ASSERT (delay_100_ns < INT64_MAX);
117+
118+
ft.QuadPart = -(int64_t)delay_100_ns;
119+
120+
res = SetWaitableTimer (state->timer, &ft, 0, NULL, NULL, FALSE);
121+
CC_ASSERT (res);
122+
123+
event = WaitForSingleObject (state->timer, INFINITE);
124+
CC_ASSERT (event == WAIT_OBJECT_0);
125+
}
126+
53127
void os_usleep (uint32_t usec)
54128
{
55-
Sleep (usec / 1000);
129+
os_internal_sleep (SCALE_100NS_PER_US * usec);
130+
}
131+
132+
static void os_thread_entry (void * arg)
133+
{
134+
os_thread_state_t * state = arg;
135+
136+
os_thread_state = state;
137+
os_thread_state->entry (os_thread_state->arg);
138+
os_thread_state = NULL;
139+
140+
os_thread_state_free (state);
56141
}
57142

58143
os_thread_t * os_thread_create (
@@ -63,8 +148,16 @@ os_thread_t * os_thread_create (
63148
void * arg)
64149
{
65150
HANDLE handle;
66-
handle =
67-
CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE)entry, (LPVOID)arg, 0, NULL);
151+
os_thread_state_t * state;
152+
153+
state = os_thread_state_init (entry, arg);
154+
handle = CreateThread (
155+
NULL,
156+
0,
157+
(LPTHREAD_START_ROUTINE)os_thread_entry,
158+
(LPVOID)state,
159+
0,
160+
NULL);
68161
CC_ASSERT (handle != INVALID_HANDLE_VALUE);
69162

70163
if (priority < 5)
@@ -84,7 +177,6 @@ static uint64_t os_get_frequency_tick (void)
84177
if (frequency == 0)
85178
{
86179
LARGE_INTEGER performanceFrequency;
87-
timeBeginPeriod (URESOLUTION);
88180
QueryPerformanceFrequency (&performanceFrequency);
89181
frequency = performanceFrequency.QuadPart;
90182
}
@@ -114,7 +206,11 @@ os_tick_t os_tick_from_us (uint32_t us)
114206

115207
void os_tick_sleep (os_tick_t tick)
116208
{
117-
Sleep ((DWORD)(1000u * tick / os_get_frequency_tick()));
209+
uint64_t delay;
210+
delay = SCALE_100NS_PER_S;
211+
delay *= tick;
212+
delay /= os_get_frequency_tick();
213+
os_internal_sleep (delay);
118214
}
119215

120216
os_sem_t * os_sem_create (size_t count)
@@ -304,15 +400,30 @@ void os_mbox_destroy (os_mbox_t * mbox)
304400
free (mbox);
305401
}
306402

307-
static VOID CALLBACK
308-
timer_callback (UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
403+
static VOID CALLBACK timer_thread (void * arg)
309404
{
310-
os_timer_t * timer = (os_timer_t *)dwUser;
405+
os_timer_t * timer = (os_timer_t *)arg;
406+
BOOL res;
311407

312-
if (timer->fn)
408+
while (timer->run)
313409
{
314-
timer->fn (timer, timer->arg);
410+
res = WaitForSingleObject (timer->timer, INFINITE);
411+
CC_ASSERT (res == WAIT_OBJECT_0);
412+
413+
if (!timer->oneshot)
414+
{
415+
res = SetWaitableTimer (timer->timer, &timer->time, 0, NULL, NULL, 0);
416+
CC_ASSERT (res == TRUE);
417+
}
418+
419+
if (timer->fn)
420+
{
421+
timer->fn (timer, timer->arg);
422+
}
315423
}
424+
425+
CloseHandle (timer->timer);
426+
free (timer);
316427
}
317428

318429
os_timer_t * os_timer_create (
@@ -323,47 +434,61 @@ os_timer_t * os_timer_create (
323434
{
324435
os_timer_t * timer;
325436

326-
timer = (os_timer_t *)malloc (sizeof (*timer));
437+
timer = (os_timer_t *)calloc (1, sizeof (os_timer_t));
327438

328439
timer->fn = fn;
329440
timer->arg = arg;
330-
timer->us = us;
331441
timer->oneshot = oneshot;
442+
timer->run = true;
443+
444+
os_timer_set (timer, us);
445+
446+
timer->timer = CreateWaitableTimerExA (
447+
NULL,
448+
NULL,
449+
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
450+
TIMER_ALL_ACCESS);
451+
CC_ASSERT (timer->timer != INVALID_HANDLE_VALUE);
452+
453+
HANDLE thread = CreateThread (
454+
NULL,
455+
0,
456+
(LPTHREAD_START_ROUTINE)timer_thread,
457+
(LPVOID)timer,
458+
0,
459+
NULL);
460+
CC_ASSERT (thread != INVALID_HANDLE_VALUE);
461+
462+
SetThreadPriority (thread, THREAD_PRIORITY_TIME_CRITICAL);
463+
464+
/* Thread will clean itself up,
465+
we don't need this handle */
466+
CloseHandle (thread);
332467

333468
return timer;
334469
}
335470

336471
void os_timer_set (os_timer_t * timer, uint32_t us)
337472
{
338-
timer->us = us;
473+
uint64_t delay = SCALE_100NS_PER_US * (uint64_t)us;
474+
CC_ASSERT (delay < INT64_MAX);
475+
timer->time.QuadPart = -(int64_t)(delay);
339476
}
340477

341478
void os_timer_start (os_timer_t * timer)
342479
{
343-
timeBeginPeriod (URESOLUTION);
344-
345-
/****************************************************************
346-
* N.B. The function timeSetEvent is obsolete. *
347-
* The reason for still using it here is that it gives *
348-
* much better resolution (15 ms -> 1 ms) than when *
349-
* using the recommended function CreateTimerQueueTimer. *
350-
****************************************************************/
351-
timer->timerID = timeSetEvent (
352-
timer->us / 1000,
353-
URESOLUTION,
354-
timer_callback,
355-
(DWORD_PTR)timer,
356-
(timer->oneshot) ? TIME_ONESHOT : TIME_PERIODIC);
480+
BOOL res;
481+
res = SetWaitableTimer (timer->timer, &timer->time, 0, NULL, NULL, FALSE);
482+
CC_ASSERT (res == TRUE);
357483
}
358484

359485
void os_timer_stop (os_timer_t * timer)
360486
{
361-
timeKillEvent (timer->timerID);
362-
363-
timeEndPeriod (URESOLUTION);
487+
CancelWaitableTimer (timer->timer);
364488
}
365489

366490
void os_timer_destroy (os_timer_t * timer)
367491
{
368-
free (timer);
492+
CancelWaitableTimer (timer->timer);
493+
timer->run = false;
369494
}

src/windows/sys/osal_sys.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,12 @@ typedef struct os_mbox
6868

6969
typedef struct os_timer
7070
{
71-
UINT timerID;
71+
HANDLE timer;
7272
void (*fn) (struct os_timer *, void * arg);
7373
void * arg;
74-
uint32_t us;
74+
LARGE_INTEGER time;
7575
bool oneshot;
76+
bool run;
7677
} os_timer_t;
7778

7879
typedef uint64_t os_tick_t;

0 commit comments

Comments
 (0)