Add Bounded Ordinary HTTPS Idle Cleanup
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-only */
|
||||
#include "web_httpd_idle.h"
|
||||
#include "web_httpd_adapter.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#define IDLE_POLL_US INT64_C(1000000)
|
||||
#define IDLE_FENCE_US INT64_C(1000000)
|
||||
|
||||
static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
static httpd_handle_t s_server;
|
||||
static esp_timer_handle_t s_timer;
|
||||
static uintptr_t s_generation;
|
||||
static bool s_accepting, s_queued, s_submitting;
|
||||
/* Only HTTPD touches rows while alive; prepare runs before SSL startup. */
|
||||
static web_httpd_idle_row_t s_rows[WEB_HTTPD_IDLE_SOCKETS];
|
||||
|
||||
static void idle_work(void *argument)
|
||||
{
|
||||
uintptr_t generation = (uintptr_t)argument;
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
bool current = s_queued && generation == s_generation;
|
||||
httpd_handle_t server = current && s_accepting ? s_server : NULL;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (server) web_httpd_idle_sweep(server, s_rows, esp_timer_get_time());
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (current && generation == s_generation) s_queued = false;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
}
|
||||
|
||||
static void idle_timer(void *argument)
|
||||
{
|
||||
(void)argument;
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
httpd_handle_t server = NULL;
|
||||
uintptr_t generation = s_generation;
|
||||
if (s_accepting && !s_queued && !s_submitting) {
|
||||
server = s_server;
|
||||
s_queued = s_submitting = true;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (!server) return;
|
||||
esp_err_t error = httpd_queue_work(server, idle_work, (void *)generation);
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
/* A callback may finish before queue_work returns. Keep the submission
|
||||
* reservation until here so it cannot clear a newer probe's queued flag. */
|
||||
if (error != ESP_OK) s_queued = false;
|
||||
s_submitting = false;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
}
|
||||
|
||||
esp_err_t web_httpd_idle_prepare(void)
|
||||
{
|
||||
#if defined(CONFIG_HTTPD_QUEUE_WORK_BLOCKING) && CONFIG_HTTPD_QUEUE_WORK_BLOCKING
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#else
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
bool allowed = !s_server && !s_queued && !s_submitting && s_generation != UINTPTR_MAX;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (!allowed) return ESP_ERR_INVALID_STATE;
|
||||
if (!s_timer) {
|
||||
esp_timer_handle_t timer = NULL;
|
||||
const esp_timer_create_args_t args = {
|
||||
.callback = idle_timer, .name = "web_idle", .skip_unhandled_events = true,
|
||||
};
|
||||
esp_err_t error = esp_timer_create(&args, &timer);
|
||||
if (error == ESP_OK) error = esp_timer_start_periodic(timer, IDLE_POLL_US);
|
||||
if (error != ESP_OK) {
|
||||
if (timer) (void)esp_timer_delete(timer);
|
||||
return error;
|
||||
}
|
||||
s_timer = timer;
|
||||
}
|
||||
memset(s_rows, 0, sizeof(s_rows));
|
||||
return ESP_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
esp_err_t web_httpd_idle_attach(httpd_handle_t server)
|
||||
{
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
bool allowed = server && s_timer && !s_server && !s_queued && !s_submitting &&
|
||||
s_generation != UINTPTR_MAX;
|
||||
if (allowed) {
|
||||
++s_generation; /* Never reused, including when HTTPD's handle is reused. */
|
||||
s_server = server;
|
||||
s_accepting = true;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return allowed ? ESP_OK : ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t web_httpd_idle_detach(httpd_handle_t server)
|
||||
{
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
bool owned = server && s_server == server;
|
||||
bool absent = !s_server;
|
||||
if (owned) s_accepting = false;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
/* Partial startup may never have attached. */
|
||||
if (!owned) return absent ? ESP_OK : ESP_ERR_INVALID_STATE;
|
||||
int64_t deadline = esp_timer_get_time() + IDLE_FENCE_US;
|
||||
for (;;) {
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
bool submitting = s_submitting;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (!submitting) return ESP_OK;
|
||||
if (esp_timer_get_time() >= deadline) return ESP_ERR_TIMEOUT;
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
void web_httpd_idle_stopped(httpd_handle_t server)
|
||||
{
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (server && s_server == server && !s_accepting && !s_submitting) {
|
||||
s_server = NULL;
|
||||
s_queued = false; /* Successful HTTPD stop joined owner and destroyed queue. */
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
}
|
||||
|
||||
void web_httpd_idle_tls(esp_https_server_user_cb_arg_t *arg)
|
||||
{
|
||||
if (!arg || !arg->tls || arg->user_cb_state != HTTPD_SSL_USER_CB_SESS_CREATE) return;
|
||||
int fd = -1;
|
||||
if (esp_tls_get_conn_sockfd(arg->tls, &fd) != ESP_OK || fd < 0) {
|
||||
/* Identity unavailable: conservatively restart every idle observation. */
|
||||
memset(s_rows, 0, sizeof(s_rows));
|
||||
return;
|
||||
}
|
||||
for (unsigned i = 0; i < WEB_HTTPD_IDLE_SOCKETS; ++i)
|
||||
if (s_rows[i].fd == fd) memset(&s_rows[i], 0, sizeof(s_rows[i]));
|
||||
}
|
||||
Reference in New Issue
Block a user