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