From db838d39009769c74f3809f906f6d1c4bb69685f Mon Sep 17 00:00:00 2001 From: RobertGnz Date: Fri, 2 Dec 2022 21:15:01 +0100 Subject: [PATCH 1/2] Api for saving heap memory when WiFiClient class is used by a local end acting as server The propose API is intended to avoid this drawback by calling the abort function of ClientContext which in turn calls tcp_abort which calls tcp_abandon. The connection is aborted and notified to the client with a RESET and the pcb and ressources associated are immediately released increasing the available heap memory. --- libraries/ESP8266WiFi/src/WiFiClient.cpp | 11 +++++ libraries/ESP8266WiFi/src/WiFiClient.h | 53 +++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/libraries/ESP8266WiFi/src/WiFiClient.cpp b/libraries/ESP8266WiFi/src/WiFiClient.cpp index 6cdd5d1803..fb10209ec0 100644 --- a/libraries/ESP8266WiFi/src/WiFiClient.cpp +++ b/libraries/ESP8266WiFi/src/WiFiClient.cpp @@ -376,6 +376,17 @@ uint16_t WiFiClient::localPort() return _client->getLocalPort(); } +// Api for heap saving. Optional use instead of WiFiClient::stop to systematically retreive some heap memory +// and avoiding server crashes in case of frequent clients connections. +void WiFiClient::abort() +{ + if (!_client) + return; + + flush(0); // Flush output buffer. Don't make any use of return boolean. + _client->abort(); // Wich in turn calls tcp_abort which calls tcp_abandon(). +} + void WiFiClient::stopAll() { for (WiFiClient* it = _s_first; it; it = it->_next) { diff --git a/libraries/ESP8266WiFi/src/WiFiClient.h b/libraries/ESP8266WiFi/src/WiFiClient.h index 170c983c0e..017331e5fe 100644 --- a/libraries/ESP8266WiFi/src/WiFiClient.h +++ b/libraries/ESP8266WiFi/src/WiFiClient.h @@ -103,7 +103,58 @@ class WiFiClient : public Client, public SList { friend class WiFiServer; using Print::write; - + +// Api for saving heap when Client class is used by a Server (WiFiServer class): Client = Server.available(). + +// Suppose the local end is the server and the remote end is the client, we will deal with heap memory at the local end. + +// When the local application (server) decides to close an active connection with a remote end it issues an Client.stop. +// the stop() function calls the close() function of ClientContext class which in turn calls tcp_close. +// The connexion is closed by tcp_close and the protocol control block (pcb) can be put in the following states depending +// on the requests sent by the remote: CLOSING, FIN_WAIT_1 and FIN_WAIT_2. In theses states pcbs are not freed, then consume +// some memory heap. +// If an acknowledgment from the remote end is received, the pcb enter in TIME_WAIT state for some minutes but pcbs in TIME_WAIT +// state are not freed. Then consume some heap memory. +// TIME_WAIT pcbs are automatically freed after some minutes or can be freed for instance issuing an tcp_kill_timewait() +// in the local application which will free the oldest pcb in TIME_WAIT state. + +// If the connection is first closed from the remote end (the client), the local end (server) receive a connection termination request. It then +// acknowledge it and enter in CLOSE_WAIT state waiting for a connection termination request from the local application. +// It then send a termination request and enter in LAST_ACK state until it receive an acknowledgment from the remote end. +// After receiving the acknowledgment it enter in ClOSED state and the local pcb is freed leaving some room in the heap memory. + +// To summarize, when a connexion termination request is send by one end (remote or local), the local pcb is not freed immediatly. +// This pcb can be in the following states: FIN_WAIT_1, FIN_WAIT_2, CLOSING, TIME_WAIT, CLOSE_WAIT, LAST_ACK. +// As a consequence, some old pcbs from old closed connections can still consume heap memory. + +// The local application can call tcp_kill_timewait hoping it will free some TIME_WAIT state pcbs. But if the server +// receive frequent connections requests and close them after sending whatever it has to send, there may be zero pcbs +// in TIME_WAIT state among all previously closed pcbs. + +// In case of insufficient memory to accept a new connection, lwip has developped a strategy: it successively tries +// to kill the oldest pcb in TIME_WAIT state, or in LAST_ACK state or in CLOSING state or the oldest active connection +// with lower priority than the new one. + +// A a matter of fact this "urgent" strategy is deployed only when very few heap memory remain available (less than some kb). +// In case of success, Client.available returns a valid Client but the local application will crash when sending or receiving +// data from the client (Client.read ou readuntil or available) because this need more heap memory and just some kb were +// freed in lwip to allocate the new pcb structure ans start the new connection. + +// The propose API is intended to avoid this drawback by calling the abort function of ClientContext which in turn +// calls tcp_abort which calls tcp_abandon. The connection is aborted and notified to the client with a RESET +// and the pcb and ressources associated are immediately released increasing the available heap memory. + +// This API can be used in two ways: + +// WiFiClient Client; +// 1- Replace every Client.stop() with Client.abort() +// or +// 2- In conjonction with Client.stop in the following style: +// # define MIN_HEAP_FREE 20000 // or whatever min available heap memory convienent for your application +// if ( ESP.getFreeHeap() >= MIN_HEAP_FREE ) Client.stop(); +// else Client.abort(); + void abort(); + static void stopAll(); static void stopAllExcept(WiFiClient * c); From 48a4a58cfe77c890ac1b73a69325659719a47fc8 Mon Sep 17 00:00:00 2001 From: Maxim Prokhorov Date: Tue, 6 Dec 2022 12:23:25 +0300 Subject: [PATCH 2/2] docs, shorter description PR still has a full text, API docs preferably should not contain too much of impl details that may or may not be relevant to a common user --- doc/esp8266wifi/client-class.rst | 29 ++++++++++++++ libraries/ESP8266WiFi/src/WiFiClient.h | 55 ++------------------------ 2 files changed, 33 insertions(+), 51 deletions(-) diff --git a/doc/esp8266wifi/client-class.rst b/doc/esp8266wifi/client-class.rst index 11e412181e..7875a416ff 100644 --- a/doc/esp8266wifi/client-class.rst +++ b/doc/esp8266wifi/client-class.rst @@ -29,6 +29,35 @@ Default input value 0 means that effective value is left at the discretion of th ``stop()`` returns ``false`` in case of an issue when closing the client (for instance a timed-out ``flush``). Depending on implementation, its parameter can be passed to ``flush()``. +abort +~~~~~ + +.. code:: cpp + + void abort(); + + +Originally proposed in `#8738 `__ +Unlike ``stop()``, immediately shuts down internal connection object. + +Under usual circumstances, we either enter ``CLOSE_WAIT`` or ``TIME_WAIT`` state. But, the connection object is not freed right away, and requires us to either +* wait until ``malloc()`` returns ``NULL`` when our TCP stack tries to allocate memory for a new connection +* manually call ``tcp_kill_timewait()`` to forcibly stop the 'oldest' connection + +This API frees up resources used by the connection. Consider using it instead of ``stop()`` if your application handles a lot of clients and frequently runs out of available heap memory. + +*Example:* + +.. code:: cpp + # define MIN_HEAP_FREE 20000 // or whatever min available heap memory convienent for your application + auto client = server.accept(); + // ... do something with the client object ... + if (ESP.getFreeHeap() >= MIN_HEAP_FREE) { + client.stop(); + } else { + client.abort(); + } + setNoDelay ~~~~~~~~~~ diff --git a/libraries/ESP8266WiFi/src/WiFiClient.h b/libraries/ESP8266WiFi/src/WiFiClient.h index 017331e5fe..41ec5d8547 100644 --- a/libraries/ESP8266WiFi/src/WiFiClient.h +++ b/libraries/ESP8266WiFi/src/WiFiClient.h @@ -104,57 +104,6 @@ class WiFiClient : public Client, public SList { using Print::write; -// Api for saving heap when Client class is used by a Server (WiFiServer class): Client = Server.available(). - -// Suppose the local end is the server and the remote end is the client, we will deal with heap memory at the local end. - -// When the local application (server) decides to close an active connection with a remote end it issues an Client.stop. -// the stop() function calls the close() function of ClientContext class which in turn calls tcp_close. -// The connexion is closed by tcp_close and the protocol control block (pcb) can be put in the following states depending -// on the requests sent by the remote: CLOSING, FIN_WAIT_1 and FIN_WAIT_2. In theses states pcbs are not freed, then consume -// some memory heap. -// If an acknowledgment from the remote end is received, the pcb enter in TIME_WAIT state for some minutes but pcbs in TIME_WAIT -// state are not freed. Then consume some heap memory. -// TIME_WAIT pcbs are automatically freed after some minutes or can be freed for instance issuing an tcp_kill_timewait() -// in the local application which will free the oldest pcb in TIME_WAIT state. - -// If the connection is first closed from the remote end (the client), the local end (server) receive a connection termination request. It then -// acknowledge it and enter in CLOSE_WAIT state waiting for a connection termination request from the local application. -// It then send a termination request and enter in LAST_ACK state until it receive an acknowledgment from the remote end. -// After receiving the acknowledgment it enter in ClOSED state and the local pcb is freed leaving some room in the heap memory. - -// To summarize, when a connexion termination request is send by one end (remote or local), the local pcb is not freed immediatly. -// This pcb can be in the following states: FIN_WAIT_1, FIN_WAIT_2, CLOSING, TIME_WAIT, CLOSE_WAIT, LAST_ACK. -// As a consequence, some old pcbs from old closed connections can still consume heap memory. - -// The local application can call tcp_kill_timewait hoping it will free some TIME_WAIT state pcbs. But if the server -// receive frequent connections requests and close them after sending whatever it has to send, there may be zero pcbs -// in TIME_WAIT state among all previously closed pcbs. - -// In case of insufficient memory to accept a new connection, lwip has developped a strategy: it successively tries -// to kill the oldest pcb in TIME_WAIT state, or in LAST_ACK state or in CLOSING state or the oldest active connection -// with lower priority than the new one. - -// A a matter of fact this "urgent" strategy is deployed only when very few heap memory remain available (less than some kb). -// In case of success, Client.available returns a valid Client but the local application will crash when sending or receiving -// data from the client (Client.read ou readuntil or available) because this need more heap memory and just some kb were -// freed in lwip to allocate the new pcb structure ans start the new connection. - -// The propose API is intended to avoid this drawback by calling the abort function of ClientContext which in turn -// calls tcp_abort which calls tcp_abandon. The connection is aborted and notified to the client with a RESET -// and the pcb and ressources associated are immediately released increasing the available heap memory. - -// This API can be used in two ways: - -// WiFiClient Client; -// 1- Replace every Client.stop() with Client.abort() -// or -// 2- In conjonction with Client.stop in the following style: -// # define MIN_HEAP_FREE 20000 // or whatever min available heap memory convienent for your application -// if ( ESP.getFreeHeap() >= MIN_HEAP_FREE ) Client.stop(); -// else Client.abort(); - void abort(); - static void stopAll(); static void stopAllExcept(WiFiClient * c); @@ -199,6 +148,10 @@ class WiFiClient : public Client, public SList { virtual bool outputCanTimeout () override { return connected(); } virtual bool inputCanTimeout () override { return connected(); } + // Immediately stops this client instance. + // Unlike stop(), does not wait to gracefuly shutdown the connection. + void abort(); + protected: static int8_t _s_connected(void* arg, void* tpcb, int8_t err);