Coverage Report

Created: 2025-11-24 06:38

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
#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
16.5k
{
166
16.5k
  WINPR_ASSERT(timer);
167
168
16.5k
  bool mainloop = false;
169
16.5k
  uint64_t next = UINT64_MAX;
170
16.5k
  uint64_t now = winpr_GetTickCount64NS();
171
172
16.5k
  ArrayList_Lock(timer->entries);
173
16.5k
  ArrayList_ForEach(timer->entries, runExpiredTimer, &now, &mainloop);
174
16.5k
  if (mainloop)
175
0
    (void)SetEvent(timer->mainevent);
176
177
16.5k
  size_t pos = 0;
178
16.5k
  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.5k
  ArrayList_Unlock(timer->entries);
192
193
16.5k
  return next;
194
16.5k
}
195
196
static DWORD WINAPI timer_thread(LPVOID arg)
197
17.6k
{
198
17.6k
  FreeRDPTimer* timer = arg;
199
17.6k
  WINPR_ASSERT(timer);
200
201
  // TODO: Currently we only support ms granularity, look for ways to improve
202
17.6k
  DWORD timeout = INFINITE;
203
17.6k
  HANDLE handles[2] = { utils_get_abort_event(timer->rdp), timer->event };
204
205
34.1k
  while (timer->running &&
206
17.6k
         (WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, timeout) != WAIT_OBJECT_0))
207
16.5k
  {
208
16.5k
    (void)ResetEvent(timer->event);
209
16.5k
    const uint64_t next = expire_and_reschedule(timer);
210
16.5k
    const uint64_t now = winpr_GetTickCount64NS();
211
16.5k
    if (next == UINT64_MAX)
212
16.5k
    {
213
16.5k
      timeout = INFINITE;
214
16.5k
      continue;
215
16.5k
    }
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.6k
  return 0;
229
17.6k
}
230
#endif
231
232
void freerdp_timer_free(FreeRDPTimer* timer)
233
17.6k
{
234
17.6k
  if (!timer)
235
0
    return;
236
237
17.6k
  timer->running = false;
238
17.6k
  if (timer->event)
239
17.6k
    (void)SetEvent(timer->event);
240
241
17.6k
  if (timer->thread)
242
17.6k
  {
243
17.6k
    (void)WaitForSingleObject(timer->thread, INFINITE);
244
17.6k
    CloseHandle(timer->thread);
245
17.6k
  }
246
17.6k
  if (timer->mainevent)
247
17.6k
    CloseHandle(timer->mainevent);
248
17.6k
  if (timer->event)
249
17.6k
    CloseHandle(timer->event);
250
17.6k
  ArrayList_Free(timer->entries);
251
17.6k
  free(timer);
252
17.6k
}
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 NULL;
259
260
0
  timer_entry_t* copy = calloc(1, sizeof(timer_entry_t));
261
0
  if (!copy)
262
0
    return NULL;
263
0
  *copy = *entry;
264
0
  return copy;
265
0
}
266
267
FreeRDPTimer* freerdp_timer_new(rdpRdp* rdp)
268
17.6k
{
269
17.6k
  WINPR_ASSERT(rdp);
270
17.6k
  FreeRDPTimer* timer = calloc(1, sizeof(FreeRDPTimer));
271
17.6k
  if (!timer)
272
0
    return NULL;
273
17.6k
  timer->rdp = rdp;
274
275
17.6k
  timer->entries = ArrayList_New(TRUE);
276
17.6k
  if (!timer->entries)
277
0
    goto fail;
278
17.6k
  wObject* obj = ArrayList_Object(timer->entries);
279
17.6k
  WINPR_ASSERT(obj);
280
17.6k
  obj->fnObjectNew = entry_new;
281
17.6k
  obj->fnObjectFree = free;
282
283
17.6k
  timer->event = CreateEventA(NULL, TRUE, FALSE, NULL);
284
17.6k
  if (!timer->event)
285
0
    goto fail;
286
287
17.6k
  timer->mainevent = CreateEventA(NULL, TRUE, FALSE, NULL);
288
17.6k
  if (!timer->mainevent)
289
0
    goto fail;
290
291
17.6k
#if defined(FREERDP_TIMER_SUPPORTED)
292
17.6k
  timer->running = true;
293
17.6k
  timer->thread = CreateThread(NULL, 0, timer_thread, timer, 0, NULL);
294
17.6k
  if (!timer->thread)
295
0
    goto fail;
296
17.6k
#endif
297
17.6k
  return timer;
298
299
0
fail:
300
0
  freerdp_timer_free(timer);
301
0
  return NULL;
302
17.6k
}
303
304
static BOOL runExpiredTimerOnMainloop(void* data, WINPR_ATTR_UNUSED size_t index,
305
                                      WINPR_ATTR_UNUSED va_list ap)
306
0
{
307
0
  timer_entry_t* entry = data;
308
0
  WINPR_ASSERT(entry);
309
0
  WINPR_ASSERT(entry->cb);
310
311
  /* Skip events not on mainloop */
312
0
  if (!entry->mainloop)
313
0
    return TRUE;
314
315
  /* Skip all timers that have been deactivated. */
316
0
  if (entry->intervallNS == 0)
317
0
    return TRUE;
318
319
0
  uint64_t* now = va_arg(ap, uint64_t*);
320
0
  WINPR_ASSERT(now);
321
322
0
  if (entry->nextRunTimeNS > *now)
323
0
    return TRUE;
324
325
0
  runTimerEvent(entry, now);
326
0
  return TRUE;
327
0
}
328
329
bool freerdp_timer_poll(FreeRDPTimer* timer)
330
0
{
331
0
  WINPR_ASSERT(timer);
332
333
0
  if (WaitForSingleObject(timer->mainevent, 0) != WAIT_OBJECT_0)
334
0
    return true;
335
336
0
  ArrayList_Lock(timer->entries);
337
0
  (void)ResetEvent(timer->mainevent);
338
0
  uint64_t now = winpr_GetTickCount64NS();
339
0
  ArrayList_ForEach(timer->entries, runExpiredTimerOnMainloop, &now);
340
0
  (void)SetEvent(timer->event); // Trigger a wakeup of timer thread to reschedule
341
0
  ArrayList_Unlock(timer->entries);
342
0
  return true;
343
0
}
344
345
HANDLE freerdp_timer_get_event(FreeRDPTimer* timer)
346
0
{
347
0
  WINPR_ASSERT(timer);
348
0
  return timer->mainevent;
349
0
}