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 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 NULL;
259
260 timer_entry_t* copy = calloc(1, sizeof(timer_entry_t));
261 if (!copy)
262 return NULL;
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 NULL;
273 timer->rdp = rdp;
274
275 timer->entries = ArrayList_New(TRUE);
276 if (!timer->entries)
277 goto fail;
278 wObject* obj = ArrayList_Object(timer->entries);
279 WINPR_ASSERT(obj);
280 obj->fnObjectNew = entry_new;
281 obj->fnObjectFree = free;
282
283 timer->event = CreateEventA(NULL, TRUE, FALSE, NULL);
284 if (!timer->event)
285 goto fail;
286
287 timer->mainevent = CreateEventA(NULL, TRUE, FALSE, NULL);
288 if (!timer->mainevent)
289 goto fail;
290
291#if defined(FREERDP_TIMER_SUPPORTED)
292 timer->running = true;
293 timer->thread = CreateThread(NULL, 0, timer_thread, timer, 0, NULL);
294 if (!timer->thread)
295 goto fail;
296#endif
297 return timer;
298
299fail:
300 freerdp_timer_free(timer);
301 return NULL;
302}
303
304static BOOL runExpiredTimerOnMainloop(void* data, WINPR_ATTR_UNUSED size_t index,
305 WINPR_ATTR_UNUSED va_list ap)
306{
307 timer_entry_t* entry = data;
308 WINPR_ASSERT(entry);
309 WINPR_ASSERT(entry->cb);
310
311 /* Skip events not on mainloop */
312 if (!entry->mainloop)
313 return TRUE;
314
315 /* Skip all timers that have been deactivated. */
316 if (entry->intervallNS == 0)
317 return TRUE;
318
319 uint64_t* now = va_arg(ap, uint64_t*);
320 WINPR_ASSERT(now);
321
322 if (entry->nextRunTimeNS > *now)
323 return TRUE;
324
325 runTimerEvent(entry, now);
326 return TRUE;
327}
328
329bool freerdp_timer_poll(FreeRDPTimer* timer)
330{
331 WINPR_ASSERT(timer);
332
333 if (WaitForSingleObject(timer->mainevent, 0) != WAIT_OBJECT_0)
334 return true;
335
336 ArrayList_Lock(timer->entries);
337 (void)ResetEvent(timer->mainevent);
338 uint64_t now = winpr_GetTickCount64NS();
339 ArrayList_ForEach(timer->entries, runExpiredTimerOnMainloop, &now);
340 (void)SetEvent(timer->event); // Trigger a wakeup of timer thread to reschedule
341 ArrayList_Unlock(timer->entries);
342 return true;
343}
344
345HANDLE freerdp_timer_get_event(FreeRDPTimer* timer)
346{
347 WINPR_ASSERT(timer);
348 return timer->mainevent;
349}
This struct contains function pointer to initialize/free objects.
Definition collections.h:57