APClass::onDisable() destroys the netif before unregistering the WIFI_EVENT handler
Board
ESP32-WROOM-32E
Device Description
Custom board with an ESP32-WROOM-32E module
Hardware Configuration
A cellular modem on one UART and a meter on another; neither is involved in this report
Version
v3.3.11
Type
Bug
IDE Name
PlatformIO (pioarduino platform-espressif32 55.03.311, arduino + espidf)
Operating System
Windows 11
Flash frequency
40M
PSRAM enabled
no
Upload speed
921600
Description
APClass::onDisable() tears the AP interface down and only afterwards unregisters the
WIFI_EVENT handler that reaches into it:
// libraries/WiFi/src/AP.cpp:181
bool APClass::onDisable() {
Network.removeEvent(_wifi_ap_event_handle);
_wifi_ap_event_handle = 0;
_esp_netif = NULL;
destroyNetif(); // :187
if (_ap_ev_instance != NULL) {
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &_ap_event_cb); // :189
_ap_ev_instance = NULL;
}
return true;
}destroyNetif() deletes the event group that the handler path uses, and clears the pointer
only afterwards:
// libraries/Network/src/NetworkInterface.cpp:275
vEventGroupDelete(_interface_event_group);
_interface_event_group = NULL;while clearStatusBits() tests that pointer and then dereferences it:
// libraries/Network/src/NetworkInterface.cpp:222
int NetworkInterface::clearStatusBits(int bits) {
if (!_interface_event_group) {
_initial_bits &= ~bits;
return _initial_bits;
}
return xEventGroupClearBits(_interface_event_group, bits); // :227
}So between destroyNetif() and the esp_event_handler_unregister() two lines later, a
WIFI_EVENT dispatched on the system event task can still reach _ap_event_cb ->
APClass::_onApEvent() -> clearStatusBits() on a deleted event group. The check-then-use in
clearStatusBits is not atomic against the delete either, so even a correctly ordered
unregister would leave a narrower window.
What makes the window more than theoretical is that switching the AP off raises the very
events that traverse it. _onApEvent calls clearStatusBits for two of them:
WIFI_EVENT_AP_STOP(AP.cpp:119) - always raised by the disable itselfWIFI_EVENT_AP_STADISCONNECTEDwhen the last station goes (AP.cpp:145) - raised for each station that was associated when the AP was taken down
Suggested fix
Unregister the WIFI_EVENT handler before destroyNetif(), so no dispatch can be in flight
against a destroyed interface:
bool APClass::onDisable() {
Network.removeEvent(_wifi_ap_event_handle);
_wifi_ap_event_handle = 0;
if (_ap_ev_instance != NULL) {
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &_ap_event_cb);
_ap_ev_instance = NULL;
}
_esp_netif = NULL;
destroyNetif();
return true;
}Note that esp_event_handler_unregister() does not wait for a handler already running on the
event loop task, so closing the ordering still leaves the check-then-use in clearStatusBits.
Guarding the group's lifetime there would close it properly.
What I can and cannot show
I found this by reading, while investigating panics that turned out to have a different cause (a stack overflow in my own code). I have no reproducer that pins a crash on this path, and I would rather say so than attach a backtrace that a later measurement showed belonged elsewhere.
Reporting it as a lifetime/ordering issue that is visible in the source, on the chance it is worth closing regardless.
Sketch
#include <WiFi.h>
void setup() {
WiFi.mode(WIFI_AP_STA);
WiFi.AP.begin();
WiFi.AP.create("repro-ap", "12345678");
delay(30000); // associate a phone here, so the teardown raises AP_STADISCONNECTED
WiFi.AP.end(); // onDisable(): destroyNetif() at AP.cpp:187, unregister at :189
}
void loop() {}Debug Message
None that I can attribute to this path. I found it by reading while investigating panics that a later measurement traced to a stack overflow in my own code, so attaching that backtrace here would point at the wrong thing.Other Steps to Reproduce
No response
I have checked existing issues, online documentation and the Troubleshooting Guide
- I confirm I have checked existing issues, online documentation and Troubleshooting guide.
Source: espressif/arduino-esp32