Skip to content

Commit e1ab04e

Browse files
committed
esp32/mpthreadport: Fix double delete of tasks on soft reset.
Python threads (created via the `_thread` module) are backed by a FreeRTOS task. Managing the deletion of the task can be tricky, and there are currently some bugs with this in the esp32 port. The actual crash seen was in FreeRTOS' `uxListRemove()`, and that's because of two calls to `vTaskDelete()` for the same task: one in `freertos_entry()` when the task ran to completion, and the other in `mp_thread_deinit()`. The latter tried to delete the task a second time because it was still in the linked list, because `vTaskPreDeletionHook()` had not yet been called. And the reason `vTaskPreDeletionHook()` was yet to be called is because the FreeRTOS idle task was starved. This commit fixes that. There are three things done by this commit: - remove the `vTaskPreDeletionHook`, it's not needed anymore because task stack memory is allocated by the IDF, not on the MicroPython heap - when a task finishes it now removes itself from the linked list, just before it deletes itself - on soft reset, all tasks are deleted and removed from the linked list in one swoop (while the mutex is held) Signed-off-by: Damien George <damien@micropython.org>
1 parent 9d56518 commit e1ab04e

File tree

2 files changed

+37
-60
lines changed

2 files changed

+37
-60
lines changed

ports/esp32/boards/sdkconfig.base

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n
5050
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=2
5151
CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y
5252
CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=n
53-
CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK=y
5453

5554
# UDP
5655
CONFIG_LWIP_PPP_SUPPORT=y

ports/esp32/mpthreadport.c

Lines changed: 37 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,16 @@
4141
#define MP_THREAD_DEFAULT_STACK_SIZE (MP_THREAD_MIN_STACK_SIZE + MICROPY_STACK_CHECK_MARGIN)
4242
#define MP_THREAD_PRIORITY (ESP_TASK_PRIO_MIN + 1)
4343

