/*************************************************************************** * Copyright (C) 2026 Eclipse ThreadX contributors * * This program and the accompanying materials are made available under the * terms of the MIT License which is available at * https://opensource.org/licenses/MIT. * * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). * The AI-generated portions may be considered public domain (CC0-1.0) * and not subject to the project's licence. The human contributor has * reviewed and verified that the code is correct. * * SPDX-License-Identifier: MIT and CC0-1.0 **************************************************************************/ /**************************************************************************/ /** */ /** ThreadX Component */ /** */ /** FreeRTOS compatibility layer, task creation resource accounting */ /** */ /**************************************************************************/ /* xTaskCreate() takes two byte pool allocations, the stack and the task structure, then creates a notification semaphore and a thread. It is the function the rest of the layer's error handling is modelled on, so these checks also serve as the reference for what correct unwinding looks like. */ #include "txfr_test_harness.h" #include "task.h" #define TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE) #define TASK_PRIORITY (3u) static StaticTask_t task_control_block; static StackType_t task_stack[TASK_STACK_DEPTH]; /* Each failure case gets its own buffers, for two reasons. vTaskDelete() only queues a task for the idle task to reap, so the control block of a task deleted above is still live while this test thread runs. And if one of these paths ever regresses and leaves an object behind, a shared buffer would carry that damage into the next case and report misleading counts there. */ static StaticTask_t semaphore_failure_control_block; static StackType_t semaphore_failure_stack[TASK_STACK_DEPTH]; static StaticTask_t thread_failure_control_block; static StackType_t thread_failure_stack[TASK_STACK_DEPTH]; static StaticTask_t resume_failure_control_block; static StackType_t resume_failure_stack[TASK_STACK_DEPTH]; static void test_task_entry(void *p_arg) { (void)p_arg; /* Never scheduled at this priority while the test thread runs, but a task entry point must not return. */ for(;;) { vTaskDelay(portMAX_DELAY); } } void txfr_test_body(void) { BaseType_t result; TaskHandle_t task; TXFR_COUNTERS counters; /* A successful creation takes both allocations, creates the notification semaphore and the thread, and releases nothing. */ task = NULL; txfr_test_account_start(TXFR_INJECT_NONE, 0); result = xTaskCreate(test_task_entry, "ok", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, &task); txfr_test_account_stop(&counters); txfr_test_check("xTaskCreate reports success", result == pdPASS); txfr_test_check("xTaskCreate returns a handle", task != NULL); txfr_test_check_counts("xTaskCreate accounting", &counters, 2, 0, 2, 0); if(task != NULL) { vTaskDelete(task); } /* The notification semaphore fails before anything else is created, so both allocations must come back and nothing needs deleting. */ task = NULL; txfr_test_account_start(TXFR_INJECT_SEMAPHORE_CREATE, 1); result = xTaskCreate(test_task_entry, "sem", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, &task); txfr_test_account_stop(&counters); txfr_test_check("xTaskCreate fails when the semaphore fails", result != pdPASS); txfr_test_check_counts("semaphore failure releases stack and task", &counters, 2, 2, 1, 0); /* The thread creation fails with the semaphore already in place, so that semaphore must be deleted and both allocations released. */ task = NULL; txfr_test_account_start(TXFR_INJECT_THREAD_CREATE, 1); result = xTaskCreate(test_task_entry, "thread", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, &task); txfr_test_account_stop(&counters); txfr_test_check("xTaskCreate fails when the thread fails", result != pdPASS); txfr_test_check_counts("thread failure unwinds the semaphore too", &counters, 2, 2, 2, 1); /* The static variant takes no allocations, and creates the semaphore and the thread from the caller's buffers. */ txfr_test_account_start(TXFR_INJECT_NONE, 0); task = xTaskCreateStatic(test_task_entry, "static", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, task_stack, &task_control_block); txfr_test_account_stop(&counters); txfr_test_check("xTaskCreateStatic returns a handle", task != NULL); txfr_test_check_counts("xTaskCreateStatic accounting", &counters, 0, 0, 2, 0); if(task != NULL) { vTaskDelete(task); } /* As with the queues, the static variant owns no memory but still creates kernel objects and still returns nothing on failure. A semaphore or a thread left behind here stays registered in the kernel, pointing into the caller's StaticTask_t. */ txfr_test_account_start(TXFR_INJECT_SEMAPHORE_CREATE, 1); task = xTaskCreateStatic(test_task_entry, "s_sem", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, semaphore_failure_stack, &semaphore_failure_control_block); txfr_test_account_stop(&counters); txfr_test_check("xTaskCreateStatic returns NULL on semaphore failure", task == NULL); txfr_test_check_counts("static semaphore failure leaves nothing behind", &counters, 0, 0, 1, 0); txfr_test_account_start(TXFR_INJECT_THREAD_CREATE, 1); task = xTaskCreateStatic(test_task_entry, "s_thr", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, thread_failure_stack, &thread_failure_control_block); txfr_test_account_stop(&counters); txfr_test_check("xTaskCreateStatic returns NULL on thread failure", task == NULL); txfr_test_check_counts("static thread failure unwinds the semaphore", &counters, 0, 0, 2, 1); /* The resume is the last step, so by then both kernel objects exist. The thread has to be terminated before it can be deleted, which is the order the idle task uses when it reaps a deleted task. */ txfr_test_account_start(TXFR_INJECT_THREAD_RESUME, 1); task = xTaskCreateStatic(test_task_entry, "s_res", TASK_STACK_DEPTH, NULL, TASK_PRIORITY, resume_failure_stack, &resume_failure_control_block); txfr_test_account_stop(&counters); txfr_test_check("xTaskCreateStatic returns NULL on resume failure", task == NULL); txfr_test_check_counts("static resume failure unwinds thread and semaphore", &counters, 0, 0, 2, 2); }