Skip to content

Commit aa2502e

Browse files
committed
WebServer: Improve heap checks
Use the correct functions and set good limits for ESP32. Also separate heap and maximum allocation size checks to improve tracing clarity.
1 parent 6ef43e0 commit aa2502e

1 file changed

Lines changed: 50 additions & 19 deletions

File tree

src/WebServer.cpp

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,22 @@ struct guard_type {
5050
#endif
5151

5252
#ifndef ASYNCWEBSERVER_MINIMUM_ALLOC
53+
#ifdef ESP8266
5354
#define ASYNCWEBSERVER_MINIMUM_ALLOC 1024
55+
#else
56+
#define ASYNCWEBSERVER_MINIMUM_ALLOC 4096
57+
#endif
5458
#endif
5559

5660
#ifndef ASYNCWEBSERVER_MINIMUM_HEAP
61+
#ifdef ESP8266
5762
#define ASYNCWEBSERVER_MINIMUM_HEAP 2048
63+
#else /* ESP32 */
64+
// This is a *vastly* larger number. The ESP32 TCP stack does a *ton* of dynamic
65+
// allocation in the cricital path; and if we OOM the stack, it hangs up connections,
66+
// leaking bits and pieces and leaving the available memory fragmented.
67+
#define ASYNCWEBSERVER_MINIMUM_HEAP 8192
68+
#endif
5869
#endif
5970

6071

@@ -79,23 +90,34 @@ static bool minimal_send_503(AsyncClient* c) {
7990
auto w = c->write(MSG_503, sizeof(MSG_503)-1, ASYNC_WRITE_FLAG_COPY);
8091

8192
// assume any nonzero value is success
82-
DEBUG_PRINTFP("*** Sent 503 to %d (%d), result %d\n", (intptr_t) c, c->getRemotePort(), w);
93+
DEBUG_PRINTFP("*** Sent 503 to %08X (%d), result %d\n", (intptr_t) c, c->getRemotePort(), w);
8394
if (w == 0) {
8495
c->close(true); // sorry bud, we're really that strapped for ram
8596
}
8697
return (w != 0);
8798
}
8899

89100
#ifdef ESP8266
90-
#define GET_MAX_BLOCK_SIZE getMaxFreeBlockSize
101+
static inline size_t get_heap_available() {
102+
return ESP.getFreeHeap();
103+
}
104+
105+
static inline size_t get_heap_alloc() {
106+
return ESP.getMaxFreeBlockSize();
107+
}
91108
#else
92-
#define GET_MAX_BLOCK_SIZE getMaxAllocHeap
93-
#endif
109+
// Platform functions don't correctly check for malloc()'s heap; at least on ESP32-WROVER
110+
// they incorrectly include some internal memory that is not accessible to malloc().
111+
// Reimplement using the correct MALLOC_CAPs.
112+
static inline size_t get_heap_available() {
113+
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_DEFAULT);
114+
}
94115

95-
static bool heap_ok(size_t minHeap) {
96-
return (ESP.getFreeHeap() > minHeap)
97-
&& (ESP.GET_MAX_BLOCK_SIZE() > ASYNCWEBSERVER_MINIMUM_ALLOC);
116+
static inline size_t get_heap_alloc() {
117+
return heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_DEFAULT);
98118
}
119+
#endif
120+
99121

