From 513c0134038fe95ac804d35c8e551dab11b377cf Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Thu, 2 Jul 2026 08:30:14 +0200 Subject: [PATCH] fix(freertos): discard stale wake events on linux port context switch On the Linux port, tasks are pthreads that cannot be forcibly paused; the scheduler "unschedules" a task only by selecting a different next task, and the outgoing task keeps running until it next blocks itself in event_wait (e.g. via vPortYield). If the scheduler later switches back into such a task (event_signal) before it ever parked in event_wait, that wake is never consumed and event_triggered stays latched. The task's next voluntary block then returns immediately instead of blocking (e.g. vTaskDelay(100ms) returning in 0ms), which desynchronizes kernel and port scheduling state. Clear the outgoing task's pending event when switching to a different task, under s_port_mutex and atomically with the scheduling decision. Any latched-but-unconsumed wake is stale at that point; a legitimate wake can only be delivered later, once the scheduler selects the task again as next_thread. Add event_clear() to the wait_for_event helper to support this. Verified: minimal repros (busy-wait with another ready task, and pthread-blocked task) now measure 92-101 ms for vTaskDelay(100ms) instead of 0 ms; linux_freertos kernel test app 18/18 pass; the previously failing esp_linenoise host test suite passes 34/34. --- .../FreeRTOS-Kernel/portable/linux/port.c | 27 +++++++++++++++++++ .../portable/linux/utils/wait_for_event.c | 9 +++++++ .../portable/linux/utils/wait_for_event.h | 7 +++++ 3 files changed, 43 insertions(+) diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/port.c b/components/freertos/FreeRTOS-Kernel/portable/linux/port.c index f358a96e258..b1994e2ec2a 100644 --- a/components/freertos/FreeRTOS-Kernel/portable/linux/port.c +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/port.c @@ -219,6 +219,33 @@ static void linux_port_switch_context(TaskHandle_t current_task_hdl) * in its initial event_wait (linux_port_task_runner), so we must * signal it even though current_thread == next_thread. */ if (next_thread != current_thread || !s_scheduler_started) { + /* Discard any stale wake on the task being switched out. + * + * Because tasks are pthreads that cannot be forcibly paused, + * the port can only "unschedule" a task by choosing a different + * next task; the outgoing task keeps running until it next + * blocks itself in event_wait (e.g. via vPortYield). If, while + * it was unscheduled, the scheduler already switched back into + * it (event_signal in a later switch) without the task ever + * having parked in event_wait, that signal is never consumed + * and event_triggered stays latched. Its next voluntary block + * would then return immediately instead of blocking (e.g. + * vTaskDelay(100ms) returning in 0ms). + * + * Clearing here, under s_port_mutex and atomically with the + * scheduling decision, drops that latched-but-unconsumed wake. + * It is safe: a legitimate wake can only be delivered later, + * when the scheduler next selects this task as next_thread. + * + * Reached only for a genuine switch to a different task: + * entering this block with next_thread == current_thread + * happens solely on the first switch (!s_scheduler_started), + * which the current_thread NULL-check below also tolerates. + * The NULL-check additionally guards the deleted-task path + * where the outgoing task has no thread mapping. */ + if (current_thread) { + event_clear(current_thread->ev); + } linux_port_unblock_thread(next_thread); } } diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.c b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.c index f816518b477..2f561aefad1 100644 --- a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.c +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.c @@ -92,3 +92,12 @@ void event_signal(event_t *ev) pthread_cond_signal(&ev->cond); pthread_mutex_unlock(&ev->mutex); } + +/*-----------------------------------------------------------*/ +/* Discard a pending signal that was never consumed by a wait */ +void event_clear(event_t *ev) +{ + pthread_mutex_lock(&ev->mutex); + ev->event_triggered = false; + pthread_mutex_unlock(&ev->mutex); +} diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.h b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.h index 4b20c802a59..6630a67d385 100644 --- a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.h +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.h @@ -92,6 +92,13 @@ bool event_wait_timed(event_t *ev, time_t ms); */ void event_signal(event_t *ev); +/** + * @brief Discard a pending signal that was never consumed by a wait + * + * @param ev + */ +void event_clear(event_t *ev); + #ifdef __cplusplus } #endif