diff --git a/components/esp_event/esp_event.c b/components/esp_event/esp_event.c index 516483288a..4edb3ff2da 100644 --- a/components/esp_event/esp_event.c +++ b/components/esp_event/esp_event.c @@ -38,6 +38,7 @@ static const char* TAG = "event"; static const char* esp_event_any_base = "any"; +static const char* esp_event_handler_cleanup = "cleanup"; #ifdef CONFIG_ESP_EVENT_LOOP_PROFILING static SLIST_HEAD(esp_event_loop_instance_list_t, esp_event_loop_instance) s_event_loops = @@ -142,51 +143,8 @@ static void handler_execute(esp_event_loop_instance_t* loop, esp_event_handler_n #ifdef CONFIG_ESP_EVENT_LOOP_PROFILING diff = esp_timer_get_time() - start; - xSemaphoreTake(loop->profiling_mutex, portMAX_DELAY); - - // At this point handler may be already unregistered. - // This happens in "handler instance can unregister itself" test case. - // To prevent memory corruption error it's necessary to check if pointer is still valid. - esp_event_loop_node_t* loop_node; - SLIST_FOREACH(loop_node, &(loop->loop_nodes), next) { - // try to find the handler amongst the list of handlers that are registered on the - // loop level (i.e., registered to be triggered for all events) - esp_event_handler_node_t* handler_node; - SLIST_FOREACH(handler_node, &(loop_node->handlers), next) { - if(handler_node == handler) { - handler->invoked++; - handler->time += diff; - } - } - - // try to find the handler amongst the list of handlers that are registered on the - // base level (i.e., registered to be triggered for all events of a specific event base) - esp_event_base_node_t* base_node; - SLIST_FOREACH(base_node, &(loop_node->base_nodes), next) { - esp_event_handler_node_t* base_handler_node; - SLIST_FOREACH(base_handler_node, &(base_node->handlers), next) { - if (base_handler_node == handler) { - handler->invoked++; - handler->time += diff; - } - } - - // try to find the handler amongst the list of handlers that are registered on the - // ID level (i.e., registered to be triggered for a specific event ID of a specific event base) - esp_event_id_node_t* id_node; - SLIST_FOREACH(id_node, &(base_node->id_nodes), next) { - esp_event_handler_node_t* id_handler_node; - SLIST_FOREACH(id_handler_node, &(id_node->handlers), next) { - if (id_handler_node == handler) { - handler->invoked++; - handler->time += diff; - } - } - } - } - } - - xSemaphoreGive(loop->profiling_mutex); + handler->invoked++; + handler->time += diff; #endif } @@ -391,8 +349,8 @@ static esp_err_t base_node_remove_handler(esp_event_base_node_t* base_node, int3 if (SLIST_EMPTY(&(it->handlers))) { SLIST_REMOVE(&(base_node->id_nodes), it, esp_event_id_node, next); free(it); - return ESP_OK; } + return ESP_OK; } } } @@ -416,8 +374,8 @@ static esp_err_t loop_node_remove_handler(esp_event_loop_node_t* loop_node, esp_ if (SLIST_EMPTY(&(it->handlers)) && SLIST_EMPTY(&(it->id_nodes))) { SLIST_REMOVE(&(loop_node->base_nodes), it, esp_event_base_node, next); free(it); - return ESP_OK; } + return ESP_OK; } } } @@ -426,6 +384,23 @@ static esp_err_t loop_node_remove_handler(esp_event_loop_node_t* loop_node, esp_ return ESP_ERR_NOT_FOUND; } +static esp_err_t loop_remove_handler(esp_event_remove_handler_context_t* ctx) +{ + esp_event_loop_node_t *it, *temp; + SLIST_FOREACH_SAFE(it, &(ctx->loop->loop_nodes), next, temp) { + esp_err_t res = loop_node_remove_handler(it, ctx->event_base, ctx->event_id, ctx->handler_ctx, ctx->legacy); + + if (res == ESP_OK) { + if (SLIST_EMPTY(&(it->base_nodes)) && SLIST_EMPTY(&(it->handlers))) { + SLIST_REMOVE(&(ctx->loop->loop_nodes), it, esp_event_loop_node, next); + free(it); + } + return ESP_OK; + } + } + return ESP_ERR_NOT_FOUND; +} + static void handler_instances_remove_all(esp_event_handler_nodes_t* handlers) { esp_event_handler_node_t *it, *temp; @@ -474,6 +449,104 @@ static void inline __attribute__((always_inline)) post_instance_delete(esp_event memset(post, 0, sizeof(*post)); } +static esp_err_t find_and_unregister_handler(esp_event_remove_handler_context_t* ctx) +{ + esp_event_handler_node_t *handler_to_unregister = NULL; + esp_event_handler_node_t *handler; + esp_event_loop_node_t *loop_node; + esp_event_base_node_t *base_node; + esp_event_id_node_t *id_node; + + SLIST_FOREACH(loop_node, &(ctx->loop->loop_nodes), next) { + // Execute loop level handlers + SLIST_FOREACH(handler, &(loop_node->handlers), next) { + if (ctx->legacy) { + if (handler->handler_ctx->handler == ctx->handler_ctx->handler) { + handler_to_unregister = handler; + break; + } + } else { + if (handler->handler_ctx == ctx->handler_ctx) { + handler_to_unregister = handler; + break; + } + } + } + + if (handler_to_unregister != NULL) { + break; + } + + SLIST_FOREACH(base_node, &(loop_node->base_nodes), next) { + if (base_node->base == ctx->event_base) { + // Execute base level handlers + SLIST_FOREACH(handler, &(base_node->handlers), next) { + if (ctx->legacy) { + if (handler->handler_ctx->handler == ctx->handler_ctx->handler) { + handler_to_unregister = handler; + break; + } + } else { + if (handler->handler_ctx == ctx->handler_ctx) { + handler_to_unregister = handler; + break; + } + } + } + + if (handler_to_unregister != NULL) { + break; + } + + SLIST_FOREACH(id_node, &(base_node->id_nodes), next) { + if (id_node->id == ctx->event_id) { + // Execute id level handlers + SLIST_FOREACH(handler, &(id_node->handlers), next) { + if (ctx->legacy) { + if (handler->handler_ctx->handler == ctx->handler_ctx->handler) { + handler_to_unregister = handler; + break; + } + } else { + if (handler->handler_ctx == ctx->handler_ctx) { + handler_to_unregister = handler; + break; + } + } + } + } + } + } + } + } + + if (handler_to_unregister == NULL) { + /* handler not found in the lists, return */ + return ESP_ERR_NOT_FOUND; + } + + if (handler_to_unregister->unregistered) { + /* the handler was found in a list but has already be marked + * as unregistered. It means an event was already created to + * remove from the list. return OK but do nothing */ + return ESP_OK; + } + /* handler found in the lists and not already marked as unregistered. Mark it as unregistered + * and post an event to remove it from the lists */ + handler_to_unregister->unregistered = true; + if (ctx->legacy) { + /* in case of legacy code, we have to copy the handler_ctx content since it was created in the calling function */ + esp_event_handler_instance_context_t *handler_ctx_copy = calloc(1, sizeof(esp_event_handler_instance_context_t)); + if (!handler_ctx_copy) { + return ESP_ERR_NO_MEM; + } + handler_ctx_copy->arg = ctx->handler_ctx->arg; + handler_ctx_copy->handler = ctx->handler_ctx->handler; + ctx->handler_ctx = handler_ctx_copy; + } + return esp_event_post_to(ctx->loop, esp_event_handler_cleanup, 0, ctx, sizeof(esp_event_remove_handler_context_t), portMAX_DELAY); +} + /* ---------------------------- Public API --------------------------------- */ esp_err_t esp_event_loop_create(const esp_event_loop_args_t* event_loop_args, esp_event_loop_handle_t* event_loop) @@ -509,14 +582,6 @@ esp_err_t esp_event_loop_create(const esp_event_loop_args_t* event_loop_args, es goto on_err; } -#ifdef CONFIG_ESP_EVENT_LOOP_PROFILING - loop->profiling_mutex = xSemaphoreCreateMutex(); - if (loop->profiling_mutex == NULL) { - ESP_LOGE(TAG, "create event loop profiling mutex failed"); - goto on_err; - } -#endif - SLIST_INIT(&(loop->loop_nodes)); // Create the loop task if requested @@ -562,12 +627,6 @@ on_err: vSemaphoreDelete(loop->mutex); } -#ifdef CONFIG_ESP_EVENT_LOOP_PROFILING - if (loop->profiling_mutex != NULL) { - vSemaphoreDelete(loop->profiling_mutex); - } -#endif - free(loop); return err; @@ -594,10 +653,25 @@ esp_err_t esp_event_loop_run(esp_event_loop_handle_t event_loop, TickType_t tick int64_t remaining_ticks = ticks_to_run; #endif - while(xQueueReceive(loop->queue, &post, ticks_to_run) == pdTRUE) { + while (xQueueReceive(loop->queue, &post, remaining_ticks) == pdTRUE) { // The event has already been unqueued, so ensure it gets executed. xSemaphoreTakeRecursive(loop->mutex, portMAX_DELAY); + // check if the event retrieve from the queue is the internal event that is + // triggered when a handler needs to be removed.. + if (post.base == esp_event_handler_cleanup) { + assert(post.data.ptr != NULL); + esp_event_remove_handler_context_t* ctx = (esp_event_remove_handler_context_t*)post.data.ptr; + loop_remove_handler(ctx); + + // if the handler unregistration request came from legacy code, + // we have to free handler_ctx pointer since it points to memory + // allocated by esp_event_handler_unregister_with_internal + if (ctx->legacy) { + free(ctx->handler_ctx); + } + } + loop->running_task = xTaskGetCurrentTaskHandle(); bool exec = false; @@ -610,24 +684,30 @@ esp_err_t esp_event_loop_run(esp_event_loop_handle_t event_loop, TickType_t tick SLIST_FOREACH_SAFE(loop_node, &(loop->loop_nodes), next, temp_node) { // Execute loop level handlers SLIST_FOREACH_SAFE(handler, &(loop_node->handlers), next, temp_handler) { - handler_execute(loop, handler, post); - exec |= true; + if (!handler->unregistered) { + handler_execute(loop, handler, post); + exec |= true; + } } SLIST_FOREACH_SAFE(base_node, &(loop_node->base_nodes), next, temp_base) { if (base_node->base == post.base) { // Execute base level handlers SLIST_FOREACH_SAFE(handler, &(base_node->handlers), next, temp_handler) { - handler_execute(loop, handler, post); - exec |= true; + if (!handler->unregistered) { + handler_execute(loop, handler, post); + exec |= true; + } } SLIST_FOREACH_SAFE(id_node, &(base_node->id_nodes), next, temp_id_node) { if (id_node->id == post.id) { // Execute id level handlers SLIST_FOREACH_SAFE(handler, &(id_node->handlers), next, temp_handler) { - handler_execute(loop, handler, post); - exec |= true; + if (!handler->unregistered) { + handler_execute(loop, handler, post); + exec |= true; + } } // Skip to next base node break; @@ -674,14 +754,10 @@ esp_err_t esp_event_loop_delete(esp_event_loop_handle_t event_loop) esp_event_loop_instance_t* loop = (esp_event_loop_instance_t*) event_loop; SemaphoreHandle_t loop_mutex = loop->mutex; -#ifdef CONFIG_ESP_EVENT_LOOP_PROFILING - SemaphoreHandle_t loop_profiling_mutex = loop->profiling_mutex; -#endif xSemaphoreTakeRecursive(loop->mutex, portMAX_DELAY); #ifdef CONFIG_ESP_EVENT_LOOP_PROFILING - xSemaphoreTake(loop->profiling_mutex, portMAX_DELAY); portENTER_CRITICAL(&s_event_loops_spinlock); SLIST_REMOVE(&s_event_loops, loop, esp_event_loop_instance, next); portEXIT_CRITICAL(&s_event_loops_spinlock); @@ -711,10 +787,6 @@ esp_err_t esp_event_loop_delete(esp_event_loop_handle_t event_loop) free(loop); // Free loop mutex before deleting xSemaphoreGiveRecursive(loop_mutex); -#ifdef CONFIG_ESP_EVENT_LOOP_PROFILING - xSemaphoreGive(loop_profiling_mutex); - vSemaphoreDelete(loop_profiling_mutex); -#endif vSemaphoreDelete(loop_mutex); return ESP_OK; @@ -814,24 +886,21 @@ esp_err_t esp_event_handler_unregister_with_internal(esp_event_loop_handle_t eve } esp_event_loop_instance_t* loop = (esp_event_loop_instance_t*) event_loop; + esp_event_remove_handler_context_t remove_handler_ctx = {loop, event_base, event_id, handler_ctx, legacy}; - xSemaphoreTakeRecursive(loop->mutex, portMAX_DELAY); - - esp_event_loop_node_t *it, *temp; - - SLIST_FOREACH_SAFE(it, &(loop->loop_nodes), next, temp) { - esp_err_t res = loop_node_remove_handler(it, event_base, event_id, handler_ctx, legacy); - - if (res == ESP_OK && SLIST_EMPTY(&(it->base_nodes)) && SLIST_EMPTY(&(it->handlers))) { - SLIST_REMOVE(&(loop->loop_nodes), it, esp_event_loop_node, next); - free(it); - break; - } + /* remove the handler if the mutex is taken successfully. + * otherwise it will be removed from the list later */ + esp_err_t res = ESP_FAIL; + if (xSemaphoreTake(loop->mutex, 0) == pdTRUE) { + res = loop_remove_handler(&remove_handler_ctx); + xSemaphoreGive(loop->mutex); + } else { + xSemaphoreTakeRecursive(loop->mutex, portMAX_DELAY); + res = find_and_unregister_handler(&remove_handler_ctx); + xSemaphoreGiveRecursive(loop->mutex); } - xSemaphoreGiveRecursive(loop->mutex); - - return ESP_OK; + return res; } esp_err_t esp_event_handler_unregister_with(esp_event_loop_handle_t event_loop, esp_event_base_t event_base, diff --git a/components/esp_event/private_include/esp_event_internal.h b/components/esp_event/private_include/esp_event_internal.h index 9c05cd665a..f7b1612996 100644 --- a/components/esp_event/private_include/esp_event_internal.h +++ b/components/esp_event/private_include/esp_event_internal.h @@ -31,6 +31,7 @@ typedef struct esp_event_handler_node { int64_t time; /**< total runtime of this handler across all calls */ #endif SLIST_ENTRY(esp_event_handler_node) next; /**< next event handler in the list */ + bool unregistered; } esp_event_handler_node_t; typedef SLIST_HEAD(esp_event_handler_instances, esp_event_handler_node) esp_event_handler_nodes_t; @@ -78,11 +79,18 @@ typedef struct esp_event_loop_instance { #ifdef CONFIG_ESP_EVENT_LOOP_PROFILING atomic_uint_least32_t events_received; /**< number of events successfully posted to the loop */ atomic_uint_least32_t events_dropped; /**< number of events dropped due to queue being full */ - SemaphoreHandle_t profiling_mutex; /**< mutex used for profiliing */ SLIST_ENTRY(esp_event_loop_instance) next; /**< next event loop in the list */ #endif } esp_event_loop_instance_t; +typedef struct esp_event_remove_handler_context_t { + esp_event_loop_instance_t* loop; /**< Instance of the event loop from which the handler has to be removed */ + esp_event_base_t event_base; /**< The event base identification of the handler that has to be removed */ + int32_t event_id; /**< The event identification value of the handler that has to be removed */ + esp_event_handler_instance_context_t* handler_ctx; /**< The handler context of the handler that has to be removed */ + bool legacy; /**< Set to true when the handler unregistration request was made from legacy code */ +} esp_event_remove_handler_context_t; + #if CONFIG_ESP_EVENT_POST_FROM_ISR typedef union esp_event_post_data { uint32_t val; diff --git a/components/esp_event/test_apps/main/test_event_common.cpp b/components/esp_event/test_apps/main/test_event_common.cpp index 09e2bc5c13..569b97902e 100644 --- a/components/esp_event/test_apps/main/test_event_common.cpp +++ b/components/esp_event/test_apps/main/test_event_common.cpp @@ -1575,19 +1575,27 @@ TEST_CASE("default event loop: registering event handler instance without instan #if CONFIG_ESP_EVENT_LOOP_PROFILING static void handler_all(void* arg, esp_event_base_t event_base, int32_t event_id, void* data) { - printf("Event received: base=%s, id=%" PRId32 ", data=%p\n", event_base, event_id, data); + printf("base=%s, id=%" PRId32 ", data=%p\n", event_base, event_id, data); } static void handler_base(void* arg, esp_event_base_t event_base, int32_t event_id, void* data) { - printf("Event received: base=%s, id=%" PRId32 ", data=%p\n", event_base, event_id, data); + printf("base=%s, id=%" PRId32 ", data=%p\n", event_base, event_id, data); } static void handler_id(void* arg, esp_event_base_t event_base, int32_t event_id, void* data) { - printf("Event received: base=%s, id=%" PRId32 ", data=%p\n", event_base, event_id, data); + printf("base=%s, id=%" PRId32 ", data=%p\n", event_base, event_id, data); } +/* This test executes the following steps: + * 1) register handler_id for a event id A and event base B, + * 2) register handler_base for a given event base, + * 3) register handler_all for on the loop level, + * 4) post an event (A, B), + * 5) call esp_event_dump and make sure by parsing the string IN PYTEST ENVIRONMENT that + * all handlers profiling data is printed + * 6) unregister the handlers successfully */ TEST_CASE("profiling reports valid values", "[event][default]") { TEST_ESP_OK(esp_event_loop_create_default()); @@ -1616,4 +1624,114 @@ TEST_CASE("profiling reports valid values", "[event][default]") /* delete loop */ TEST_ESP_OK(esp_event_loop_delete_default()); } + +static void handler_id2(void* arg, esp_event_base_t event_base, int32_t event_id, void* data) +{ + printf("event received: base=%s, id=%" PRId32 ", data=%p\n", event_base, event_id, data); + + /* self unregistering handler */ + TEST_ESP_OK(esp_event_handler_unregister(s_test_base1, TEST_EVENT_BASE1_EV1, handler_id2)); + + /* register a new handler on id level */ + TEST_ESP_OK(esp_event_handler_register(s_test_base1, TEST_EVENT_BASE1_EV1, handler_id, NULL)); +} + +/* This test executes the following steps: + * 1) register handler_id2 for a given event id and event base, + * 2) post an event to trigger the handler_id2, + * 3) unregister the handler_id2 during the execution of the handler_id2 and register + * handler_id instead. + * 4) call esp_event_dump and make sure by parsing the string IN PYTEST ENVIRONMENT that + * 1 handler profiling data is printed + * 5) unregister the handler_id successfully */ +TEST_CASE("esp_event_dump does not show self unregistered handler", "[event][default]") +{ + TEST_ESP_OK(esp_event_loop_create_default()); + + /* register handler for event base 1 and event id 1 */ + TEST_ESP_OK(esp_event_handler_register(s_test_base1, TEST_EVENT_BASE1_EV1, handler_id2, NULL)); + + /* post an event on event base 1, event id 1 */ + TEST_ESP_OK(esp_event_post(s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, pdMS_TO_TICKS(1000))); + + /* post an event 1 from base 1 and check the dump. + * - 1 handler invoked 0 times, exec time is 0 us */ + esp_event_dump(stdout); + + /* unregister handler id */ + TEST_ESP_OK(esp_event_handler_unregister(s_test_base1, TEST_EVENT_BASE1_EV1, handler_id)); + + /* delete loop */ + TEST_ESP_OK(esp_event_loop_delete_default()); +} + +static SemaphoreHandle_t s_event_mutex; +static StaticSemaphore_t s_event_mutex_buf; +static size_t s_handler_triggered = 0; + +static void self_unregistering_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* data) +{ + xSemaphoreTake(s_event_mutex, portMAX_DELAY); + + /* self unregistering handler */ + TEST_ESP_OK(esp_event_handler_unregister(s_test_base1, TEST_EVENT_BASE1_EV1, self_unregistering_handler)); + + s_handler_triggered++; + + xSemaphoreGive(s_event_mutex); +} + +/* This test makes sure that once a handler is unregistered, it is never called again. + * This test follows the update in esp event unregistration process where a handler can + * be marked as unregistered but not removed directly from the list it belongs to. + * The test creates a handler triggered by a specific combination of event base / event id + * and generates 2 consecutive events that should trigger the handler. The handler unregisters + * itself. Make sure that the second event does not trigger the handler again. + * + * Both event posts need to be queued before the unregistration happens, to make sure that event if + * the handler is still present in the handlers lists when the second event triggers, it will not + * be called. To make sure of that, the execution of the handler is blocked by a mutex released from + * test after the 2 events are posted. */ +TEST_CASE("self unregistered handlers are never called again after they return", "[event][default]") +{ + s_event_mutex = xSemaphoreCreateMutexStatic(&s_event_mutex_buf); + TEST_ASSERT_NOT_NULL(s_event_mutex); + + esp_err_t ret = esp_event_loop_create_default(); + printf("esp_event_loop_create_default %d\n", ret); + TEST_ESP_OK(ret); + + /* register handler for event base 1 and event id 1 */ + ret = esp_event_handler_register(s_test_base1, TEST_EVENT_BASE1_EV1, self_unregistering_handler, NULL); + printf("esp_event_handler_register %d\n", ret); + TEST_ESP_OK(ret); + + /* take the mutex to block the execution of the self_unregistering_handler */ + xSemaphoreTake(s_event_mutex, portMAX_DELAY); + + /* post 2 times the event on event base 1, event id 1 */ + ret = esp_event_post(s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, pdMS_TO_TICKS(1000)); + printf("esp_event_post %d\n", ret); + TEST_ESP_OK(ret); + ret = esp_event_post(s_test_base1, TEST_EVENT_BASE1_EV1, NULL, 0, pdMS_TO_TICKS(1000)); + printf("esp_event_post %d\n", ret); + TEST_ESP_OK(ret); + + /* release the mutex to execute the self_unregistering_handler */ + xSemaphoreGive(s_event_mutex); + + /* make sure the handler was called only once */ + TEST_ASSERT(s_handler_triggered == 1); + + /* delete mutex */ + vSemaphoreDelete(s_event_mutex); + + /* reset the static variable in case the test gets called once more */ + s_handler_triggered = 0; + + /* delete loop */ + ret = esp_event_loop_delete_default(); + printf("esp_event_loop_delete_default %d\n", ret); + TEST_ESP_OK(ret); +} #endif // CONFIG_ESP_EVENT_LOOP_PROFILING diff --git a/components/esp_event/test_apps/pytest_esp_event.py b/components/esp_event/test_apps/pytest_esp_event.py index c5d87ecad3..71ee1bfe20 100644 --- a/components/esp_event/test_apps/pytest_esp_event.py +++ b/components/esp_event/test_apps/pytest_esp_event.py @@ -54,3 +54,16 @@ def test_esp_event_profiling(dut: Dut) -> None: matches = dut.expect(r'HANDLER .+ inv:[1-9][0-9]{0,} time:[1-9][0-9]{0,} us', timeout=2) matches_arr = matches.group().split(b'\r\n') assert (len(matches_arr) == 3) + dut.expect('1 Tests 0 Failures 0 Ignored', timeout=120) + dut.expect_exact("Enter next test, or 'enter' to see menu") + + dut.write('"esp_event_dump does not show self unregistered handler"') + # look for 1 handlers never invoked + matches = dut.expect(r'HANDLER .+ inv:0 time:0 us', timeout=2) + matches_arr = matches.group().split(b'\r\n') + assert (len(matches_arr) == 1) + dut.expect('1 Tests 0 Failures 0 Ignored', timeout=120) + dut.expect_exact("Enter next test, or 'enter' to see menu") + + dut.write('"self unregistered handlers are never called again after they return"') + dut.expect('1 Tests 0 Failures 0 Ignored', timeout=120)