diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3fe20738..c3193381 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: - name: Install AsyncTCP (ESP32) if: ${{ matrix.core == 'esp32:esp32' }} - run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/mathieucarbou/AsyncTCP#v3.2.15 + run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/mathieucarbou/AsyncTCP#v3.3.0 - name: Install ESPAsyncTCP (ESP8266) if: ${{ matrix.core == 'esp8266:esp8266' }} diff --git a/README.md b/README.md index de4f91a6..8e2d2b32 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,8 @@ lib_deps = mathieucarbou/ESPAsyncWebServer @ 3.4.0 **Dependencies:** -- **ESP32 with AsyncTCP**: `mathieucarbou/AsyncTCP @ 3.2.15` - Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.15](https://github.com/mathieucarbou/AsyncTCP/releases) +- **ESP32 with AsyncTCP**: `mathieucarbou/AsyncTCP @ 3.3.0` + Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.3.0](https://github.com/mathieucarbou/AsyncTCP/releases) - **ESP32 with AsyncTCPSock**: `https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip` @@ -99,7 +99,7 @@ AsyncTCPSock can be used instead of AsyncTCP by excluding AsyncTCP from the libr lib_compat_mode = strict lib_ldf_mode = chain lib_deps = - ; mathieucarbou/AsyncTCP @ 3.2.15 + ; mathieucarbou/AsyncTCP @ 3.3.0 https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip mathieucarbou/ESPAsyncWebServer @ 3.4.0 lib_ignore = @@ -116,7 +116,7 @@ Performance of `mathieucarbou/ESPAsyncWebServer @ 3.4.0`: > autocannon -c 10 -w 10 -d 20 http://192.168.4.1 ``` -With `mathieucarbou/AsyncTCP @ 3.2.15` +With `mathieucarbou/AsyncTCP @ 3.3.0` [![](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png)](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png) diff --git a/docs/index.md b/docs/index.md index de4f91a6..8e2d2b32 100644 --- a/docs/index.md +++ b/docs/index.md @@ -80,8 +80,8 @@ lib_deps = mathieucarbou/ESPAsyncWebServer @ 3.4.0 **Dependencies:** -- **ESP32 with AsyncTCP**: `mathieucarbou/AsyncTCP @ 3.2.15` - Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.15](https://github.com/mathieucarbou/AsyncTCP/releases) +- **ESP32 with AsyncTCP**: `mathieucarbou/AsyncTCP @ 3.3.0` + Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.3.0](https://github.com/mathieucarbou/AsyncTCP/releases) - **ESP32 with AsyncTCPSock**: `https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip` @@ -99,7 +99,7 @@ AsyncTCPSock can be used instead of AsyncTCP by excluding AsyncTCP from the libr lib_compat_mode = strict lib_ldf_mode = chain lib_deps = - ; mathieucarbou/AsyncTCP @ 3.2.15 + ; mathieucarbou/AsyncTCP @ 3.3.0 https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip mathieucarbou/ESPAsyncWebServer @ 3.4.0 lib_ignore = @@ -116,7 +116,7 @@ Performance of `mathieucarbou/ESPAsyncWebServer @ 3.4.0`: > autocannon -c 10 -w 10 -d 20 http://192.168.4.1 ``` -With `mathieucarbou/AsyncTCP @ 3.2.15` +With `mathieucarbou/AsyncTCP @ 3.3.0` [![](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png)](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png) diff --git a/library.json b/library.json index 804e915b..e37ab94d 100644 --- a/library.json +++ b/library.json @@ -28,7 +28,7 @@ { "owner": "mathieucarbou", "name": "AsyncTCP", - "version": "^3.2.15", + "version": "^3.3.0", "platforms": "espressif32" }, { diff --git a/platformio.ini b/platformio.ini index 4a59440a..099d5bc7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -31,7 +31,7 @@ lib_deps = ; bblanchon/ArduinoJson @ 5.13.4 ; bblanchon/ArduinoJson @ 6.21.5 bblanchon/ArduinoJson @ 7.2.1 - mathieucarbou/AsyncTCP @ 3.2.15 + mathieucarbou/AsyncTCP @ 3.3.0 board = esp32dev board_build.partitions = partitions-4MB.csv board_build.filesystem = littlefs @@ -49,7 +49,7 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ ; board = esp32-s3-devkitc-1 ; board = esp32-c6-devkitc-1 lib_deps = - mathieucarbou/AsyncTCP @ 3.2.15 + mathieucarbou/AsyncTCP @ 3.3.0 [env:arduino-310] platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc3/platform-espressif32.zip @@ -102,7 +102,7 @@ board = ${sysenv.PIO_BOARD} platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip board = ${sysenv.PIO_BOARD} lib_deps = - mathieucarbou/AsyncTCP @ 3.2.15 + mathieucarbou/AsyncTCP @ 3.3.0 [env:ci-arduino-310] platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc3/platform-espressif32.zip diff --git a/src/WebResponseImpl.h b/src/WebResponseImpl.h index b58c5bbb..fa462b69 100644 --- a/src/WebResponseImpl.h +++ b/src/WebResponseImpl.h @@ -47,6 +47,10 @@ class AsyncBasicResponse : public AsyncWebServerResponse { class AsyncAbstractResponse : public AsyncWebServerResponse { private: + // 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}; 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 7a26e923..bf8235e9 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -352,7 +352,21 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u request->client()->close(); return 0; } + // 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 + log_d("(chunk) out of in-flight credits"); +#endif + return 0; + } + _ackedLength += len; + _in_flight -= (_in_flight > len) ? len : _in_flight; + // get the size of available sock space size_t space = request->client()->space(); size_t headLen = _head.length(); @@ -364,16 +378,31 @@ 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()); + _in_flight += out.length(); + --_in_flight_credit; // take a credit return out.length(); } } if (_state == RESPONSE_CONTENT) { + // 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. + // That way we could balance on having half the buffer in-flight while another half is filling up, while minimizing events in asynctcp q + if (_in_flight > space) { + // log_d("defer user call %u/%u", _in_flight, space); + // take the credit back since we are ignoring this ack and rely on other inflight data + if (len) + --_in_flight_credit; + return 0; + } + size_t outLen; if (_chunked) { if (space <= 8) { return 0; } + outLen = space; } else if (!_sendContentLength) { outLen = space; @@ -422,6 +451,8 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u if (outLen) { _writtenLength += request->client()->write((const char*)buf, outLen); + _in_flight += outLen; + --_in_flight_credit; // take a credit } if (_chunked) {