mirror of
https://github.com/FreeRDP/FreeRDP.git
synced 2025-06-03 00:00:20 +00:00
[core,timer] Add a timer implementation
Adds a timer implementation (bound to a RDP context) that is capable of handling multiple timers simultaneously.
This commit is contained in:
parent
374707d4fa
commit
72a09b1675
101
include/freerdp/timer.h
Normal file
101
include/freerdp/timer.h
Normal file
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Timer implementation
|
||||
*
|
||||
* Copyright 2025 Armin Novak <anovak@thincast.com>
|
||||
* Copyright 2025 Thincast Technologies GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <freerdp/api.h>
|
||||
#include <freerdp/types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/** Type definition for timer IDs
|
||||
* @since version 3.16.0
|
||||
*/
|
||||
typedef uint64_t FreeRDP_TimerID;
|
||||
|
||||
/** @brief Callback function pointer type definition.
|
||||
* An expired timer will be called, depending on \ref mainloop argument of \ref
|
||||
* freerdp_timer_add, background thread or mainloop. This also greatly influence jitter and
|
||||
* precision of the call. If called by \b mainloop, which might be blocked, delays for up to
|
||||
* 100ms are to be expected. If called from a background thread no locking is performed, so be
|
||||
* sure to lock your resources where necessary.
|
||||
*
|
||||
*
|
||||
* @param context The RDP context this timer belongs to
|
||||
* @param userdata Custom userdata provided by \ref freerdp_timer_add
|
||||
* @param timerID The timer ID that expired
|
||||
* @param timestamp The current timestamp for the call. The base is not specified, but the
|
||||
* resolution is in nanoseconds.
|
||||
* @param interval The last interval value
|
||||
*
|
||||
* @return A new interval (might differ from the last one set) or \b 0 to disable the timer
|
||||
*
|
||||
* @since version 3.16.0
|
||||
*/
|
||||
typedef uint64_t (*FreeRDP_TimerCallback)(rdpContext* context, void* userdata,
|
||||
FreeRDP_TimerID timerID, uint64_t timestamp,
|
||||
uint64_t interval);
|
||||
|
||||
/** @brief Add a new timer to the list of running timers
|
||||
*
|
||||
* @note While the API allows nano second precision the execution time might vary depending on
|
||||
* various circumstances.
|
||||
* \b mainloop executed callbacks will have a huge jitter and execution times are expected to be
|
||||
* delayed up to multiple 10s of milliseconds. Current implementation also does not guarantee
|
||||
* more than 10ms granularity even for background thread callbacks, but that might improve with
|
||||
* newer versions.
|
||||
*
|
||||
* @note Current implementation limits all timers to be executed by a single background thread.
|
||||
* So ensure your callbacks are not blocking for a long time as both, \b mainloop and background
|
||||
* thread executed callbacks will delay execution of other tasks.
|
||||
*
|
||||
* @param context The RDP context the timer belongs to
|
||||
* @param intervalNS The (first) timer expiration interval in nanoseconds
|
||||
* @param callback The function to be called when the timer expires. Must not be \b NULL
|
||||
* @param userdata Custom userdata passed to the callback. The pointer is only passed, it is up
|
||||
* to the user to ensure the data exists when the timer expires.
|
||||
* @param mainloop \b true run the callback in mainloop context or \b false from background
|
||||
* thread
|
||||
* @return A new timer ID or \b 0 in case of failure
|
||||
* @since version 3.16.0
|
||||
*/
|
||||
FREERDP_API FreeRDP_TimerID freerdp_timer_add(rdpContext* context, uint64_t intervalNS,
|
||||
FreeRDP_TimerCallback callback, void* userdata,
|
||||
bool mainloop);
|
||||
|
||||
/** @brief Remove a timer from the list of running timers
|
||||
*
|
||||
* @param context The RDP context the timer belongs to
|
||||
* @param id The timer ID to remove
|
||||
*
|
||||
* @return \b true if the timer was removed, \b false otherwise
|
||||
* @since version 3.16.0
|
||||
*/
|
||||
FREERDP_API bool freerdp_timer_remove(rdpContext* context, FreeRDP_TimerID id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -149,6 +149,8 @@ set(${MODULE_PREFIX}_SRCS
|
||||
rdstls.h
|
||||
aad.c
|
||||
aad.h
|
||||
timer.c
|
||||
timer.h
|
||||
)
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} ${${MODULE_PREFIX}_GATEWAY_SRCS})
|
||||
|
@ -390,16 +390,16 @@ DWORD freerdp_get_event_handles(rdpContext* context, HANDLE* events, DWORD count
|
||||
WINPR_ASSERT(context->rdp);
|
||||
WINPR_ASSERT(events || (count == 0));
|
||||
|
||||
nCount += transport_get_event_handles(context->rdp->transport, events, count);
|
||||
|
||||
if (nCount == 0)
|
||||
const size_t rrc = rdp_get_event_handles(context->rdp, &events[nCount], count - nCount);
|
||||
if (rrc == 0)
|
||||
return 0;
|
||||
|
||||
nCount += WINPR_ASSERTING_INT_CAST(uint32_t, rrc);
|
||||
|
||||
if (events && (nCount < count + 2))
|
||||
{
|
||||
events[nCount++] = freerdp_channels_get_event_handle(context->instance);
|
||||
events[nCount++] = getChannelErrorEventHandle(context);
|
||||
events[nCount++] = utils_get_abort_event(context->rdp);
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
|
@ -2279,6 +2279,8 @@ int rdp_check_fds(rdpRdp* rdp)
|
||||
|
||||
if (status < 0)
|
||||
WLog_Print(rdp->log, WLOG_DEBUG, "transport_check_fds() - %i", status);
|
||||
else
|
||||
status = freerdp_timer_poll(rdp->timer);
|
||||
|
||||
return status;
|
||||
}
|
||||
@ -2301,6 +2303,46 @@ BOOL freerdp_get_stats(rdpRdp* rdp, UINT64* inBytes, UINT64* outBytes, UINT64* i
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static bool rdp_new_common(rdpRdp* rdp)
|
||||
{
|
||||
WINPR_ASSERT(rdp);
|
||||
|
||||
bool rc = false;
|
||||
rdp->transport = transport_new(rdp->context);
|
||||
if (!rdp->transport)
|
||||
goto fail;
|
||||
|
||||
if (rdp->io)
|
||||
{
|
||||
if (!transport_set_io_callbacks(rdp->transport, rdp->io))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
rdp->aad = aad_new(rdp->context, rdp->transport);
|
||||
if (!rdp->aad)
|
||||
goto fail;
|
||||
|
||||
rdp->nego = nego_new(rdp->transport);
|
||||
if (!rdp->nego)
|
||||
goto fail;
|
||||
|
||||
rdp->mcs = mcs_new(rdp->transport);
|
||||
if (!rdp->mcs)
|
||||
goto fail;
|
||||
|
||||
rdp->license = license_new(rdp);
|
||||
if (!rdp->license)
|
||||
goto fail;
|
||||
|
||||
rdp->fastpath = fastpath_new(rdp);
|
||||
if (!rdp->fastpath)
|
||||
goto fail;
|
||||
|
||||
rc = true;
|
||||
fail:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate new RDP module.
|
||||
* @return new RDP module
|
||||
@ -2308,9 +2350,8 @@ BOOL freerdp_get_stats(rdpRdp* rdp, UINT64* inBytes, UINT64* outBytes, UINT64* i
|
||||
|
||||
rdpRdp* rdp_new(rdpContext* context)
|
||||
{
|
||||
rdpRdp* rdp = NULL;
|
||||
DWORD flags = 0;
|
||||
rdp = (rdpRdp*)calloc(1, sizeof(rdpRdp));
|
||||
rdpRdp* rdp = (rdpRdp*)calloc(1, sizeof(rdpRdp));
|
||||
|
||||
if (!rdp)
|
||||
return NULL;
|
||||
@ -2356,9 +2397,7 @@ rdpRdp* rdp_new(rdpContext* context)
|
||||
#endif
|
||||
}
|
||||
|
||||
rdp->transport = transport_new(context);
|
||||
|
||||
if (!rdp->transport)
|
||||
if (!rdp_new_common(rdp))
|
||||
goto fail;
|
||||
|
||||
{
|
||||
@ -2371,15 +2410,6 @@ rdpRdp* rdp_new(rdpContext* context)
|
||||
*rdp->io = *io;
|
||||
}
|
||||
|
||||
rdp->aad = aad_new(context, rdp->transport);
|
||||
if (!rdp->aad)
|
||||
goto fail;
|
||||
|
||||
rdp->license = license_new(rdp);
|
||||
|
||||
if (!rdp->license)
|
||||
goto fail;
|
||||
|
||||
rdp->input = input_new(rdp);
|
||||
|
||||
if (!rdp->input)
|
||||
@ -2390,21 +2420,6 @@ rdpRdp* rdp_new(rdpContext* context)
|
||||
if (!rdp->update)
|
||||
goto fail;
|
||||
|
||||
rdp->fastpath = fastpath_new(rdp);
|
||||
|
||||
if (!rdp->fastpath)
|
||||
goto fail;
|
||||
|
||||
rdp->nego = nego_new(rdp->transport);
|
||||
|
||||
if (!rdp->nego)
|
||||
goto fail;
|
||||
|
||||
rdp->mcs = mcs_new(rdp->transport);
|
||||
|
||||
if (!rdp->mcs)
|
||||
goto fail;
|
||||
|
||||
rdp->redirection = redirection_new();
|
||||
|
||||
if (!rdp->redirection)
|
||||
@ -2438,6 +2453,11 @@ rdpRdp* rdp_new(rdpContext* context)
|
||||
rdp->abortEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (!rdp->abortEvent)
|
||||
goto fail;
|
||||
|
||||
rdp->timer = freerdp_timer_new(rdp);
|
||||
if (!rdp->timer)
|
||||
goto fail;
|
||||
|
||||
return rdp;
|
||||
|
||||
fail:
|
||||
@ -2462,12 +2482,14 @@ static void rdp_reset_free(rdpRdp* rdp)
|
||||
rdp->fips_decrypt = NULL;
|
||||
(void)security_unlock(rdp);
|
||||
|
||||
aad_free(rdp->aad);
|
||||
mcs_free(rdp->mcs);
|
||||
nego_free(rdp->nego);
|
||||
license_free(rdp->license);
|
||||
transport_free(rdp->transport);
|
||||
fastpath_free(rdp->fastpath);
|
||||
|
||||
rdp->aad = NULL;
|
||||
rdp->mcs = NULL;
|
||||
rdp->nego = NULL;
|
||||
rdp->license = NULL;
|
||||
@ -2478,15 +2500,10 @@ static void rdp_reset_free(rdpRdp* rdp)
|
||||
BOOL rdp_reset(rdpRdp* rdp)
|
||||
{
|
||||
BOOL rc = TRUE;
|
||||
rdpContext* context = NULL;
|
||||
rdpSettings* settings = NULL;
|
||||
|
||||
WINPR_ASSERT(rdp);
|
||||
|
||||
context = rdp->context;
|
||||
WINPR_ASSERT(context);
|
||||
|
||||
settings = rdp->settings;
|
||||
rdpSettings* settings = rdp->settings;
|
||||
WINPR_ASSERT(settings);
|
||||
|
||||
bulk_reset(rdp->bulk);
|
||||
@ -2505,41 +2522,13 @@ BOOL rdp_reset(rdpRdp* rdp)
|
||||
if (!rc)
|
||||
goto fail;
|
||||
|
||||
rc = FALSE;
|
||||
rdp->transport = transport_new(context);
|
||||
if (!rdp->transport)
|
||||
goto fail;
|
||||
|
||||
if (rdp->io)
|
||||
{
|
||||
if (!transport_set_io_callbacks(rdp->transport, rdp->io))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
aad_free(rdp->aad);
|
||||
rdp->aad = aad_new(context, rdp->transport);
|
||||
if (!rdp->aad)
|
||||
goto fail;
|
||||
|
||||
rdp->nego = nego_new(rdp->transport);
|
||||
if (!rdp->nego)
|
||||
goto fail;
|
||||
|
||||
rdp->mcs = mcs_new(rdp->transport);
|
||||
if (!rdp->mcs)
|
||||
rc = rdp_new_common(rdp);
|
||||
if (!rc)
|
||||
goto fail;
|
||||
|
||||
if (!transport_set_layer(rdp->transport, TRANSPORT_LAYER_TCP))
|
||||
goto fail;
|
||||
|
||||
rdp->license = license_new(rdp);
|
||||
if (!rdp->license)
|
||||
goto fail;
|
||||
|
||||
rdp->fastpath = fastpath_new(rdp);
|
||||
if (!rdp->fastpath)
|
||||
goto fail;
|
||||
|
||||
rdp->errorInfo = 0;
|
||||
rc = rdp_finalize_reset_flags(rdp, TRUE);
|
||||
|
||||
@ -2556,6 +2545,7 @@ void rdp_free(rdpRdp* rdp)
|
||||
{
|
||||
if (rdp)
|
||||
{
|
||||
freerdp_timer_free(rdp->timer);
|
||||
rdp_reset_free(rdp);
|
||||
|
||||
freerdp_settings_free(rdp->settings);
|
||||
@ -3147,3 +3137,18 @@ void rdp_log_build_warnings(rdpRdp* rdp)
|
||||
option_is_runtime_checks);
|
||||
log_build_warn_ssl(rdp);
|
||||
}
|
||||
|
||||
size_t rdp_get_event_handles(rdpRdp* rdp, HANDLE* handles, uint32_t count)
|
||||
{
|
||||
size_t nCount = transport_get_event_handles(rdp->transport, handles, count);
|
||||
|
||||
if (nCount == 0)
|
||||
return 0;
|
||||
|
||||
if (count < nCount + 2UL)
|
||||
return 0;
|
||||
|
||||
handles[nCount++] = utils_get_abort_event(rdp);
|
||||
handles[nCount++] = freerdp_timer_get_event(rdp->timer);
|
||||
return nCount;
|
||||
}
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include "redirection.h"
|
||||
#include "capabilities.h"
|
||||
#include "channels.h"
|
||||
#include "timer.h"
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/settings.h>
|
||||
@ -207,6 +208,7 @@ struct rdp_rdp
|
||||
wLog* log;
|
||||
char log_context[64];
|
||||
WINPR_JSON* wellknown;
|
||||
FreeRDPTimer* timer;
|
||||
};
|
||||
|
||||
FREERDP_LOCAL BOOL rdp_read_security_header(rdpRdp* rdp, wStream* s, UINT16* flags, UINT16* length);
|
||||
@ -304,4 +306,6 @@ BOOL rdp_reset_runtime_settings(rdpRdp* rdp);
|
||||
|
||||
void rdp_log_build_warnings(rdpRdp* rdp);
|
||||
|
||||
FREERDP_LOCAL size_t rdp_get_event_handles(rdpRdp* rdp, HANDLE* handles, uint32_t count);
|
||||
|
||||
#endif /* FREERDP_LIB_CORE_RDP_H */
|
||||
|
321
libfreerdp/core/timer.c
Normal file
321
libfreerdp/core/timer.c
Normal file
@ -0,0 +1,321 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Timer implementation
|
||||
*
|
||||
* Copyright 2025 Armin Novak <anovak@thincast.com>
|
||||
* Copyright 2025 Thincast Technologies GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <winpr/thread.h>
|
||||
#include <winpr/collections.h>
|
||||
|
||||
#include <freerdp/timer.h>
|
||||
#include "rdp.h"
|
||||
#include "utils.h"
|
||||
#include "timer.h"
|
||||
|
||||
typedef ALIGN64 struct
|
||||
{
|
||||
FreeRDP_TimerID id;
|
||||
uint64_t intervallNS;
|
||||
uint64_t nextRunTimeNS;
|
||||
FreeRDP_TimerCallback cb;
|
||||
void* userdata;
|
||||
rdpContext* context;
|
||||
bool mainloop;
|
||||
} timer_entry_t;
|
||||
|
||||
struct ALIGN64 freerdp_timer_s
|
||||
{
|
||||
rdpRdp* rdp;
|
||||
wArrayList* entries;
|
||||
HANDLE thread;
|
||||
HANDLE event;
|
||||
HANDLE mainevent;
|
||||
size_t maxIdx;
|
||||
bool running;
|
||||
};
|
||||
|
||||
FreeRDP_TimerID freerdp_timer_add(rdpContext* context, uint64_t intervalNS,
|
||||
FreeRDP_TimerCallback callback, void* userdata, bool mainloop)
|
||||
{
|
||||
WINPR_ASSERT(context);
|
||||
WINPR_ASSERT(context->rdp);
|
||||
|
||||
FreeRDPTimer* timer = context->rdp->timer;
|
||||
WINPR_ASSERT(timer);
|
||||
|
||||
if ((intervalNS == 0) || !callback)
|
||||
return false;
|
||||
|
||||
const uint64_t cur = winpr_GetTickCount64NS();
|
||||
const timer_entry_t entry = { .id = timer->maxIdx++,
|
||||
.intervallNS = intervalNS,
|
||||
.nextRunTimeNS = cur + intervalNS,
|
||||
.cb = callback,
|
||||
.userdata = userdata,
|
||||
.context = context,
|
||||
.mainloop = mainloop };
|
||||
|
||||
if (!ArrayList_Append(timer->entries, &entry))
|
||||
return 0;
|
||||
(void)SetEvent(timer->event);
|
||||
return entry.id;
|
||||
}
|
||||
|
||||
static BOOL foreach_entry(void* data, WINPR_ATTR_UNUSED size_t index, va_list ap)
|
||||
{
|
||||
timer_entry_t* entry = data;
|
||||
WINPR_ASSERT(entry);
|
||||
|
||||
FreeRDP_TimerID id = va_arg(ap, FreeRDP_TimerID);
|
||||
|
||||
if (entry->id == id)
|
||||
{
|
||||
/* Mark the timer to be disabled.
|
||||
* It will be removed on next rescheduling event
|
||||
*/
|
||||
entry->intervallNS = 0;
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool freerdp_timer_remove(rdpContext* context, FreeRDP_TimerID id)
|
||||
{
|
||||
WINPR_ASSERT(context);
|
||||
WINPR_ASSERT(context->rdp);
|
||||
|
||||
FreeRDPTimer* timer = context->rdp->timer;
|
||||
WINPR_ASSERT(timer);
|
||||
|
||||
return !ArrayList_ForEach(timer->entries, foreach_entry, id);
|
||||
}
|
||||
|
||||
static BOOL runTimerEvent(timer_entry_t* entry, uint64_t* now)
|
||||
{
|
||||
WINPR_ASSERT(entry);
|
||||
|
||||
entry->intervallNS =
|
||||
entry->cb(entry->context, entry->userdata, entry->id, *now, entry->intervallNS);
|
||||
*now = winpr_GetTickCount64NS();
|
||||
entry->nextRunTimeNS = *now + entry->intervallNS;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL runExpiredTimer(void* data, WINPR_ATTR_UNUSED size_t index,
|
||||
WINPR_ATTR_UNUSED va_list ap)
|
||||
{
|
||||
timer_entry_t* entry = data;
|
||||
WINPR_ASSERT(entry);
|
||||
WINPR_ASSERT(entry->cb);
|
||||
|
||||
/* Skip all timers that have been deactivated. */
|
||||
if (entry->intervallNS == 0)
|
||||
return TRUE;
|
||||
|
||||
uint64_t* now = va_arg(ap, uint64_t*);
|
||||
WINPR_ASSERT(now);
|
||||
|
||||
bool* mainloop = va_arg(ap, bool*);
|
||||
WINPR_ASSERT(mainloop);
|
||||
|
||||
if (entry->nextRunTimeNS > *now)
|
||||
return TRUE;
|
||||
|
||||
if (entry->mainloop)
|
||||
*mainloop = true;
|
||||
else
|
||||
runTimerEvent(entry, now);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static uint64_t expire_and_reschedule(FreeRDPTimer* timer)
|
||||
{
|
||||
WINPR_ASSERT(timer);
|
||||
|
||||
bool mainloop = false;
|
||||
uint64_t next = UINT64_MAX;
|
||||
uint64_t now = winpr_GetTickCount64NS();
|
||||
|
||||
ArrayList_Lock(timer->entries);
|
||||
ArrayList_ForEach(timer->entries, runExpiredTimer, &now, &mainloop);
|
||||
if (mainloop)
|
||||
(void)SetEvent(timer->mainevent);
|
||||
|
||||
size_t pos = 0;
|
||||
while (pos < ArrayList_Count(timer->entries))
|
||||
{
|
||||
timer_entry_t* entry = ArrayList_GetItem(timer->entries, pos);
|
||||
WINPR_ASSERT(entry);
|
||||
if (entry->intervallNS == 0)
|
||||
{
|
||||
ArrayList_RemoveAt(timer->entries, pos);
|
||||
continue;
|
||||
}
|
||||
if (next > entry->nextRunTimeNS)
|
||||
next = entry->nextRunTimeNS;
|
||||
pos++;
|
||||
}
|
||||
ArrayList_Unlock(timer->entries);
|
||||
|
||||
if (next == UINT64_MAX)
|
||||
return 0;
|
||||
return next;
|
||||
}
|
||||
|
||||
static DWORD WINAPI timer_thread(LPVOID arg)
|
||||
{
|
||||
FreeRDPTimer* timer = arg;
|
||||
WINPR_ASSERT(timer);
|
||||
|
||||
// TODO: Currently we only support ms granularity, look for ways to improve
|
||||
DWORD timeout = INFINITE;
|
||||
HANDLE handles[2] = { utils_get_abort_event(timer->rdp), timer->event };
|
||||
|
||||
while (WaitForMultipleObjects(ARRAYSIZE(handles), handles, FALSE, timeout) != WAIT_OBJECT_0)
|
||||
{
|
||||
(void)ResetEvent(timer->event);
|
||||
const uint64_t next = expire_and_reschedule(timer);
|
||||
const uint64_t now = winpr_GetTickCount64NS();
|
||||
if (now >= next)
|
||||
{
|
||||
timeout = INFINITE;
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint64_t diff = next - now;
|
||||
const uint64_t diffMS = diff / 1000;
|
||||
timeout = MIN(INFINITE, (uint32_t)diffMS);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void freerdp_timer_free(FreeRDPTimer* timer)
|
||||
{
|
||||
if (!timer)
|
||||
return;
|
||||
|
||||
if (timer->event)
|
||||
(void)SetEvent(timer->event);
|
||||
timer->running = false;
|
||||
if (timer->thread)
|
||||
{
|
||||
(void)WaitForSingleObject(timer->thread, INFINITE);
|
||||
CloseHandle(timer->thread);
|
||||
}
|
||||
if (timer->mainevent)
|
||||
CloseHandle(timer->mainevent);
|
||||
if (timer->event)
|
||||
CloseHandle(timer->event);
|
||||
ArrayList_Free(timer->entries);
|
||||
free(timer);
|
||||
}
|
||||
|
||||
static void* entry_new(const void* val)
|
||||
{
|
||||
const timer_entry_t* entry = val;
|
||||
if (!entry)
|
||||
return NULL;
|
||||
|
||||
timer_entry_t* copy = calloc(1, sizeof(timer_entry_t));
|
||||
if (!copy)
|
||||
return NULL;
|
||||
*copy = *entry;
|
||||
return copy;
|
||||
}
|
||||
|
||||
FreeRDPTimer* freerdp_timer_new(rdpRdp* rdp)
|
||||
{
|
||||
WINPR_ASSERT(rdp);
|
||||
FreeRDPTimer* timer = calloc(1, sizeof(FreeRDPTimer));
|
||||
if (!timer)
|
||||
return NULL;
|
||||
timer->rdp = rdp;
|
||||
|
||||
timer->entries = ArrayList_New(TRUE);
|
||||
if (!timer->entries)
|
||||
goto fail;
|
||||
wObject* obj = ArrayList_Object(timer->entries);
|
||||
WINPR_ASSERT(obj);
|
||||
obj->fnObjectNew = entry_new;
|
||||
obj->fnObjectFree = free;
|
||||
|
||||
timer->event = CreateEventA(NULL, TRUE, FALSE, NULL);
|
||||
if (!timer->event)
|
||||
goto fail;
|
||||
|
||||
timer->mainevent = CreateEventA(NULL, TRUE, FALSE, NULL);
|
||||
if (!timer->mainevent)
|
||||
goto fail;
|
||||
|
||||
timer->running = true;
|
||||
timer->thread = CreateThread(NULL, 0, timer_thread, timer, 0, NULL);
|
||||
if (!timer->thread)
|
||||
goto fail;
|
||||
return timer;
|
||||
|
||||
fail:
|
||||
freerdp_timer_free(timer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static BOOL runExpiredTimerOnMainloop(void* data, WINPR_ATTR_UNUSED size_t index,
|
||||
WINPR_ATTR_UNUSED va_list ap)
|
||||
{
|
||||
timer_entry_t* entry = data;
|
||||
WINPR_ASSERT(entry);
|
||||
WINPR_ASSERT(entry->cb);
|
||||
|
||||
/* Skip events not on mainloop */
|
||||
if (!entry->mainloop)
|
||||
return TRUE;
|
||||
|
||||
/* Skip all timers that have been deactivated. */
|
||||
if (entry->intervallNS == 0)
|
||||
return TRUE;
|
||||
|
||||
uint64_t* now = va_arg(ap, uint64_t*);
|
||||
WINPR_ASSERT(now);
|
||||
|
||||
if (entry->nextRunTimeNS > *now)
|
||||
return TRUE;
|
||||
|
||||
runTimerEvent(entry, now);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool freerdp_timer_poll(FreeRDPTimer* timer)
|
||||
{
|
||||
WINPR_ASSERT(timer);
|
||||
|
||||
if (WaitForSingleObject(timer->mainevent, 0) != WAIT_OBJECT_0)
|
||||
return true;
|
||||
|
||||
ArrayList_Lock(timer->entries);
|
||||
(void)ResetEvent(timer->mainevent);
|
||||
uint64_t now = winpr_GetTickCount64NS();
|
||||
ArrayList_ForEach(timer->entries, runExpiredTimerOnMainloop, &now);
|
||||
(void)SetEvent(timer->event); // Trigger a wakeup of timer thread to reschedule
|
||||
ArrayList_Unlock(timer->entries);
|
||||
return true;
|
||||
}
|
||||
|
||||
HANDLE freerdp_timer_get_event(FreeRDPTimer* timer)
|
||||
{
|
||||
WINPR_ASSERT(timer);
|
||||
return timer->mainevent;
|
||||
}
|
34
libfreerdp/core/timer.h
Normal file
34
libfreerdp/core/timer.h
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Timer implementation
|
||||
*
|
||||
* Copyright 2025 Armin Novak <anovak@thincast.com>
|
||||
* Copyright 2025 Thincast Technologies GmbH
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <freerdp/api.h>
|
||||
#include <freerdp/types.h>
|
||||
|
||||
typedef struct freerdp_timer_s FreeRDPTimer;
|
||||
|
||||
FREERDP_LOCAL void freerdp_timer_free(FreeRDPTimer* timer);
|
||||
|
||||
WINPR_ATTR_MALLOC(freerdp_timer_free, 1)
|
||||
FREERDP_LOCAL FreeRDPTimer* freerdp_timer_new(rdpRdp* rdp);
|
||||
|
||||
FREERDP_LOCAL bool freerdp_timer_poll(FreeRDPTimer* timer);
|
||||
FREERDP_LOCAL HANDLE freerdp_timer_get_event(FreeRDPTimer* timer);
|
Loading…
Reference in New Issue
Block a user