Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(freertos): Made select function non-blocking on Linux target (IDFGH-13569) #14457

Merged
merged 1 commit into from
Sep 14, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 71 additions & 43 deletions components/freertos/esp_additions/FreeRTOSSimulator_wrappers.c
Original file line number Diff line number Diff line change
@@ -1,56 +1,84 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* 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 s_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
// Lookup the select symbol
if (s_real_select == NULL) {
s_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);
}
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;

// 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 = s_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 can be any value greater than zero.
* 10 is a good trade-off between CPU time usage and timeout resolution.
*/
vTaskDelay(10);
snake-4 marked this conversation as resolved.
Show resolved Hide resolved
}
return ret;
}
Loading