Coverage Report

Created: 2025-07-01 06:46

/src/FreeRDP/libfreerdp/core/timer.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * Timer implementation
4
 *
5
 * Copyright 2025 Armin Novak <anovak@thincast.com>
6
 * Copyright 2025 Thincast Technologies GmbH
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
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
36
typedef 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
47
struct 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
58
FreeRDP_TimerID freerdp_timer_add(rdpContext* context, uint64_t intervalNS,
59
                                  FreeRDP_TimerCallback callback, void* userdata, bool mainloop)
60
0
{
61
0
  WINPR_ASSERT(context);
62
0
  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
0
  FreeRDPTimer* timer = context->rdp->timer;
74
0
  WINPR_ASSERT(timer);
75
76
0
  if ((intervalNS == 0) || !callback)
77
0
    return false;
78
79
0
  const uint64_t cur = winpr_GetTickCount64NS();
80
0
  const timer_entry_t entry = { .id = ++timer->maxIdx,
81
0
                              .intervallNS = intervalNS,
82
0
                              .nextRunTimeNS = cur + intervalNS,
83
0
                              .cb = callback,
84
0
                              .userdata = userdata,
85
0
                              .context = context,
86
0
                              .mainloop = mainloop };
87
88
0
  if (!ArrayList_Append(timer->entries, &entry))
89
0
    return 0;
90
0
  (void)SetEvent(timer->event);
91
0
  return entry.id;
92
0
#endif
93
0
}
94
95
static BOOL foreach_entry(void* data, WINPR_ATTR_UNUSED size_t index, va_list ap)
96
0
{
97
0
  timer_entry_t* entry = data;
98
0
  WINPR_ASSERT(entry);
99
100
0
  FreeRDP_TimerID id = va_arg(ap, FreeRDP_TimerID);
101
102
0
  if (entry->id == id)
103
0
  {
104
    /* Mark the timer to be disabled.
105
     * It will be removed on next rescheduling event
106
     */
107
0
    entry->intervallNS = 0;
108
0
    return FALSE;
109
0
  }
110
0
  return TRUE;
111
0
}
112
113
bool freerdp_timer_remove(rdpContext* context, FreeRDP_TimerID id)
114
0
{
115
0
  WINPR_ASSERT(context);
116
0
  WINPR_ASSERT(context->rdp);
117
118
0
  FreeRDPTimer* timer = context->rdp->timer;
119
0
  WINPR_ASSERT(timer);
120
121
0
  return !ArrayList_ForEach(timer->entries, foreach_entry, id);
122
0
}
123
124
static BOOL runTimerEvent(timer_entry_t* entry, uint64_t* now)
125
0
{
126
0
  WINPR_ASSERT(entry);
127
128
0
  entry->intervallNS =
129
0
      entry->cb(entry->context, entry->userdata, entry->id, *now, entry->intervallNS);
130
0
  *now = winpr_GetTickCount64NS();
131
0
  entry->nextRunTimeNS = *now + entry->intervallNS;
132
0
  return TRUE;
133
0
}
134
135
static BOOL runExpiredTimer(void* data, WINPR_ATTR_UNUSED size_t index,
136
                            WINPR_ATTR_UNUSED va_list ap)
137
0
{
138
0
  timer_entry_t* entry = data;
139
0
  WINPR_ASSERT(entry);
140
0
  WINPR_ASSERT(entry->cb);
141
142
  /* Skip all timers that have been deactivated. */
143
0
  if (entry->intervallNS == 0)
144
0
    return TRUE;
145
146
0
  uint64_t* now = va_arg(ap, uint64_t*);
147
0
  WINPR_ASSERT(now);
148
149
0
  bool* mainloop = va_arg(ap, bool*);
150
0
  WINPR_ASSERT(mainloop);
151
152
0
  if (entry->nextRunTimeNS > *now)
153
0
    return TRUE;
154
155
0
  if (entry->mainloop)
156
0
    *mainloop = true;
157
0
  else
158
0
    runTimerEvent(entry, now);
159
160
0
  return TRUE;
161
0
}
162
163
#if defined(FREERDP_TIMER_SUPPORTED)
164
static uint64_t expire_and_reschedule(FreeRDPTimer* timer)
165
0
{
166
0
  WINPR_ASSERT(timer);
167
168
0
  bool mainloop = false;
169
0
  uint64_t next = UINT64_MAX;
170
0
  uint64_t now = winpr_GetTickCount64NS();
171
172
0
  ArrayList_Lock(timer->entries);
173
0
  ArrayList_ForEach(timer->entries, runExpiredTimer, &now, &mainloop);
174
0
  if (mainloop)
175
0
    (void)SetEvent(timer->mainevent);
176
177
0
  size_t pos = 0;
178
0
  while (pos < ArrayList_Count(timer->entries))
179
0
  {
180
0
    timer_entry_t* entry = ArrayList_GetItem(timer->entries, pos);
181
0
    WINPR_ASSERT(entry);
182
0
    if (entry->intervallNS == 0)
183
0
    {
184
0
      ArrayList_RemoveAt(timer->entries, pos);
185
0
      continue;
186
0
    }
187
0
    if (next > entry->nextRunTimeNS)
188
0
      next = entry->nextRunTimeNS;
189
0
    pos++;
190
0
  }
191
0
  ArrayList_Unlock(timer->entries);
192
193
0
  if (next == UINT64_MAX)
194
0
    return 0;
195
0
  return next;
196
0
}
197
198
static DWORD WINAPI timer_thread(LPVOID arg)
199
0
{
200
0
  FreeRDPTimer* timer = arg;
201
0
  WINPR_ASSERT(timer);
202
203
  // TODO: Currently we only support ms granularity, look for ways to improve
204
0
  DWORD timeout = INFINITE;
205
0
  HANDLE handles[2] = { utils_get_abort_event(timer->rdp), timer->event };
206
207
0
  while (timer->running &&
208
0
         (WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, timeout) != WAIT_OBJECT_0))