44+
typedef enum {
45+
MP_THREAD_RUN_STATE_NEW,
46+
MP_THREAD_RUN_STATE_RUNNING,
47+
MP_THREAD_RUN_STATE_FINISHED,
48+
} mp_thread_run_state_t;
49+
4450
// this structure forms a linked list, one node per active thread
4551
typedef struct _mp_thread_t {
4652
TaskHandle_t id; // system id of thread
47-
int ready; // whether the thread is ready and running
53+
mp_thread_run_state_t run_state; // current run state of the thread
4854
void *arg; // thread Python args, a GC root pointer
4955
void *stack; // pointer to the stack
5056
size_t stack_len; // number of words in the stack
@@ -60,18 +66,16 @@ void mp_thread_init(void *stack, uint32_t stack_len) {
6066
mp_thread_set_state(&mp_state_ctx.thread);
6167
// create the first entry in the linked list of all threads
6268
thread_entry0.id = xTaskGetCurrentTaskHandle();
63-
thread_entry0.ready = 1;
69+
thread_entry0.run_state = MP_THREAD_RUN_STATE_RUNNING;
6470
thread_entry0.arg = NULL;
6571
thread_entry0.stack = stack;
6672
thread_entry0.stack_len = stack_len;
6773
thread_entry0.next = NULL;
74+
thread = &thread_entry0;
6875
mp_thread_mutex_init(&thread_mutex);
6976

7077
// memory barrier to ensure above data is committed
7178
__sync_synchronize();
72-
73-
// vTaskPreDeletionHook needs the thread ready after thread_mutex is ready
74-
thread = &thread_entry0;
7579
}
7680

7781
void mp_thread_gc_others(void) {
@@ -82,7 +86,7 @@ void mp_thread_gc_others(void) {
8286
if (th->id == xTaskGetCurrentTaskHandle()) {
8387
continue;
8488
}
85-
if (!th->ready) {
89+
if (th->run_state != MP_THREAD_RUN_STATE_RUNNING) {
8690
continue;
8791
}
8892
gc_collect_root(th->stack, th->stack_len);
@@ -106,7 +110,7 @@ void mp_thread_start(void) {
106110
mp_thread_mutex_lock(&thread_mutex, 1);
107111
for (mp_thread_t *th = thread; th != NULL; th = th->next) {
108112
if (th->id == xTaskGetCurrentTaskHandle()) {
109-
th->ready = 1;
113+
th->run_state = MP_THREAD_RUN_STATE_RUNNING;
110114
break;
111115
}
112116
}
@@ -116,12 +120,22 @@ void mp_thread_start(void) {
116120
static void *(*ext_thread_entry)(void *) = NULL;
117121

118122
static void freertos_entry(void *arg) {
123+
// Run the Python code.
119124
if (ext_thread_entry) {
120125
ext_thread_entry(arg);
121126
}
122-
vTaskDelete(NULL);
123-
for (;;) {;
127+
128+
// Remove the thread from the linked-list of active threads.
129+
mp_thread_mutex_lock(&thread_mutex, 1);
130+
for (mp_thread_t **th = &thread; *th != NULL; th = &(*th)->next) {
131+
if ((*th)->id == xTaskGetCurrentTaskHandle()) {
132+
*th = (*th)->next;
133+
}
124134
}
135+
mp_thread_mutex_unlock(&thread_mutex);
136+
137+
// Delete this FreeRTOS task (this call to vTaskDelete will not return).
138+
vTaskDelete(NULL);
125139
}
126140

127141
mp_uint_t mp_thread_create_ex(void *(*entry)(void *), void *arg, size_t *stack_size, int priority, char *name) {
@@ -147,7 +161,7 @@ mp_uint_t mp_thread_create_ex(void *(*entry)(void *), void *arg, size_t *stack_s
147161
}
148162

149163
// add thread to linked list of all threads
150-
th->ready = 0;
164+
th->run_state = MP_THREAD_RUN_STATE_NEW;
151165
th->arg = arg;
152166
th->stack = pxTaskGetStackStart(th->id);
153167
th->stack_len = *stack_size / sizeof(uintptr_t);
@@ -167,33 +181,7 @@ void mp_thread_finish(void) {
167181
mp_thread_mutex_lock(&thread_mutex, 1);
168182
for (mp_thread_t *th = thread; th != NULL; th = th->next) {
169183
if (th->id == xTaskGetCurrentTaskHandle()) {
170-
th->ready = 0;
171-
break;
172-
}
173-
}
174-
mp_thread_mutex_unlock(&thread_mutex);
175-
}
176-
177-
// This is called either from vTaskDelete() or from the FreeRTOS idle task, so
178-
// may not be within Python context. Therefore MP_STATE_THREAD may not be valid
179-
// and it does not have the GIL.
180-
void vTaskPreDeletionHook(void *tcb) {
181-
if (thread == NULL) {
182-
// threading not yet initialised
183-
return;
184-
}
185-
mp_thread_t *prev = NULL;
186-
mp_thread_mutex_lock(&thread_mutex, 1);
187-
for (mp_thread_t *th = thread; th != NULL; prev = th, th = th->next) {
188-
// unlink the node from the list
189-
if ((void *)th->id == tcb) {
190-
if (prev != NULL) {
191-
prev->next = th->next;
192-
} else {
193-
// move the start pointer
194-
thread = th->next;
195-
}
196-
// The "th" memory will eventually be reclaimed by the GC.
184+
th->run_state = MP_THREAD_RUN_STATE_FINISHED;
197185
break;
198186
}
199187
}
@@ -221,32 +209,22 @@ void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) {
221209
}
222210

223211
void mp_thread_deinit(void) {
224-
for (;;) {
225-
// Find a task to delete
226-
TaskHandle_t id = NULL;
227-
mp_thread_mutex_lock(&thread_mutex, 1);
228-
for (mp_thread_t *th = thread; th != NULL; th = th->next) {
229-
// Don't delete the current task
230-
if (th->id != xTaskGetCurrentTaskHandle()) {
231-
id = th->id;
232-
break;
233-
}
234-
}
235-
mp_thread_mutex_unlock(&thread_mutex);
212+
// The current task should be thread_entry0 and should be the last in the linked list.
213+
assert(thread_entry0.id == xTaskGetCurrentTaskHandle());
214+
assert(thread_entry0.next == NULL);
236215

237-
if (id == NULL) {
238-
// No tasks left to delete
239-
break;
240-
} else {
241-
// Call FreeRTOS to delete the task (it will call vTaskPreDeletionHook)
242-
vTaskDelete(id);
216+
// Delete all tasks except the main one.
217+
mp_thread_mutex_lock(&thread_mutex, 1);
218+
for (mp_thread_t *th = thread; th != NULL; th = th->next) {
219+
if (th != &thread_entry0) {
220+
vTaskDelete(th->id);
243221
}
244222
}
245-
}
246-
247-
#else
223+
thread = &thread_entry0;
224+
mp_thread_mutex_unlock(&thread_mutex);
248225

249-
void vTaskPreDeletionHook(void *tcb) {
226+
// Give the idle task a chance to run, to clean up any deleted tasks.
227+
vTaskDelay(1);
250228
}
251229

252230
#endif // MICROPY_PY_THREAD

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy