ESP32 Thread (Border Router) stop issues / esp_openthread_stop()
Posted: Tue Aug 25, 2026 8:57 am
Hey,
I ran into an issue regarding the ESP32 Thread Border router shutdown. I have opened an issue-ticket for this problem on the ESP-IDF Github but don't seem to get a reply, so I wondered if anyone on this forum had a similar experience before or has a working Thread shutdown implemented in their own project.
Here is the Issue description as documented on Github:
### Answers checklist.
- [x] I have read the documentation [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/) and the issue is not addressed there.
- [x] I have updated my IDF branch (master or release) to the latest version and checked that the issue is present there.
- [x] I have searched the issue tracker for a similar issue and not found a similar issue.
### IDF version.
6.0.2 & 5.5.3
### Espressif SoC revision.
ESP32 (host) + CC2652P7 as RCP
### Operating System used.
Linux
### How did you build your project?
VS Code IDE
### If you are using Windows, please specify command line type.
None
### Development Kit.
Custom Board
### Power Supply used.
USB
### What is the expected behavior?
I want to shut the Thread stack down at runtime, without rebooting. After disabling the stack with otThreadSetEnabled(instance, false) and otIp6SetEnabled(instance, false), I expect esp_openthread_stop() to tear down everything belonging to the Thread stack and its IPv6 interface: the mainloop task, the netif glue etc. and to leave the device in a clean state.
From there I should be able to either re-initialise Thread or bring up a different protocol on the same host.
### What is the actual behavior?
The device panics inside FreeRTOS:
The backtrace shows that the ESP tries to push something to a already deleted Queue and the Microcontroller crashes. Backtrace placed into DEBUG LOGS section.
### Steps to reproduce.
1. Bring up an OpenThread Border Router with esp_openthread_init() + esp_openthread_border_router_init(), netif glue attached as usual, Ethernet as backbone.
2. Let the device run for a few seconds.
3. From a task other than the OpenThread mainloop task, run the following shutdown sequence (error handling trimmed for readability):
### Debug Logs.
https://github.com/espressif/esp-idf/issues/18973
I ran into an issue regarding the ESP32 Thread Border router shutdown. I have opened an issue-ticket for this problem on the ESP-IDF Github but don't seem to get a reply, so I wondered if anyone on this forum had a similar experience before or has a working Thread shutdown implemented in their own project.
Here is the Issue description as documented on Github:
### Answers checklist.
- [x] I have read the documentation [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/) and the issue is not addressed there.
- [x] I have updated my IDF branch (master or release) to the latest version and checked that the issue is present there.
- [x] I have searched the issue tracker for a similar issue and not found a similar issue.
### IDF version.
6.0.2 & 5.5.3
### Espressif SoC revision.
ESP32 (host) + CC2652P7 as RCP
### Operating System used.
Linux
### How did you build your project?
VS Code IDE
### If you are using Windows, please specify command line type.
None
### Development Kit.
Custom Board
### Power Supply used.
USB
### What is the expected behavior?
I want to shut the Thread stack down at runtime, without rebooting. After disabling the stack with otThreadSetEnabled(instance, false) and otIp6SetEnabled(instance, false), I expect esp_openthread_stop() to tear down everything belonging to the Thread stack and its IPv6 interface: the mainloop task, the netif glue etc. and to leave the device in a clean state.
From there I should be able to either re-initialise Thread or bring up a different protocol on the same host.
### What is the actual behavior?
The device panics inside FreeRTOS:
Code: Select all
assert failed: xQueueGenericSend queue.c:936 (pxQueue)
### Steps to reproduce.
1. Bring up an OpenThread Border Router with esp_openthread_init() + esp_openthread_border_router_init(), netif glue attached as usual, Ethernet as backbone.
2. Let the device run for a few seconds.
3. From a task other than the OpenThread mainloop task, run the following shutdown sequence (error handling trimmed for readability):
Code: Select all
esp_err_t ThreadController::stop()
{
esp_openthread_register_rcp_failure_handler(nullptr);
esp_openthread_lock_acquire(portMAX_DELAY);
esp_openthread_border_router_deinit();
esp_openthread_lock_release();
otInstance *instance = esp_openthread_get_instance();
if (!esp_openthread_lock_acquire(portMAX_DELAY)) {
ESP_LOGE(TAG, "Could not acquire OpenThread lock");
return ESP_ERR_TIMEOUT;
}
otError ot_error = OT_ERROR_NONE;
if (otThreadGetDeviceRole(instance) != OT_DEVICE_ROLE_DISABLED) {
ot_error = otThreadSetEnabled(instance, false);
}
if (ot_error == OT_ERROR_NONE && otIp6IsEnabled(instance)) {
ot_error = otIp6SetEnabled(instance, false);
}
while (otTaskletsArePending(instance)) {
otTaskletsProcess(instance);
}
esp_openthread_lock_release();
// barrier / delay to let pending esp_event and lwIP work drain
return esp_openthread_stop(); // <-- assert happens here
}
Code: Select all
E (71375) OPENTHREAD: esp_openthread_platform_process(217): process dns_upstream_qu failed
E (71385) OPENTHREAD: esp_openthread_platform_process failed
D (71385) event: running post OPENTHREAD_EVENT:10 with handler 0x401133cc and context 0x3ffdbdf0 on loop 0x3ffc9654
--- 0x401133cc: esp_netif_action_leave_ip6_multicast_group at <IDF_PATH>/components/esp_netif/esp_netif_handlers.c:108
D (71405) esp_netif_handlers: esp_netif action leave_ip6_multicast_group with netif0x3ffdb8d8 from event_id=10
assert failed: xQueueGenericSend queue.c:936 (pxQueue)
Backtrace: 0x4008a7c9:0x3ffcb590 0x4008a795:0x3ffcb5b0 0x40089c1a:0x3ffcb5d0 0x401eefac:0x3ffcb6f0 0x4011fc1f:0x3ffcb730 0x40114a5b:0x3ffcb760 0x4011f75a:0x3ffcb790 0x4010a54d:0x3ffcb7b0 0x4010a5b0:0x3ffcb800 0x4010ac12:0x3ffcb830 0x4010af57:0x3ffcb870 0x40113b29:0x3ffcb8b0 0x40113ab5:0x3ffcb900 0x400ffb50:0x3ffcb920
--- 0x4008a7c9: panic_abort at <IDF_PATH>/components/esp_system/panic.c:464
--- 0x4008a795: esp_system_abort at <IDF_PATH>/components/esp_system/port/esp_system_chip.c:87
--- 0x40089c1a: __assert_func at <IDF_PATH>/components/esp_libc/src/assert.c:81
--- 0x401eefac: xQueueGenericSend at <IDF_PATH>/components/freertos/FreeRTOS-Kernel/queue.c:937
--- 0x4011fc1f: openthread_netif_transmit at <IDF_PATH>/components/openthread/src/esp_openthread_netif_glue.c:210
--- 0x40114a5b: esp_netif_transmit at <IDF_PATH>/components/esp_netif/lwip/esp_netif_lwip.c:1433
--- 0x4011f75a: openthread_output_ip6 at <IDF_PATH>/components/openthread/src/esp_openthread_lwip_netif.c:52
--- 0x4010a54d: ip6_output_if_src at <IDF_PATH>/components/lwip/lwip/src/core/ipv6/ip6.c:1295
--- 0x4010a5b0: ip6_output_if at <IDF_PATH>/components/lwip/lwip/src/core/ipv6/ip6.c:1185
--- 0x4010ac12: mld6_send at <IDF_PATH>/components/lwip/lwip/src/core/ipv6/mld6.c:662
--- 0x4010af57: mld6_leavegroup_netif at <IDF_PATH>/components/lwip/lwip/src/core/ipv6/mld6.c:473
--- 0x40113b29: esp_netif_leave_ip6_multicast_group_api at <IDF_PATH>/components/esp_netif/lwip/esp_netif_lwip.c:2957
--- 0x40113ab5: esp_netif_api_cb at <IDF_PATH>/components/esp_netif/lwip/esp_netif_lwip.c:227
--- 0x400ffb50: tcpip_thread at <IDF_PATH>/components/lwip/lwip/src/api/tcpip.c:136