Skip to content

Commit

Permalink
Merge pull request #7 from ESP32Async/issue-4
Browse files Browse the repository at this point in the history
Introduce -D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=0|1 to be able to enabled/disable inflight in chunk response
  • Loading branch information
mathieucarbou authored Jan 22, 2025
2 parents 8183c6d + 1d154df commit cc6da5e
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ jobs:
- env: ci-arduino-3-no-json
board: esp32dev

- env: ci-arduino-3-chunk-inflight
board: esp32dev

- env: ci-esp8266
board: huzzah
- env: ci-esp8266
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::vector<uint8_t>>` for WebSocket.
Expand Down
9 changes: 9 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ lib_deps =
; board = esp32-s3-devkitc-1
; board = esp32-c6-devkitc-1

[env:arduino-3-chunk-inflight]
build_flags = ${env.build_flags}
-D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=1

[env:perf-test-AsyncTCP]
build_flags = ${env.build_flags}
-D PERF_TEST=1
Expand Down Expand Up @@ -94,6 +98,11 @@ board = ${sysenv.PIO_BOARD}
lib_deps =
ESP32Async/AsyncTCP @ 3.3.2

[env:ci-arduino-3-chunk-inflight]
board = ${sysenv.PIO_BOARD}
build_flags = ${env.build_flags}
-D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=1

[env:ci-esp8266]
platform = espressif8266
board = ${sysenv.PIO_BOARD}
Expand Down
6 changes: 6 additions & 0 deletions src/ESPAsyncWebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 0
#endif

class AsyncWebServer;
class AsyncWebServerRequest;
class AsyncWebServerResponse;
Expand Down
2 changes: 2 additions & 0 deletions src/WebResponseImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
16 changes: 13 additions & 3 deletions src/WebResponses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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.
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit cc6da5e

Please sign in to comment.