From fca0939b587d28c475b91c6658d1c468df1876c4 Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Wed, 22 Jan 2025 11:42:29 +0100 Subject: [PATCH 1/2] Introduce ASYNCWEBSERVER_USE_CHUNK_INFLIGHT to be able to disable inflight in chunk --- .github/workflows/ci.yml | 3 +++ platformio.ini | 9 +++++++++ src/ESPAsyncWebServer.h | 6 ++++++ src/WebResponseImpl.h | 2 ++ src/WebResponses.cpp | 16 +++++++++++++--- 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dca0618b..0507b4e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,6 +109,9 @@ jobs: - env: ci-arduino-3-no-json board: esp32dev + - env: ci-arduino-3-no-chunk-inflight + board: esp32dev + - env: ci-esp8266 board: huzzah - env: ci-esp8266 diff --git a/platformio.ini b/platformio.ini index 0c5a2042..11406716 100644 --- a/platformio.ini +++ b/platformio.ini @@ -50,6 +50,10 @@ lib_deps = ; board = esp32-s3-devkitc-1 ; board = esp32-c6-devkitc-1 +[env:arduino-3-no-chunk-inflight] +build_flags = ${env.build_flags} + -D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=0 + [env:perf-test-AsyncTCP] build_flags = ${env.build_flags} -D PERF_TEST=1 @@ -94,6 +98,11 @@ board = ${sysenv.PIO_BOARD} lib_deps = ESP32Async/AsyncTCP @ 3.3.2 +[env:ci-arduino-3-no-chunk-inflight] +board = ${sysenv.PIO_BOARD} +build_flags = ${env.build_flags} + -D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=0 + [env:ci-esp8266] platform = espressif8266 board = ${sysenv.PIO_BOARD} diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index 00aed07d..2aaf72d6 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -60,6 +60,12 @@ #define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined"))) #endif +// See https://github.com/ESP32Async/ESPAsyncWebServer/commit/3d3456e9e81502a477f6498c44d0691499dda8f9#diff-646b25b11691c11dce25529e3abce843f0ba4bd07ab75ec9eee7e72b06dbf13fR388-R392 +// This setting slowdown chunk serving but avoids crashing or deadlocks in the case where slow chunk responses are created, like file serving form SD Card +#ifndef ASYNCWEBSERVER_USE_CHUNK_INFLIGHT + #define ASYNCWEBSERVER_USE_CHUNK_INFLIGHT 1 +#endif + class AsyncWebServer; class AsyncWebServerRequest; class AsyncWebServerResponse; diff --git a/src/WebResponseImpl.h b/src/WebResponseImpl.h index fa462b69..c93aa4d1 100644 --- a/src/WebResponseImpl.h +++ b/src/WebResponseImpl.h @@ -47,10 +47,12 @@ class AsyncBasicResponse : public AsyncWebServerResponse { class AsyncAbstractResponse : public AsyncWebServerResponse { private: +#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT // amount of responce data in-flight, i.e. sent, but not acked yet size_t _in_flight{0}; // in-flight queue credits size_t _in_flight_credit{2}; +#endif String _head; // Data is inserted into cache at begin(). // This is inefficient with vector, but if we use some other container, diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index bf8235e9..09a7f8d8 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -352,21 +352,25 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u request->client()->close(); return 0; } + +#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT // return a credit for each chunk of acked data (polls does not give any credits) if (len) ++_in_flight_credit; // for chunked responses ignore acks if there are no _in_flight_credits left if (_chunked && !_in_flight_credit) { -#ifdef ESP32 + #ifdef ESP32 log_d("(chunk) out of in-flight credits"); -#endif + #endif return 0; } - _ackedLength += len; _in_flight -= (_in_flight > len) ? len : _in_flight; // get the size of available sock space +#endif + + _ackedLength += len; size_t space = request->client()->space(); size_t headLen = _head.length(); @@ -378,13 +382,16 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u String out = _head.substring(0, space); _head = _head.substring(space); _writtenLength += request->client()->write(out.c_str(), out.length()); +#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT _in_flight += out.length(); --_in_flight_credit; // take a credit +#endif return out.length(); } } if (_state == RESPONSE_CONTENT) { +#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT // for response data we need to control the queue and in-flight fragmentation. Sending small chunks could give low latency, // but flood asynctcp's queue and fragment socket buffer space for large responses. // Let's ignore polled acks and acks in case when we have more in-flight data then the available socket buff space. @@ -396,6 +403,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u --_in_flight_credit; return 0; } +#endif size_t outLen; if (_chunked) { @@ -451,8 +459,10 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u if (outLen) { _writtenLength += request->client()->write((const char*)buf, outLen); +#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT _in_flight += outLen; --_in_flight_credit; // take a credit +#endif } if (_chunked) { From 1d154df485910704244b483c90b251fecd522698 Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Wed, 22 Jan 2025 14:13:06 +0100 Subject: [PATCH 2/2] Disable ASYNCWEBSERVER_USE_CHUNK_INFLIGHT by default and updated doc --- .github/workflows/ci.yml | 2 +- README.md | 2 ++ platformio.ini | 8 ++++---- src/ESPAsyncWebServer.h | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0507b4e2..48c280ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,7 +109,7 @@ jobs: - env: ci-arduino-3-no-json board: esp32dev - - env: ci-arduino-3-no-chunk-inflight + - env: ci-arduino-3-chunk-inflight board: esp32dev - env: ci-esp8266 diff --git a/README.md b/README.md index 2fb77534..8471e3d9 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,8 @@ I personally use the following configuration in my projects: -D CONFIG_ASYNC_TCP_STACK_SIZE=4096 // reduce the stack size (default is 16K) ``` +If you need to server long / slow requests using chunk encoding (like fiel download from SD Card), you might need to set `-D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=1`. + ## `AsyncWebSocketMessageBuffer` and `makeBuffer()` The fork from [yubox-node-org](https://github.com/yubox-node-org/ESPAsyncWebServer) introduces some breaking API changes compared to the original library, especially regarding the use of `std::shared_ptr>` for WebSocket. diff --git a/platformio.ini b/platformio.ini index 11406716..fe147c41 100644 --- a/platformio.ini +++ b/platformio.ini @@ -50,9 +50,9 @@ lib_deps = ; board = esp32-s3-devkitc-1 ; board = esp32-c6-devkitc-1 -[env:arduino-3-no-chunk-inflight] +[env:arduino-3-chunk-inflight] build_flags = ${env.build_flags} - -D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=0 + -D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=1 [env:perf-test-AsyncTCP] build_flags = ${env.build_flags} @@ -98,10 +98,10 @@ board = ${sysenv.PIO_BOARD} lib_deps = ESP32Async/AsyncTCP @ 3.3.2 -[env:ci-arduino-3-no-chunk-inflight] +[env:ci-arduino-3-chunk-inflight] board = ${sysenv.PIO_BOARD} build_flags = ${env.build_flags} - -D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=0 + -D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=1 [env:ci-esp8266] platform = espressif8266 diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index 2aaf72d6..0b2803bd 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -63,7 +63,7 @@ // See https://github.com/ESP32Async/ESPAsyncWebServer/commit/3d3456e9e81502a477f6498c44d0691499dda8f9#diff-646b25b11691c11dce25529e3abce843f0ba4bd07ab75ec9eee7e72b06dbf13fR388-R392 // This setting slowdown chunk serving but avoids crashing or deadlocks in the case where slow chunk responses are created, like file serving form SD Card #ifndef ASYNCWEBSERVER_USE_CHUNK_INFLIGHT - #define ASYNCWEBSERVER_USE_CHUNK_INFLIGHT 1 + #define ASYNCWEBSERVER_USE_CHUNK_INFLIGHT 0 #endif class AsyncWebServer;