* Fixed the resource leaks on the xQueueCreate error paths xQueueCreate() allocated the queue descriptor and its backing memory, then created two ThreadX semaphores, and returned NULL on either semaphore failure without releasing anything. Since no handle reached the caller, vQueueDelete() could not be used to recover, so both allocations were lost. A failure on the second semaphore additionally abandoned the read semaphore it had already created, leaving a live ThreadX control block inside freed memory. Release the backing memory and the descriptor on both paths, and delete the read semaphore before returning when the write semaphore cannot be created. This is the teardown order vQueueDelete() already uses, and it matches the cleanup xTaskCreate() performs on its own error paths. Verified with a fault injection harness that intercepts the ThreadX byte pool and semaphore entry points to force tx_semaphore_create() to fail on a chosen call. On a read semaphore failure the layer previously performed 2 allocations and 0 releases, and on a write semaphore failure 2 allocations, 0 releases and 0 semaphore deletions. It now performs 2 releases in both cases and deletes the read semaphore in the second, with the byte pool restored to its prior state. Fixes https://github.com/eclipse-threadx/threadx/issues/570 Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com> * Added a regression suite for the FreeRTOS compatibility layer The compatibility layer had no tests in this repository, which is awkward for its creation functions in particular. Each of them takes one or two byte pool allocations for its bookkeeping and then creates ThreadX kernel objects, and each returns NULL when a kernel object cannot be created. The caller is left without a handle, so it cannot call the matching delete function, and anything the layer failed to release is gone until the system restarts. A leaking version and a correct version are indistinguishable from the outside, which is how the leak in issue 570 went unnoticed. Add a suite that counts what the layer takes and gives back. A test asks the harness to fail a chosen kernel creation call, then checks the number of byte pool allocations, releases, object creations and object deletions performed. The ThreadX entry points are intercepted with the linker's --wrap so that tx_freertos.c is compiled exactly as it ships, with no test hooks in it. Note that tx_api.h maps the public API onto the error checking entry points, so the _txe_ symbols are the ones wrapped. Coverage is the creation and teardown paths of queues, tasks, semaphores, mutexes, event groups and timers, including a regression test for the two paths fixed for issue 570. The suite follows the layout of the existing ThreadX and SMP suites, is registered with ctest, and runs in CI through the shared regression template. It is built 32 bit because the Linux port defines ULONG as unsigned int on x86_64 while the layer passes pointers through ULONG arguments, so a 64 bit build truncates them. It is Linux only because --wrap has no MSVC equivalent, and the CMake configuration says so rather than failing at link time. Validated by building the suite against the layer as it stands before the issue 570 fix, where the two expected checks fail with the leaked counts, and against the fixed layer, where all three tests pass. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
FreeRTOS compatibility layer regression tests
Regression tests for utility/rtos_compatibility_layers/FreeRTOS/tx_freertos.c,
built against the real ThreadX Linux port.
Running them
./scripts/build_freertos.sh
./scripts/test_freertos.sh
Or directly, which is the same thing:
test/freertos/cmake/run.sh build all
test/freertos/cmake/run.sh test all
CI runs both scripts through .github/workflows/regression_test.yml.
What they cover
Every function in the layer that creates an object takes one or two byte pool
allocations for its bookkeeping and then creates one or more ThreadX kernel
objects. When one of those kernel objects cannot be created, the function
returns NULL, or pdFAIL for xTaskCreate(), and the caller is left with no
handle and therefore no way to call the matching delete function. Anything the
layer fails to release on the way out is lost until the system restarts.
That makes these error paths invisible from the outside: a leaking version and a correct version return exactly the same thing to the caller. The suite therefore counts the ThreadX primitives the layer reaches for, and checks that each error path gives back precisely what it took.
| Test | Covers |
|---|---|
txfr_queue_create_test |
xQueueCreate, xQueueCreateStatic, vQueueDelete |
txfr_task_create_test |
xTaskCreate, xTaskCreateStatic |
txfr_sync_create_test |
semaphores, mutexes, event groups and timers |
How the fault injection works
A test asks the harness to fail a chosen kernel creation call, then reads back
how many allocations, releases, object creations and object deletions the layer
performed. The interception is done with the linker's --wrap option, so
tx_freertos.c is compiled exactly as it ships, with no test hooks in it.
Two things are worth knowing before adding tests:
tx_api.hmaps the public API onto the error checking entry points, so the symbols that exist at link time are the_txe_variants, and those are what the wrap list incmake/regression/CMakeLists.txtnames. A--wrapfor a name that does not resolve is silently ignored, so a typo there produces a test that quietly never injects anything.txfr_malloc()andtxfr_free()cannot be wrapped, because they are defined intx_freertos.cand called from within it, so the compiler resolves those calls internally. The byte pool counts stand in for them.
Because --wrap is a GNU ld and lld feature with no MSVC equivalent, this
suite is Linux only. The CMake configuration stops with a clear message rather
than failing later with confusing link errors.
Fixtures
fixtures/FreeRTOSConfig.h configures the layer for the tests. Two of its
settings are not arbitrary:
configASSERT()andTX_FREERTOS_ASSERT_FAIL()are empty, since the tests drive error paths deliberately and neither may halt the run.portDISABLE_INTERRUPTS()andportENABLE_INTERRUPTS()are defined up front.FreeRTOS.hpicks those by compiler rather than by target, so a GNU build otherwise resolves them to the bare metal__disable_interrupts()intrinsic, which does not exist when the layer is hosted on Linux.
fixtures/tx_user.h supplies TX_THREAD_USER_EXTENSION, which the layer
requires, as documented in the layer's own readme.md.
Why the build is 32 bit
The Linux port defines ULONG as unsigned int on x86_64, while the layer
passes pointers through ULONG arguments, such as the task argument and the
timer identifier. A 64 bit build truncates those pointers, which the compiler
reports as -Wpointer-to-int-cast and which crashes the timer callback
wrapper. The ThreadX and SMP suites build 32 bit for their own reasons; this
suite has to.