Commit cc93a683ca for freeswitch.com

commit cc93a683ca6e1f8f47c1d6e38bd2daf8fa6a7b1c
Author: Dmitry Verenitsin <morbit85@gmail.com>
Date:   Fri Aug 21 19:10:14 2026 +0500

    [core] Fix _timerfd_check using wrong syscall to detect timer expiration. Add unit-tests. (#3129)

    `timerfd_gettime()` returns the time remaining until the next periodic tick,
    regardless of how many previous ticks have already fired and remain unread.
    Replace with `poll()` which correctly checks for pending unread expirations.

diff --git a/src/switch_time.c b/src/switch_time.c
index 4a70b60fca..fef5cfd3d7 100644
--- a/src/switch_time.c
+++ b/src/switch_time.c
@@ -38,6 +38,7 @@

 #ifdef HAVE_TIMERFD_CREATE
 #include <sys/timerfd.h>
+#include <poll.h>
 #endif

 //#if defined(DARWIN)
@@ -497,27 +498,27 @@ static switch_status_t _timerfd_next(switch_timer_t *timer)
 static switch_status_t _timerfd_check(switch_timer_t *timer, switch_bool_t step)
 {
 	interval_timer_t *it = timer->private_info;
-	struct itimerspec val;
-	int diff;
+	struct pollfd pfd = { .events = POLLIN };

 	if (!it) {
 		return SWITCH_STATUS_GENERR;
 	}

-	timerfd_gettime(it->fd, &val);
-	diff = val.it_value.tv_nsec / 1000;
+	pfd.fd = it->fd;

-	if (diff > 0) {
-		/* still pending */
-		timer->diff = diff;
-		return SWITCH_STATUS_FALSE;
-	} else {
-		/* timer pending */
+	if (poll(&pfd, 1, 0) > 0) {
+		/* timer has fired */
 		timer->diff = 0;
 		if (step) {
 			_timerfd_step(timer);
 		}
+
 		return SWITCH_STATUS_SUCCESS;
+	} else {
+		/* still pending */
+		timer->diff = 1;
+
+		return SWITCH_STATUS_FALSE;
 	}
 }

diff --git a/tests/unit/.gitignore b/tests/unit/.gitignore
index 966fc80eb9..37a7064a16 100644
--- a/tests/unit/.gitignore
+++ b/tests/unit/.gitignore
@@ -36,6 +36,7 @@ switch_ulp_recover1
 switch_ulp_recover2
 switch_ulp_recover3
 switch_ulp_recover4
+switch_timer
 switch_utils
 switch_vad
 switch_vpx
diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am
index a3c59059b3..cb974ee3a1 100644
--- a/tests/unit/Makefile.am
+++ b/tests/unit/Makefile.am
@@ -9,6 +9,7 @@ noinst_PROGRAMS+= switch_hold switch_sip
 noinst_PROGRAMS += switch_core_media
 noinst_PROGRAMS += test_mod_verto
 noinst_PROGRAMS += test_mod_event_socket
+noinst_PROGRAMS += switch_timer

 if HAVE_PCAP
 noinst_PROGRAMS += switch_rtp_pcap
diff --git a/tests/unit/switch_timer.c b/tests/unit/switch_timer.c
new file mode 100644
index 0000000000..73e1bcb3e1
--- /dev/null
+++ b/tests/unit/switch_timer.c
@@ -0,0 +1,116 @@
+/*
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ * Copyright (C) 2026, Anthony Minessale II <anthm@freeswitch.org>
+ *
+ * Version: MPL 1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ *
+ * The Initial Developer of the Original Code is
+ * Anthony Minessale II <anthm@freeswitch.org>
+ * Portions created by the Initial Developer are Copyright (C)
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Dmitry Verenitsin <dmitry.verenitsin@signalwire.com>
+ *
+ *
+ * switch_timer.c -- timer tests
+ *
+ */
+#include <switch.h>
+#include <test/switch_test.h>
+
+static int module_loaded = 0;
+
+FST_MINCORE_BEGIN("./conf")
+
+FST_SUITE_BEGIN(switch_timer)
+
+FST_SETUP_BEGIN()
+{
+	if (!module_loaded) {
+		const char *err = NULL;
+		switch_loadable_module_init(SWITCH_FALSE);
+		switch_loadable_module_load_module("", "CORE_SOFTTIMER_MODULE", SWITCH_TRUE, &err);
+		module_loaded = 1;
+	}
+}
+FST_SETUP_END()
+
+FST_TEARDOWN_BEGIN()
+{
+}
+FST_TEARDOWN_END()
+
+/* timer_check returns FALSE with non-zero diff when no tick has elapsed,
+ * returns SUCCESS with diff=0 after sleeping past the interval */
+FST_TEST_BEGIN(test_timer_check)
+{
+	switch_timer_t timer = { 0 };
+	switch_status_t status;
+
+	fst_requires(switch_core_timer_init(&timer, "soft", 20, 160, fst_pool) == SWITCH_STATUS_SUCCESS);
+
+	/* immediately after init - no tick yet */
+	status = switch_core_timer_check(&timer, SWITCH_FALSE);
+	fst_check(status == SWITCH_STATUS_FALSE);
+	fst_check(timer.diff != 0);
+
+	/* sleep past the 20ms interval */
+	switch_sleep(50000); /* 50ms */
+
+	/* now the tick should be detected */
+	status = switch_core_timer_check(&timer, SWITCH_FALSE);
+	fst_check(status == SWITCH_STATUS_SUCCESS);
+	fst_check(timer.diff == 0);
+
+	switch_core_timer_destroy(&timer);
+}
+FST_TEST_END()
+
+/* step=FALSE does not advance tick/samplecount, step=TRUE advances by exactly 1 tick */
+FST_TEST_BEGIN(test_timer_check_step)
+{
+	switch_timer_t timer = { 0 };
+	switch_size_t tick_before;
+	uint32_t samplecount_before;
+	switch_status_t status;
+
+	fst_requires(switch_core_timer_init(&timer, "soft", 20, 160, fst_pool) == SWITCH_STATUS_SUCCESS);
+
+	switch_sleep(50000); /* 50ms - let the timer tick */
+
+	tick_before = timer.tick;
+	samplecount_before = timer.samplecount;
+
+	/* step=FALSE: reports ready but does not advance */
+	status = switch_core_timer_check(&timer, SWITCH_FALSE);
+	fst_check(status == SWITCH_STATUS_SUCCESS);
+	fst_check(timer.tick == tick_before);
+	fst_check(timer.samplecount == samplecount_before);
+
+	/* step=TRUE: advances tick by 1, samplecount by samples */
+	status = switch_core_timer_check(&timer, SWITCH_TRUE);
+	fst_check(status == SWITCH_STATUS_SUCCESS);
+	fst_check(timer.tick == tick_before + 1);
+	fst_check(timer.samplecount == samplecount_before + 160);
+
+	switch_core_timer_destroy(&timer);
+}
+FST_TEST_END()
+
+FST_SUITE_END()
+
+FST_MINCORE_END()