[openthread] Guard InstanceLock against uninitialized semaphore (#14940)
Some checks failed
CI for docker images / Build docker containers (docker, ubuntu-24.04) (push) Has been cancelled
CI for docker images / Build docker containers (docker, ubuntu-24.04-arm) (push) Has been cancelled
CI for docker images / Build docker containers (ha-addon, ubuntu-24.04) (push) Has been cancelled
CI for docker images / Build docker containers (ha-addon, ubuntu-24.04-arm) (push) Has been cancelled
CI / Create common environment (push) Has been cancelled
CI / Check pylint (push) Has been cancelled
CI / Run script/ci-custom (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.11) (push) Has been cancelled
CI / Run pytest (macOS-latest, 3.14) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.11) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.13) (push) Has been cancelled
CI / Run pytest (ubuntu-latest, 3.14) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.11) (push) Has been cancelled
CI / Run pytest (windows-latest, 3.14) (push) Has been cancelled
CI / Determine which jobs to run (push) Has been cancelled
CI / Run integration tests (push) Has been cancelled
CI / Run C++ unit tests (push) Has been cancelled
CI / Run CodSpeed benchmarks (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 IDF (push) Has been cancelled
CI / Run script/clang-tidy for ESP8266 (push) Has been cancelled
CI / Run script/clang-tidy for ZEPHYR (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Has been cancelled
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Has been cancelled
CI / Test components batch (${{ matrix.components }}) (push) Has been cancelled
CI / pre-commit.ci lite (push) Has been cancelled
CI / Build target branch for memory impact (push) Has been cancelled
CI / Build PR branch for memory impact (push) Has been cancelled
CI / Comment memory impact (push) Has been cancelled
CI / CI Status (push) Has been cancelled
Synchronise Device Classes from Home Assistant / Sync Device Classes (push) Has been cancelled

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Swoboda
2026-03-18 21:23:47 -04:00
committed by GitHub
parent f8be27ce6d
commit 403ba262c6
2 changed files with 25 additions and 1 deletions

View File

@@ -11,6 +11,7 @@
#include <openthread/instance.h>
#include <openthread/thread.h>
#include <atomic>
#include <optional>
#include <vector>
@@ -28,6 +29,8 @@ class OpenThreadComponent : public Component {
float get_setup_priority() const override { return setup_priority::WIFI; }
bool is_connected() const { return this->connected_; }
/// Returns true once esp_openthread_init() has completed and the OT lock is usable.
bool is_lock_initialized() const { return this->lock_initialized_; }
network::IPAddresses get_ip_addresses();
std::optional<otIp6Address> get_omr_address();
void ot_main();
@@ -51,6 +54,7 @@ class OpenThreadComponent : public Component {
uint32_t poll_period_{0};
#endif
std::optional<int8_t> output_power_{};
std::atomic<bool> lock_initialized_{false};
bool teardown_started_{false};
bool teardown_complete_{false};
bool connected_{false};

View File

@@ -8,6 +8,7 @@
#include "esp_openthread_lock.h"
#include "esp_task_wdt.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -81,6 +82,9 @@ void OpenThreadComponent::ot_main() {
// Initialize the OpenThread stack
// otLoggingSetLevel(OT_LOG_LEVEL_DEBG);
ESP_ERROR_CHECK(esp_openthread_init(&config));
// Mark lock as initialized so InstanceLock callers know it's safe to acquire.
// Must be set after esp_openthread_init() which creates the internal semaphore.
this->lock_initialized_ = true;
// Fetch OT instance once to avoid repeated call into OT stack
otInstance *instance = esp_openthread_get_instance();
@@ -180,7 +184,8 @@ void OpenThreadComponent::ot_main() {
esp_openthread_launch_mainloop();
// Clean up
// Clean up - reset lock flag before deinit destroys the semaphore
this->lock_initialized_ = false;
esp_openthread_deinit();
esp_openthread_netif_glue_deinit();
esp_netif_destroy(openthread_netif);
@@ -210,6 +215,9 @@ network::IPAddresses OpenThreadComponent::get_ip_addresses() {
otInstance *OpenThreadComponent::get_openthread_instance_() { return esp_openthread_get_instance(); }
std::optional<InstanceLock> InstanceLock::try_acquire(int delay) {
if (!global_openthread_component->is_lock_initialized()) {
return {};
}
if (esp_openthread_lock_acquire(delay)) {
return InstanceLock();
}
@@ -217,6 +225,18 @@ std::optional<InstanceLock> InstanceLock::try_acquire(int delay) {
}
InstanceLock InstanceLock::acquire() {
// Wait for the lock to be created by ot_main() before attempting to acquire it.
// esp_openthread_lock_acquire() will assert-crash if called before esp_openthread_init().
constexpr uint32_t lock_init_timeout_ms = 10000;
uint32_t start = millis();
while (!global_openthread_component->is_lock_initialized()) {
if (millis() - start > lock_init_timeout_ms) {
ESP_LOGE(TAG, "OpenThread lock not initialized after %" PRIu32 "ms, aborting", lock_init_timeout_ms);
abort();
}
delay(10);
esp_task_wdt_reset();
}
while (!esp_openthread_lock_acquire(100)) {
esp_task_wdt_reset();
}