FreeRDP
Loading...
Searching...
No Matches
libfreerdp/core/timer.c
1
21#include <winpr/thread.h>
22#include <winpr/collections.h>
23
24#include <freerdp/timer.h>
25#include <freerdp/log.h>
26#include "rdp.h"
27#include "utils.h"
28#include "timer.h"
29
30#if !defined(EMSCRIPTEN)
31#define FREERDP_TIMER_SUPPORTED
32#else
33#define TAG FREERDP_TAG("timer")
34#endif
35
36typedef ALIGN64 struct
37{
38 FreeRDP_TimerID id;
39 uint64_t intervallNS;
40 uint64_t nextRunTimeNS;
41 FreeRDP_TimerCallback cb;
42 void* userdata;
43 rdpContext* context;
44 bool mainloop;
45} timer_entry_t;
46
47struct ALIGN64 freerdp_timer_s
48{
49 rdpRdp* rdp;
50 wArrayList* entries;
51 HANDLE thread;
52 HANDLE event;
53 HANDLE mainevent;
54 size_t maxIdx;
55 bool running;
56};
57
58FreeRDP_TimerID freerdp_timer_add(rdpContext* context, uint64_t intervalNS,
59 FreeRDP_TimerCallback callback, void* userdata, bool mainloop)
60{
61 WINPR_ASSERT(context);
62 WINPR_ASSERT(context->rdp);
63
64#if !defined(FREERDP_TIMER_SUPPORTED)
65 WINPR_UNUSED(context);
66 WINPR_UNUSED(intervalNS);
67 WINPR_UNUSED(callback);
68 WINPR_UNUSED(userdata);
69 WINPR_UNUSED(mainloop);
70 WLog_WARN(TAG, "Platform does not support freerdp_timer_* API");
71 return 0;
72#else
73 FreeRDPTimer* timer = context->rdp->timer;
74 WINPR_ASSERT(timer);
75
76 if ((intervalNS == 0) || !callback)
77 return false;
78
79 const uint64_t cur = winpr_GetTickCount64NS();
80 const timer_entry_t entry = { .id = ++timer->maxIdx,
81 .intervallNS = intervalNS,
82 .nextRunTimeNS = cur + intervalNS,
83 .cb = callback,
84 .userdata = userdata,
85 .context = context,
86 .mainloop = mainloop };
87
88 if (!ArrayList_Append(timer->entries, &entry))
89 return 0;
90 (void)SetEvent(timer->event);
91 return entry.id;
92#endif
93}
94
95static BOOL foreach_entry(void* data, WINPR_ATTR_UNUSED size_t index, va_list ap)
96{
97 timer_entry_t* entry = data;
98 WINPR_ASSERT(entry);
99
100 FreeRDP_TimerID id = va_arg(ap, FreeRDP_TimerID);
101
102 if (entry->id == id)
103 {
104 /* Mark the timer to be disabled.
105 * It will be removed on next rescheduling event
106 */
107 entry->intervallNS = 0;
108 return FALSE;
109 }
110 return TRUE;
111}
112
113bool freerdp_timer_remove(rdpContext* context, FreeRDP_TimerID id)
114{
115 WINPR_ASSERT(context);
116 WINPR_ASSERT(context->rdp);
117
118 FreeRDPTimer* timer = context->rdp->timer;
119 WINPR_ASSERT(timer);
120
121 return !ArrayList_ForEach(timer->entries, foreach_entry, id);
122}
123
124static BOOL runTimerEvent(timer_entry_t* entry, uint64_t* now)
125{
126 WINPR_ASSERT(entry);
127
128 entry->intervallNS =
129 entry->cb(entry->context, entry->userdata, entry->id, *now, entry->intervallNS);
130 *now = winpr_GetTickCount64NS();
131 entry->nextRunTimeNS = *now + entry->intervallNS;
132 return TRUE;
133}
134
135static BOOL runExpiredTimer(void* data, WINPR_ATTR_UNUSED size_t index,
136 WINPR_ATTR_UNUSED va_list ap)
137{
138 timer_entry_t* entry = data;
139 WINPR_ASSERT(entry);
140 WINPR_ASSERT(entry->cb);
141
142 /* Skip all timers that have been deactivated. */
143 if (entry->intervallNS == 0)
144 return TRUE;
145
146 uint64_t* now = va_arg(ap, uint64_t*);
147 WINPR_ASSERT(now);
148
149 bool* mainloop = va_arg(ap, bool*);
150 WINPR_ASSERT(mainloop);
151
152 if (entry->nextRunTimeNS > *now)
153 return TRUE;
154
155 if (entry->mainloop)
156 *mainloop = true;
157 else
158 runTimerEvent(entry, now);
159
160 return TRUE;
161}
162
163#if defined(FREERDP_TIMER_SUPPORTED)
164static uint64_t expire_and_reschedule(FreeRDPTimer* timer)
165{
166 WINPR_ASSERT(timer);
167
168 bool mainloop = false;
169 uint64_t next = UINT64_MAX;
170 uint64_t now = winpr_GetTickCount64NS();
171
172 ArrayList_Lock(timer->entries);
173 ArrayList_ForEach(timer->entries, runExpiredTimer, &now, &mainloop);
174 if (mainloop)
175 (void)SetEvent(timer->mainevent);
176
177 size_t pos = 0;
178 while (pos < ArrayList_Count(timer->entries))
179 {
180 timer_entry_t* entry = ArrayList_GetItem(timer->entries, pos);
181 WINPR_ASSERT(entry);
182 if (entry->intervallNS == 0)
183 {
184 ArrayList_RemoveAt(timer->entries, pos);
185 continue;
186 }
187 if (next > entry->nextRunTimeNS)
188 next = entry->nextRunTimeNS;
189 pos++;
190 }
191 ArrayList_Unlock(timer->entries);
192
193 if (next == UINT64_MAX)
194 return 0;
195 return next;
196}
197
198static DWORD WINAPI timer_thread(LPVOID arg)
199{
200 FreeRDPTimer* timer = arg;
201 WINPR_ASSERT(timer);
202
203 // TODO: Currently we only support ms granularity, look for ways to improve
204 DWORD timeout = INFINITE;
205 HANDLE handles[2] = { utils_get_abort_event(timer->rdp), timer->event };
206
207 while (timer->running &&
208 (WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, timeout) != WAIT_OBJECT_0))
209 {
210 (void)ResetEvent(timer->event);
211 const uint64_t next = expire_and_reschedule(timer);
212 const uint64_t now = winpr_GetTickCount64NS();
213 if (now >= next)
214 {
215 timeout = INFINITE;
216 continue;
217 }
218
219 const uint64_t diff = next - now;
220 const uint64_t diffMS = diff / 1000;
221 timeout = INFINITE;
222 if (diffMS < INFINITE)
223 timeout = (uint32_t)diffMS;
224 }
225 return 0;
226}
227#endif
228
229void freerdp_timer_free(FreeRDPTimer* timer)
230{
231 if (!timer)
232 return;
233
234 timer->running = false;
235 if (timer->event)
236 (void)SetEvent(timer->event);
237
238 if (timer->thread)
239 {
240 (void)WaitForSingleObject(timer->thread, INFINITE);
241 CloseHandle(timer->thread);
242 }
243 if (timer->mainevent)
244 CloseHandle(timer->mainevent);
245 if (timer->event)
246 CloseHandle(timer->event);
247 ArrayList_Free(timer->entries);
248 free(timer);
249}
250
251static void* entry_new(const void* val)
252{
253 const timer_entry_t* entry = val;
254 if (!entry)
255 return NULL;
256
257 timer_entry_t* copy = calloc(1, sizeof(timer_entry_t));
258 if (!copy)
259 return NULL;
260 *copy = *entry;
261 return copy;
262}
263
264FreeRDPTimer* freerdp_timer_new(rdpRdp* rdp)
265{
266 WINPR_ASSERT(rdp);
267 FreeRDPTimer* timer = calloc(1, sizeof(FreeRDPTimer));
268 if (!timer)
269 return NULL;
270 timer->rdp = rdp;
271
272 timer->entries = ArrayList_New(TRUE);
273 if (!timer->entries)
274 goto fail;
275 wObject* obj = ArrayList_Object(timer->entries);
276 WINPR_ASSERT(obj);
277 obj->fnObjectNew = entry_new;
278 obj->fnObjectFree = free;
279
280 timer->event = CreateEventA(NULL, TRUE, FALSE, NULL);
281 if (!timer->event)
282 goto fail;
283
284 timer->mainevent = CreateEventA(NULL, TRUE, FALSE, NULL);
285 if (!timer->mainevent)
286 goto fail;
287
288#if defined(FREERDP_TIMER_SUPPORTED)
289 timer->running = true;
290 timer->thread = CreateThread(NULL, 0, timer_thread, timer, 0, NULL);
291 if (!timer->thread)
292 goto fail;
293#endif
294 return timer;
295
296fail:
297 freerdp_timer_free(timer);
298 return NULL;
299}
300
301static BOOL runExpiredTimerOnMainloop(void* data, WINPR_ATTR_UNUSED size_t index,
302 WINPR_ATTR_UNUSED va_list ap)
303{
304 timer_entry_t* entry = data;
305 WINPR_ASSERT(entry);
306 WINPR_ASSERT(entry->cb);
307
308 /* Skip events not on mainloop */
309 if (!entry->mainloop)
310 return TRUE;
311
312 /* Skip all timers that have been deactivated. */
313 if (entry->intervallNS == 0)
314 return TRUE;
315
316 uint64_t* now = va_arg(ap, uint64_t*);
317 WINPR_ASSERT(now);
318
319 if (entry->nextRunTimeNS > *now)
320 return TRUE;
321
322 runTimerEvent(entry, now);
323 return TRUE;
324}
325
326bool freerdp_timer_poll(FreeRDPTimer* timer)
327{
328 WINPR_ASSERT(timer);
329
330 if (WaitForSingleObject(timer->mainevent, 0) != WAIT_OBJECT_0)
331 return true;
332
333 ArrayList_Lock(timer->entries);
334 (void)ResetEvent(timer->mainevent);
335 uint64_t now = winpr_GetTickCount64NS();
336 ArrayList_ForEach(timer->entries, runExpiredTimerOnMainloop, &now);
337 (void)SetEvent(timer->event); // Trigger a wakeup of timer thread to reschedule
338 ArrayList_Unlock(timer->entries);
339 return true;
340}
341
342HANDLE freerdp_timer_get_event(FreeRDPTimer* timer)
343{
344 WINPR_ASSERT(timer);
345 return timer->mainevent;
346}
This struct contains function pointer to initialize/free objects.
Definition collections.h:57