Skip to content

Commit

Permalink
fix(freertos): Made select function non-blocking on Linux target
Browse files Browse the repository at this point in the history
The select function wrapper was rewritten to be non-blocking on Linux systems, as it was stealing all the CPU time from lower priority tasks when called from a higher priority task. This is because the FreeRTOS scheduler does not know that the task thread is sleeping during the system call.

This issue manifests all "slow" system calls on the Linux target, but handling the case of select fixes the problems for most ESP-IDF components.

The FreeRTOS POSIX port documentation lists this as a known issue, so user code is responsible handling this case if other system calls are used, even if unknowingly.

This closes espressif#14395 "select() blocks the FreeRTOS scheduler on Linux target" (IDFGH-13498).
  • Loading branch information
snake-4 authored Aug 28, 2024
1 parent c9df77e commit 9f9a435
Showing 1 changed file with 53 additions and 43 deletions.
96 changes: 53 additions & 43 deletions components/freertos/esp_additions/FreeRTOSSimulator_wrappers.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,64 @@
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <pthread.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <dlfcn.h>
#include <sys/types.h>
#include "esp_err.h"
#include "errno.h"

/** This module addresses the FreeRTOS simulator's coexistence with linux system calls from user apps.
* It's only included when building without lwIP, so we need to use linux system's select() which would receive
* EINTR event on every FreeRTOS interrupt; we workaround this problem by wrapping select()
* to bypass and silence these events.
#include <sys/select.h>
#include <errno.h>

/** This module addresses the FreeRTOS simulator's coexistence with Linux system calls from user apps.
* It wraps select so that it doesn't block the FreeRTOS task calling it, so that the
* scheduler will allow lower priority tasks to run.
* Without the wrapper, most components such as ESP-MQTT block lower priority tasks from running at all.
*/
typedef int (*select_func_t) (int fd, fd_set * rfds, fd_set * wfds, fd_set *efds, struct timeval *tval);
typedef int (*select_func_t)(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tval);

static inline int64_t get_us(void)
int select(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tval)
{
struct timespec spec;
clock_gettime(CLOCK_REALTIME, &spec);
return spec.tv_nsec / 1000 + spec.tv_sec * 1000000;
}
static select_func_t real_select = NULL;
TickType_t end_ticks = portMAX_DELAY;
fd_set o_rfds, o_wfds, o_efds;

int select (int fd, fd_set * rfds, fd_set * wfds, fd_set *efds, struct timeval *tval)
{
int ret;
struct timeval *tv = tval;
struct timeval timeval_local = {};
int64_t start = 0;
int64_t timeout_us = 0;
select_func_t real_select = (select_func_t) dlsym(RTLD_NEXT, "select");
if (tv != NULL) {
start = get_us();
timeout_us = tval->tv_sec * 1000000 + tval->tv_usec;
timeval_local.tv_sec = tval->tv_sec;
timeval_local.tv_usec = tval->tv_usec;
tv = &timeval_local; // this (tv != NULL) indicates that we should handle timeouts
}
while ((ret = real_select(fd, rfds, wfds, efds, tv)) < 0 && errno == EINTR) {
if (tv != NULL) {
int64_t now = get_us();
timeout_us -= now - start;
if (timeout_us < 0) {
errno = 0;
ret = 0;
break;
}
start = now;
tv->tv_usec = timeout_us % 1000000;
tv->tv_sec = timeout_us / 1000000;
// Lookup the select symbol
if (real_select == NULL)
real_select = (select_func_t)dlsym(RTLD_NEXT, "select");

// Calculate the end_ticks if a timeout is provided
if (tval != NULL)
end_ticks = xTaskGetTickCount() + pdMS_TO_TICKS(tval->tv_sec * 1000 + tval->tv_usec / 1000);

// Preserve the original FD sets as select call will change them
if (rfds) o_rfds = *rfds;
if (wfds) o_wfds = *wfds;
if (efds) o_efds = *efds;

while (1) {
// Restore original FD sets before the select call
if (rfds) *rfds = o_rfds;
if (wfds) *wfds = o_wfds;
if (efds) *efds = o_efds;

// Call select with a zero timeout to avoid blocking
struct timeval zero_tv = {0, 0};
int ret = real_select(fd, rfds, wfds, efds, &zero_tv);

// Return on success
if (ret > 0)
return ret;

// Return on any error but EINTR
if (ret == -1 && errno != EINTR)
return ret;

if (tval != NULL && xTaskGetTickCount() >= end_ticks) {
errno = 0;
return 0;
}

// Sleep for 10 tick(s) to allow other tasks to run
// This determines the percentage of CPU time for the current priority
// to be allocated to this loop.
vTaskDelay(10);
}
return ret;
}

0 comments on commit 9f9a435

Please sign in to comment.