209
0
  {
210
0
    (void)ResetEvent(timer->event);
211
0
    const uint64_t next = expire_and_reschedule(timer);
212
0
    const uint64_t now = winpr_GetTickCount64NS();
213
0
    if (now >= next)
214
0
    {
215
0
      timeout = INFINITE;
216
0
      continue;
217
0
    }
218
219
0
    const uint64_t diff = next - now;
220
0
    const uint64_t diffMS = diff / 1000;
221
0
    timeout = INFINITE;
222
0
    if (diffMS < INFINITE)
223
0
      timeout = (uint32_t)diffMS;
224
0
  }
225
0
  return 0;
226
0
}
227
#endif
228
229
void freerdp_timer_free(FreeRDPTimer* timer)
230
0
{
231
0
  if (!timer)
232
0
    return;
233
234
0
  timer->running = false;
235
0
  if (timer->event)
236
0
    (void)SetEvent(timer->event);
237
238
0
  if (timer->thread)
239
0
  {
240
0
    (void)WaitForSingleObject(timer->thread, INFINITE);
241
0
    CloseHandle(timer->thread);
242
0
  }
243
0
  if (timer->mainevent)
244
0
    CloseHandle(timer->mainevent);
245
0
  if (timer->event)
246
0
    CloseHandle(timer->event);
247
0
  ArrayList_Free(timer->entries);
248
0
  free(timer);
249
0
}
250
251
static void* entry_new(const void* val)
252
0
{
253
0
  const timer_entry_t* entry = val;
254
0
  if (!entry)
255
0
    return NULL;
256
257
0
  timer_entry_t* copy = calloc(1, sizeof(timer_entry_t));
258
0
  if (!copy)
259
0
    return NULL;
260
0
  *copy = *entry;
261
0
  return copy;
262
0
}
263
264
FreeRDPTimer* freerdp_timer_new(rdpRdp* rdp)
265
0
{
266
0
  WINPR_ASSERT(rdp);
267
0
  FreeRDPTimer* timer = calloc(1, sizeof(FreeRDPTimer));
268
0
  if (!timer)
269
0
    return NULL;
270
0
  timer->rdp = rdp;
271
272
0
  timer->entries = ArrayList_New(TRUE);
273
0
  if (!timer->entries)
274
0
    goto fail;
275
0
  wObject* obj = ArrayList_Object(timer->entries);
276
0
  WINPR_ASSERT(obj);
277
0
  obj->fnObjectNew = entry_new;
278
0
  obj->fnObjectFree = free;
279
280
0
  timer->event = CreateEventA(NULL, TRUE, FALSE, NULL);
281
0
  if (!timer->event)
282
0
    goto fail;
283
284
0
  timer->mainevent = CreateEventA(NULL, TRUE, FALSE, NULL);
285
0
  if (!timer->mainevent)
286
0
    goto fail;
287
288
0
#if defined(FREERDP_TIMER_SUPPORTED)
289
0
  timer->running = true;
290
0
  timer->thread = CreateThread(NULL, 0, timer_thread, timer, 0, NULL);
291
0
  if (!timer->thread)
292
0
    goto fail;
293
0
#endif
294
0
  return timer;
295
296
0
fail:
297
0
  freerdp_timer_free(timer);
298
0
  return NULL;
299
0
}
300
301
static BOOL runExpiredTimerOnMainloop(void* data, WINPR_ATTR_UNUSED size_t index,
302
                                      WINPR_ATTR_UNUSED va_list ap)
303
0
{
304
0
  timer_entry_t* entry = data;
305
0
  WINPR_ASSERT(entry);
306
0
  WINPR_ASSERT(entry->cb);
307
308
  /* Skip events not on mainloop */
309
0
  if (!entry->mainloop)
310
0
    return TRUE;
311
312
  /* Skip all timers that have been deactivated. */
313
0
  if (entry->intervallNS == 0)
314
0
    return TRUE;
315
316
0
  uint64_t* now = va_arg(ap, uint64_t*);
317
0
  WINPR_ASSERT(now);
318
319
0
  if (entry->nextRunTimeNS > *now)
320
0
    return TRUE;
321
322
0
  runTimerEvent(entry, now);
323
0
  return TRUE;
324
0
}
325
326
bool freerdp_timer_poll(FreeRDPTimer* timer)
327
0
{
328
0
  WINPR_ASSERT(timer);
329
330
0
  if (WaitForSingleObject(timer->mainevent, 0) != WAIT_OBJECT_0)
331
0
    return true;
332
333
0
  ArrayList_Lock(timer->entries);
334
0
  (void)ResetEvent(timer->mainevent);
335
0
  uint64_t now = winpr_GetTickCount64NS();
336
0
  ArrayList_ForEach(timer->entries, runExpiredTimerOnMainloop, &now);
337
0
  (void)SetEvent(timer->event); // Trigger a wakeup of timer thread to reschedule
338
0
  ArrayList_Unlock(timer->entries);
339
0
  return true;
340
0
}
341
342
HANDLE freerdp_timer_get_event(FreeRDPTimer* timer)
343
0
{
344
0
  WINPR_ASSERT(timer);
345
0
  return timer->mainevent;
346
0
}