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