Coverage Report

Created: 2026-04-12 07:03

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