1 /*
2 * Test backported deletion behavior by creating tasks of various affinities and
3 * check if the task memory is freed immediately under the correct conditions.
4 *
5 * The behavior of vTaskDelete() results in the immediate freeing of task memory
6 * and the immediate execution of deletion callbacks for tasks which are not
7 * running, provided they are not pinned to the other core (due to FPU cleanup
8 * requirements).
9 *
10 * If the condition is not met, freeing of task memory and execution of
11 * deletion callbacks will still be carried out by the Idle Task.
12 */
13 #include <stdio.h>
14
15 #include "freertos/FreeRTOS.h"
16 #include "freertos/task.h"
17 #include "freertos/semphr.h"
18 #include "esp_heap_caps.h"
19
20 #include "unity.h"
21 #include "test_utils.h"
22
23 #include "esp_rom_sys.h"
24
25 #define NO_OF_TSKS 3
26 #define DELAY_TICKS 2
27 /* Caps of all memory which is allocated from when a task is created */
28 #define HEAP_CAPS (portTcbMemoryCaps | portStackMemoryCaps)
29
30 #define DELAY_US_ITERATIONS 1000
31
32
tsk_self_del(void * param)33 static void tsk_self_del(void *param)
34 {
35 vTaskDelete(NULL); //Deleting self means deleting currently running task
36 }
37
tsk_extern_del(void * param)38 static void tsk_extern_del(void *param)
39 {
40 vTaskDelay(portMAX_DELAY); //Await external deletion
41 }
42
tsk_self_del_us_delay(void * param)43 static void tsk_self_del_us_delay(void *param)
44 {
45 uint32_t delay = (uint32_t)param;
46 esp_rom_delay_us(delay);
47 vTaskDelete(NULL);
48 }
49
50 TEST_CASE("FreeRTOS Delete Tasks", "[freertos]")
51 {
52 /* -------------- Test vTaskDelete() on currently running tasks ----------------*/
53 uint32_t before_count = uxTaskGetNumberOfTasks();
54 uint32_t before_heap = heap_caps_get_free_size(HEAP_CAPS);
55 for(int i = 0; i < portNUM_PROCESSORS; i++){
56 for(int j = 0; j < NO_OF_TSKS; j++){
57 TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(tsk_self_del, "tsk_self", 1024, NULL, configMAX_PRIORITIES - 1, NULL, i));
58 }
59 }
60 vTaskDelay(DELAY_TICKS); //Minimal delay to see if Idle task cleans up all tasks awaiting deletion in a single tick
61 TEST_ASSERT_EQUAL(before_count, uxTaskGetNumberOfTasks());
62 TEST_ASSERT_EQUAL(before_heap, heap_caps_get_free_size(HEAP_CAPS));
63
64 /* ------------- Test vTaskDelete() on not currently running tasks ------------ */
65 TaskHandle_t handles[NO_OF_TSKS];
66 before_heap = heap_caps_get_free_size(HEAP_CAPS);
67 //Create task pinned to the same core that will not run during task deletion
68 for(int j = 0 ; j < NO_OF_TSKS; j++){
69 TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(tsk_extern_del, "tsk_extern", 4096, NULL, configMAX_PRIORITIES - 1, &handles[j], xPortGetCoreID()));
70 }
71 TEST_ASSERT_NOT_EQUAL(before_heap, heap_caps_get_free_size(HEAP_CAPS)); //Check tasks have been created
72 //Delete the tasks, memory should be freed immediately
73 for(int j = 0; j < NO_OF_TSKS; j++){
74 vTaskDelete(handles[j]);
75 }
76 TEST_ASSERT_EQUAL(before_heap, heap_caps_get_free_size(HEAP_CAPS));
77
78 /* Test self deleting no affinity task is not removed by idle task of other core before context switch */
79 for(int i = 0; i < DELAY_US_ITERATIONS; i+= 10){
80 vTaskDelay(1); //Sync to next tick interrupt
81 xTaskCreatePinnedToCore(tsk_self_del_us_delay, "delay", 1024, (void *)i, UNITY_FREERTOS_PRIORITY - 1, NULL, tskNO_AFFINITY);
82 esp_rom_delay_us(10); //Busy wait to ensure no affinity task runs on opposite core
83 }
84
85 }
86
87
88 typedef struct {
89 SemaphoreHandle_t sem;
90 volatile bool deleted; // Check the deleted task doesn't keep running after being deleted
91 } tsk_blocks_param_t;
92
93 /* Task blocks as often as possible
94 (two or more of these can share the same semaphore and "juggle" it around)
95 */
tsk_blocks_frequently(void * param)96 static void tsk_blocks_frequently(void *param)
97 {
98 tsk_blocks_param_t *p = (tsk_blocks_param_t *)param;
99 SemaphoreHandle_t sem = p->sem;
100 srand(xTaskGetTickCount() ^ (int)xTaskGetCurrentTaskHandle());
101 while (1) {
102 assert(!p->deleted);
103 esp_rom_delay_us(rand() % 10);
104 assert(!p->deleted);
105 xSemaphoreTake(sem, portMAX_DELAY);
106 assert(!p->deleted);
107 esp_rom_delay_us(rand() % 10);
108 assert(!p->deleted);
109 xSemaphoreGive(sem);
110 }
111 }
112
113 TEST_CASE("FreeRTOS Delete Blocked Tasks", "[freertos]")
114 {
115 TaskHandle_t blocking_tasks[portNUM_PROCESSORS + 1]; // one per CPU, plus one unpinned task
116 tsk_blocks_param_t params[portNUM_PROCESSORS + 1] = { 0 };
117
118 unsigned before = heap_caps_get_free_size(MALLOC_CAP_8BIT);
119 printf("Free memory at start %u\n", before);
120
121 /* Any bugs will depend on relative timing of destroying the tasks, so create & delete many times.
122
123 Stop early if it looks like some resources have not been properly cleaned up.
124
125 (1000 iterations takes about 9 seconds on ESP32 dual core)
126 */
127 for(unsigned iter = 0; iter < 1000; iter++) {
128 // Create everything
129 SemaphoreHandle_t sem = xSemaphoreCreateMutex();
130 for(unsigned i = 0; i < portNUM_PROCESSORS + 1; i++) {
131 params[i].deleted = false;
132 params[i].sem = sem;
133
134 TEST_ASSERT_EQUAL(pdTRUE,
135 xTaskCreatePinnedToCore(tsk_blocks_frequently, "tsk_block", 4096, ¶ms[i],
136 UNITY_FREERTOS_PRIORITY - 1, &blocking_tasks[i],
137 i < portNUM_PROCESSORS ? i : tskNO_AFFINITY));
138 }
139
140 vTaskDelay(5); // Let the tasks juggle the mutex for a bit
141
142 for(unsigned i = 0; i < portNUM_PROCESSORS + 1; i++) {
143 vTaskDelete(blocking_tasks[i]);
144 params[i].deleted = true;
145 }
146 vTaskDelay(4); // Yield to the idle task for cleanup
147
148 vSemaphoreDelete(sem);
149
150 // Check we haven't leaked resources yet
151 TEST_ASSERT_GREATER_OR_EQUAL(before - 256, heap_caps_get_free_size(MALLOC_CAP_8BIT));
152 }
153 }
154