You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESP32异步Web服务器文件上传时出现pbuf引用错误

解决ESP32异步Web服务器文件上传触发LwIP pbuf断言错误的建议

问题核心分析

触发pbuf_free: p->ref > 0断言的根本原因是跨线程操作LwIP pbuf时的引用计数异常:自定义的FreeRTOS worker线程与ESP-IDF HTTP服务器的线程上下文分离,直接在worker中调用httpd_req_recv()会导致pbuf的引用计数被多个线程错误修改,最终触发释放时的断言检查。


针对性解决步骤

1. 避免在自定义worker线程中直接调用httpd_req_recv()

httpd_req_recv()是与HTTP服务器线程上下文绑定的接口,内部会直接操作LwIP的pbuf结构体。跨线程调用会破坏pbuf的引用计数逻辑。正确的做法是:

  • 在HTTP请求处理线程(即异步handler函数)中,先调用httpd_req_recv()把上传数据读取到本地缓冲区;
  • 将缓冲区的副本(或带生命周期管理的指针)传给worker线程处理,而非让worker直接调用httpd_req_recv()。

示例代码片段:

#define UPLOAD_BUF_SIZE 1024

typedef struct {
    char buffer[UPLOAD_BUF_SIZE];
    size_t buf_len;
} upload_ctx_t;

static void upload_worker(void *arg) {
    upload_ctx_t *ctx = (upload_ctx_t *)arg;
    // 这里处理上传数据,比如写入SPIFFS/SD卡
    // ... 你的文件写入逻辑 ...
    free(ctx);
}

static esp_err_t upload_handler(httpd_req_t *req) {
    upload_ctx_t *ctx = malloc(sizeof(upload_ctx_t));
    if (!ctx) return ESP_ERR_NO_MEM;

    // 在handler线程中完成数据接收
    ctx->buf_len = httpd_req_recv(req, ctx->buffer, UPLOAD_BUF_SIZE);
    if (ctx->buf_len <= 0) {
        free(ctx);
        return ESP_OK;
    }

    // 创建worker线程处理数据(确保只传本地缓冲区,而非req指针)
    xTaskCreate(upload_worker, "upload_worker", 4096, ctx, 5, NULL);
    return httpd_send(req, "Upload accepted", strlen("Upload accepted"));
}

2. 使用ESP-IDF官方的异步工作队列替代自定义worker

ESP-IDF的HTTP服务器内置了httpd_queue_work()接口,能将任务提交到服务器的原生worker线程池,保证操作在服务器上下文内进行,避免跨线程pbuf访问问题:

static void upload_worker_func(void *arg) {
    httpd_req_t *req = (httpd_req_t *)arg;
    upload_ctx_t *ctx = httpd_req_to_ctx(req);

    // 处理上传数据
    // ... 逻辑 ...

    // 回复客户端并释放资源
    httpd_req_send(req, "Upload completed", strlen("Upload completed"));
    free(ctx);
}

static esp_err_t upload_handler(httpd_req_t *req) {
    upload_ctx_t *ctx = malloc(sizeof(upload_ctx_t));
    if (!ctx) return ESP_ERR_NO_MEM;

    ctx->buf_len = httpd_req_recv(req, ctx->buffer, UPLOAD_BUF_SIZE);
    if (ctx->buf_len <= 0) {
        free(ctx);
        return ESP_OK;
    }

    // 将上下文绑定到请求,提交到服务器工作队列
    httpd_req_set_ctx(req, ctx);
    if (httpd_queue_work(req->handle, upload_worker_func, req) != ESP_OK) {
        free(ctx);
        return ESP_FAIL;
    }

    // 返回HTTPD_WAIT_FOR_WORK,告知服务器等待工作完成后回复
    return HTTPD_WAIT_FOR_WORK;
}

3. 排查pbuf生命周期的竞态问题

如果必须保留自定义worker线程,需确保HTTP请求上下文在worker处理完成前不被服务器释放:

  • 使用FreeRTOS信号量,在handler中等待worker处理完成后再返回;
  • 禁止服务器在handler返回后立即清理请求相关的pbuf资源。

4. 临时禁用断言辅助排查(非最终解决方案)

在menuconfig中关闭CONFIG_LWIP_ASSERT,如果禁用后功能正常,说明是引用计数的逻辑错误而非内存损坏,可以针对性调整线程同步逻辑;如果仍然崩溃,需排查内存越界等问题。


调用栈指向的关键信息

断言发生在pbuf_free()中,说明释放pbuf时其引用计数p->ref已经为0,这意味着:

  • 同一个pbuf被多个线程重复释放;
  • 线程间的pbuf引用计数修改未同步,导致计数被错误递减到0后再次触发释放。

内容的提问来源于stack exchange,提问作者Luca Anzalone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 21:59:59