Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
Back to tool/Back to issues
#12881·arduino-esp32

APClass::onDisable() destroys the netif before unregistering the WIFI_EVENT handler

Author: lucasmazieroCreated Sep 3, 2026Updated Sep 7, 2026
LabelsStatus: Awaiting triage

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:

cpp
// 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:

cpp
// libraries/Network/src/NetworkInterface.cpp:275
vEventGroupDelete(_interface_event_group);
_interface_event_group = NULL;

while clearStatusBits() tests that pointer and then dereferences it:

cpp
// 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 itself
  • WIFI_EVENT_AP_STADISCONNECTED when 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:

cpp
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

cpp
#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

plain
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

View original on GitHubView discussion on GitHub