100122
AsyncWebServer::AsyncWebServer(uint16_t port)
101123
: AsyncWebServer(IPADDR_ANY, port)
@@ -128,33 +150,38 @@ AsyncWebServer::AsyncWebServer(IPAddress addr, uint16_t port, const AsyncWebServ
128150
if(c == NULL)
129151
return;
130152

131-
if (!heap_ok(ASYNCWEBSERVER_MINIMUM_HEAP)) {
153+
auto heap_avail = get_heap_available();
154+
auto heap_alloc = get_heap_alloc();
155+
156+
if ((heap_avail < ASYNCWEBSERVER_MINIMUM_HEAP)
157+
|| (heap_alloc < ASYNCWEBSERVER_MINIMUM_ALLOC)) {
132158
// Protect ourselves from crashing - just abandon this request.
133-
DEBUG_PRINTFP("*** Dropping client %d (%d): %d, %d\n", (intptr_t) c, c->getRemotePort(), _requestQueue.length(), ESP.getFreeHeap());
159+
DEBUG_PRINTFP("*** Dropping client %08X (%d): %d, %d/%d\n", (intptr_t) c, c->getRemotePort(), _requestQueue.length(), heap_alloc, heap_avail);
134160
c->close(true);
135161
delete c;
136162
return;
137163
}
138164

139165
guard();
166+
auto queue_length = _requestQueue.length();
140167

141-
if (((_queueLimits.queueHeapRequired > 0) && !heap_ok(_queueLimits.queueHeapRequired))
142-
|| ((_queueLimits.nMax > 0) && (_requestQueue.length() >= _queueLimits.nMax))
168+
if (((_queueLimits.nMax > 0) && (queue_length >= _queueLimits.nMax))
169+
|| ((_queueLimits.queueHeapRequired > 0) && (heap_avail < _queueLimits.queueHeapRequired))
143170
) {
144171
// Don't even allocate anything we can avoid. Tell the client we're in trouble with a static response.
145-
DEBUG_PRINTFP("*** Rejecting client %d (%d): %d, %d\n", (intptr_t) c, c->getRemotePort(), _requestQueue.length(), ESP.getFreeHeap());
172+
DEBUG_PRINTFP("*** Rejecting client %08X (%d): %d, %d/%d\n", (intptr_t) c, c->getRemotePort(), _requestQueue.length(), heap_alloc, heap_avail);
146173
c->setNoDelay(true);
147174
c->onDisconnect([](void*r, AsyncClient* rc){
148-
DEBUG_PRINTFP("*** Client %d (%d) disconnected\n", (intptr_t)rc, rc->getRemotePort());
175+
DEBUG_PRINTFP("*** Client %08X (%d) disconnected\n", (intptr_t)rc, rc->getRemotePort());
149176
delete rc; // There is almost certainly something wrong with this - it's not OK to delete a function object while it's running
150177
});
151178
c->onAck([](void *, AsyncClient* rc, size_t s, uint32_t ){
152-
rc->close(true);
179+
if (s) rc->close(true);
153180
});
154181
c->onData([](void*, AsyncClient* rc, void*, size_t){
155182
rc->onData({});
156-
minimal_send_503(rc);
157-
});
183+
minimal_send_503(rc);
184+
});
158185
return;
159186
}
160187

@@ -351,7 +378,8 @@ void AsyncWebServer::processQueue(){
351378
DEBUG_PRINTFP("Queue: %d entries, %d running, %d queued\n", count, active, queued);
352379

353380
do {
354-
auto heap_ok = ESP.getFreeHeap() >= (_queueLimits.requestHeapRequired + _queueLimits.queueHeapRequired);
381+
auto heap_ok = get_heap_available() > _queueLimits.requestHeapRequired;
382+
auto alloc_ok = get_heap_alloc() > ASYNCWEBSERVER_MINIMUM_ALLOC;
355383
size_t active_entries = 0;
356384
AsyncWebServerRequest* next_queued_request = nullptr;
357385

@@ -369,7 +397,10 @@ void AsyncWebServer::processQueue(){
369397

370398
if (!next_queued_request) break; // all done
371399
if ((_queueLimits.nParallel > 0) && (active_entries >= _queueLimits.nParallel)) break; // lots running
372-
if ((active_entries > 0) && (!heap_ok)) break; // heap not ok
400+
if ((active_entries > 0) && (!heap_ok || !alloc_ok)) {
401+
DEBUG_PRINTFP("Can't queue more, heap %d alloc %d\n", heap_ok, alloc_ok);
402+
break;
403+
}
373404
next_queued_request->_handleRequest();
374405
} while(1); // as long as we have memory and queued requests
375406

@@ -385,7 +416,7 @@ void AsyncWebServer::processQueue(){
385416

386417
void AsyncWebServer::_dequeue(AsyncWebServerRequest *request){
387418
{
388-
DEBUG_PRINTFP("Removing %d from queue\n", (intptr_t) request);
419+
DEBUG_PRINTFP("Removing %08X from queue\n", (intptr_t) request);
389420
guard();
390421
_requestQueue.remove(request);
391422
}

0 commit comments

Comments
 (0)