Add timers module

Allows a polled timer to be set for a future event. The timeout
values are suitable for passing to g_obj_wait() or poll().
This commit is contained in:
matt335672
2025-08-20 09:48:48 +01:00
parent d8bce01e45
commit 3700867e87
7 changed files with 262 additions and 1 deletions
+2
View File
@@ -75,6 +75,8 @@ libcommon_la_SOURCES = \
string_calls.h \
thread_calls.c \
thread_calls.h \
timers.c \
timers.h \
trans.c \
trans.h \
unicode_defines.h \
+94
View File
@@ -0,0 +1,94 @@
/**
* xrdp: A Remote Desktop Protocol server.
*
* Copyright (C) Jay Sorg 2004-2025
*
* 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.
*
*/
/**
* @file common/timers.h
* @brief Timers and related functions (declarations)
* @author Matt Burt
*/
#if defined(HAVE_CONFIG_H)
#include "config_ac.h"
#endif
#include "os_calls.h"
#include "timers.h"
struct timers_oneshot
{
unsigned int add_time; // Time event was added from_g_get_elapsed_ms()
int trigger_time;
};
/******************************************************************************/
struct timers_oneshot *
timers_oneshot_init(int ms)
{
struct timers_oneshot *t = (struct timers_oneshot *)malloc(sizeof(*t));
if (t != NULL)
{
t->add_time = g_get_elapsed_ms();
t->trigger_time = (ms <= 0) ? 0 : ms;
}
return t;
}
/******************************************************************************/
int
timers_oneshot_get_remaining(struct timers_oneshot *timer,
unsigned int now)
{
int rv = -1;
if (timer != NULL)
{
if (timer->trigger_time == 0)
{
rv = 0;
}
else
{
rv = timer->trigger_time - (int)(now - timer->add_time);
if (rv <= 0)
{
rv = 0;
// (pathological) Make sure the timer doesn't stop
// triggering if it isn't attended to for the rollover
// period (~20 days for a 32-bit timer).
timer->trigger_time = 0;
}
}
}
return rv;
}
/******************************************************************************/
void
timers_oneshot_update_poll(struct timers_oneshot *timer, unsigned int now,
int *timeout)
{
if (timer != NULL && timeout != NULL)
{
int remaining = timers_oneshot_get_remaining(timer, now);
if (*timeout < 0 || *timeout > remaining)
{
*timeout = remaining;
}
}
}
+74
View File
@@ -0,0 +1,74 @@
/**
* xrdp: A Remote Desktop Protocol server.
*
* Copyright (C) Jay Sorg 2004-2025
*
* 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.
*
*/
/**
* @file common/timers.h
* @brief Timers and related functions (declarations)
* @author Matt Burt
*/
#ifndef TIMERS_H
#define TIMERS_H
#include "arch.h"
struct timers_oneshot;
/**
* Initialise a one-shot timer
* @param ms Milliseconds until timer fires (>= 0)
* @return pointer to timer
*
* Returns NULL if no memory.
*
* When the timer is no longer required, it can simply be passed to free()
*/
struct timers_oneshot *
timers_oneshot_init(int ms);
/**
* Return ms remaining on a one-shot timer
* @param timer pointer to timer (or NULL)
* @param now Value of g_get_elapsed_ms()
* @return remaining ms
*
* Once this routine has returned 0 for a particular timer, it will never
* return anything else. Don't pass anything to 'now' apart from a recent
* value from g_get_elapsed_ms()
*
* If the timer is NULL, -1 is returned.
*/
int
timers_oneshot_get_remaining(struct timers_oneshot *timer,
unsigned int now);
/**
* Variant of timers_oneshot_get_remaining() for g_obj_wait()
* @param timer pointer to timer (or NULL)
* @param now Value of g_get_elapsed_ms()
* @param[in,out] poll timeout
*
* Use this to update a timeout passed to g_obj_wait() (or poll()). The
* timeout is updated if the timer will fire before the current timeout.
*/
void
timers_oneshot_update_poll(struct timers_oneshot *timer, unsigned int now,
int *timeout);
#endif // TIMERS_H
+2 -1
View File
@@ -26,7 +26,8 @@ test_common_SOURCES = \
test_ssl_calls.c \
test_base64.c \
test_guid.c \
test_scancode.c
test_scancode.c \
test_timers.c
test_common_CFLAGS = \
@CHECK_CFLAGS@ \
+1
View File
@@ -19,6 +19,7 @@ Suite *make_suite_test_ssl_calls(void);
Suite *make_suite_test_base64(void);
Suite *make_suite_test_guid(void);
Suite *make_suite_test_scancode(void);
Suite *make_suite_test_timers(void);
TCase *make_tcase_test_os_calls_signals(void);
+1
View File
@@ -58,6 +58,7 @@ int main (void)
srunner_add_suite(sr, make_suite_test_base64());
srunner_add_suite(sr, make_suite_test_guid());
srunner_add_suite(sr, make_suite_test_scancode());
srunner_add_suite(sr, make_suite_test_timers());
srunner_set_tap(sr, "-");
/*
+88
View File
@@ -0,0 +1,88 @@
#if defined(HAVE_CONFIG_H)
#include "config_ac.h"
#endif
#include "limits.h"
#include "os_calls.h"
#include "timers.h"
#include "test_common.h"
/******************************************************************************/
START_TEST(test_timers__null_timer)
{
unsigned int now = g_get_elapsed_ms();
struct timers_oneshot *timer = NULL;
int v = timers_oneshot_get_remaining(timer, now);
ck_assert_int_eq(v, -1);
// Check any value of 'v' is not changed with a NULL timer
timers_oneshot_update_poll(timer, now, &v);
ck_assert_int_eq(v, -1);
v = 0;
timers_oneshot_update_poll(timer, now, &v);
ck_assert_int_eq(v, 0);
v = INT_MAX;
timers_oneshot_update_poll(timer, now, &v);
ck_assert_int_eq(v, INT_MAX);
}
END_TEST
/******************************************************************************/
START_TEST(test_timers__two_secs)
{
#define TOLERANCE 25 // Percent
#define HALF_WAIT 1000 // A second
struct timers_oneshot *timer = timers_oneshot_init(2 * HALF_WAIT);
ck_assert_ptr_ne(timer, NULL);
// Wait for half the total period and check the elapsed timer is
// within limits
g_sleep(HALF_WAIT);
int remaining = timers_oneshot_get_remaining(timer, g_get_elapsed_ms());
ck_assert_int_ge(remaining, HALF_WAIT - (HALF_WAIT * TOLERANCE / 100));
ck_assert_int_le(remaining, HALF_WAIT + (HALF_WAIT * TOLERANCE / 100));
// Wait for the rest of the period and check the timer is zero (or near it)
g_sleep(remaining);
unsigned int now = g_get_elapsed_ms();
int v = timers_oneshot_get_remaining(timer, now);
ck_assert_int_ge(v, 0);
ck_assert_int_le(v, 1);
// Check the timer is zero in the future
v = timers_oneshot_get_remaining(timer, now + 1000); // Second
ck_assert_int_eq(v, 0);
v = timers_oneshot_get_remaining(timer, now + 3600 * 1000); // Hour
ck_assert_int_eq(v, 0);
v = timers_oneshot_get_remaining(timer, now + 86400 * 1000); // Day
ck_assert_int_eq(v, 0);
v = timers_oneshot_get_remaining(timer, now + 7 * 86400 * 1000); // Week
ck_assert_int_eq(v, 0);
free(timer);
#undef TOLERANCE
#undef HALF_WAIT
}
END_TEST
/******************************************************************************/
Suite *
make_suite_test_timers(void)
{
Suite *s;
TCase *tc_timers;
s = suite_create("timers");
tc_timers = tcase_create("timers");
suite_add_tcase(s, tc_timers);
tcase_add_test(tc_timers, test_timers__null_timer);
tcase_add_test(tc_timers, test_timers__two_secs);
return s;
}