diff options
Diffstat (limited to 'test/smp/regression/threadx_byte_memory_long_search_test.c')
| -rw-r--r-- | test/smp/regression/threadx_byte_memory_long_search_test.c | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/test/smp/regression/threadx_byte_memory_long_search_test.c b/test/smp/regression/threadx_byte_memory_long_search_test.c new file mode 100644 index 00000000..8f076335 --- /dev/null +++ b/test/smp/regression/threadx_byte_memory_long_search_test.c @@ -0,0 +1,337 @@ +/*************************************************************************** + * 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 + **************************************************************************/ + +/* This test makes _tx_byte_pool_search walk a long chain of fragments, from + more than one core at a time. + + The SMP version of that function carries two things its ThreadX counterpart + does not, and neither was covered. + + The first is the block-search limit. Under TX_BYTE_POOL_MULTIPLE_BLOCK_SEARCH + -- which this port's tx_user.h sets to 20 -- the search holds protection + across at most twenty blocks and then deliberately drops and retakes it, so + that a long search cannot lock the other cores out for the whole walk. + Nothing in the suite ever made one search examine twenty blocks: every byte + pool it builds has a handful of fragments at most. That is what this test + fixes. + + Having dropped protection, the search then checks whether some other thread + claimed the pool while it was let go, and if so spins for + TX_BYTE_POOL_DELAY_VALUE iterations before taking ownership back and + restarting the walk. That path this test does not reach, and the reason is a + property of the port rather than of the test: the Linux SMP port serialises + every core on one pthread mutex, and the window here is an unlock + immediately followed by a lock on that mutex. A thread spinning for the + protection on another core has to win that handoff, and glibc hands an + uncontended mutex straight back to the thread that just released it. + Measured: 180,003 windows over three cores, and the pool changed hands in + none of them. So tx_byte_pool_search.c lines 283 to 301 stay uncovered on + this port, and no number of iterations here will change that -- 250 is used + below rather than the tens of thousands that were tried, because the twenty- + block threshold above is crossed by the very first search and the rest buys + nothing. + + The pool is therefore filled with small allocations until it refuses another + one, and every second allocation is then released. That leaves upwards of + twenty free fragments, each too small to satisfy the request and none of + them adjacent to another, so the search can neither stop early nor merge its + way out of the walk. Every request is expected to fail with TX_NO_MEMORY; + the failure is the point, because a search that fails is a search that + walked the whole pool. + + The layout is asserted rather than assumed. If a change to the pool + arithmetic ever made the request satisfiable, or left fewer fragments than + the twenty-block threshold needs, this test would still pass while quietly + covering nothing -- so it checks the fragment count and checks that the + request really does fail before the threads start. + + The three worker threads are deliberately not confined to a core, so that + the walk really is driven from more than one core at a time. */ + +#include <stdio.h> +#include "tx_api.h" +#include "threadx_test_port.h" + + +void test_control_return(UINT status); + + +/* Small chunks, every second one given back. The pool is filled until it + refuses another chunk rather than a fixed number of times, so that the + layout does not depend on this test agreeing with the allocator about the + per-block overhead -- get that wrong by one word and the pool is left with a + free tail large enough to satisfy the request below, and the search stops at + the first fragment instead of walking. */ + +#define CHUNK_BYTES ((ULONG) 8) +#define CHUNK_LIMIT ((UINT) 96) +#define MINIMUM_CHUNKS ((UINT) 40) + +/* Larger than any one free fragment and smaller than the pool's theoretical + capacity, which is what makes the search walk rather than refuse at the + door: _tx_byte_pool_search returns immediately when the request is at least + the theoretical total, without examining a single block. */ + +#define REQUEST_BYTES ((ULONG) 64) + +/* TX_BYTE_POOL_MULTIPLE_BLOCK_SEARCH is 20 on this port. The walk has to be + longer than that for the protection to be dropped at all. */ + +#define MINIMUM_FRAGMENTS ((ULONG) 24) + +/* Each worker searches this many times. One search is enough for the coverage; + the repetition is here so that the pool is genuinely walked from several + cores at once rather than by one thread at a time, which is the condition + the code under test was written for. */ + +#define SEARCHES_PER_THREAD ((ULONG) 250) + +#define WORKERS ((UINT) 3) + + +static TX_THREAD thread_0; +static TX_THREAD worker[WORKERS]; + +static TX_BYTE_POOL pool_0; + +static UCHAR pool_area[TX_TEST_BYTE_POOL_CAPACITY_BYTES(CHUNK_BYTES, 48)]; + +static void *chunk[CHUNK_LIMIT]; +static UINT chunk_count; + +static volatile ULONG workers_done = 0; +static volatile ULONG search_count[WORKERS]; + +static UINT error = 0; + + +static void thread_0_entry(ULONG thread_input); +static void worker_entry(ULONG thread_input); + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void threadx_byte_memory_long_search_application_define(void *first_unused_memory) +#endif +{ + +UINT status; +CHAR *pointer; +UINT i; +TX_NAME_CONST CHAR *name; +ULONG available; +ULONG fragments; +void *probe_ptr; + + + /* Setup a pointer. */ + pointer = (CHAR *) first_unused_memory; + + status = tx_byte_pool_create(&pool_0, "long search pool", pool_area, sizeof(pool_area)); + + if (status != TX_SUCCESS) + { + + printf("Running Byte Memory Long Search Test................................ ERROR #1\n"); + test_control_return(1); + return; + } + + /* Fill the pool completely with small chunks, so that no free tail is left + behind that could satisfy the request. */ + chunk_count = ((UINT) 0); + while (chunk_count < CHUNK_LIMIT) + { + + status = tx_byte_allocate(&pool_0, &chunk[chunk_count], CHUNK_BYTES, TX_NO_WAIT); + + if (status != TX_SUCCESS) + { + + break; + } + + chunk_count++; + } + + if ((chunk_count < MINIMUM_CHUNKS) || (chunk_count == CHUNK_LIMIT)) + { + + printf("Running Byte Memory Long Search Test................................ ERROR #2\n"); + test_control_return(1); + return; + } + + /* Give every second one back. Alternating rather than consecutive, so that + no free fragment has a free neighbour and the search cannot merge two of + them into a block big enough to satisfy the request. */ + for (i = 1; i < chunk_count; i = i + ((UINT) 2)) + { + + status = tx_byte_release(chunk[i]); + + if (status != TX_SUCCESS) + { + + printf("Running Byte Memory Long Search Test................................ ERROR #3\n"); + test_control_return(1); + return; + } + + chunk[i] = TX_NULL; + } + + /* The walk has to be long enough to cross the twenty-block threshold. */ + status = tx_byte_pool_info_get(&pool_0, &name, &available, &fragments, TX_NULL, TX_NULL, TX_NULL); + + if ((status != TX_SUCCESS) || (fragments < MINIMUM_FRAGMENTS)) + { + + printf("Running Byte Memory Long Search Test................................ ERROR #4\n"); + test_control_return(1); + return; + } + + /* And the request has to fail, or the search stops at the first fragment + that fits and the walk never happens. */ + status = tx_byte_allocate(&pool_0, &probe_ptr, REQUEST_BYTES, TX_NO_WAIT); + + if (status != TX_NO_MEMORY) + { + + printf("Running Byte Memory Long Search Test................................ ERROR #5\n"); + test_control_return(1); + return; + } + + status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0, + pointer, TEST_STACK_SIZE_PRINTF, + 15, 15, TX_NO_TIME_SLICE, TX_AUTO_START); + pointer = pointer + TEST_STACK_SIZE_PRINTF; + + if (status != TX_SUCCESS) + { + + printf("Running Byte Memory Long Search Test................................ ERROR #6\n"); + test_control_return(1); + return; + } + + /* The workers share one priority so that the scheduler is free to put them + on different cores, which is the whole point. */ + for (i = 0; i < WORKERS; i++) + { + + search_count[i] = ((ULONG) 0); + + status = tx_thread_create(&worker[i], "worker", worker_entry, (ULONG) i, + pointer, TEST_STACK_SIZE_PRINTF, + 16, 16, TX_NO_TIME_SLICE, TX_AUTO_START); + pointer = pointer + TEST_STACK_SIZE_PRINTF; + + if (status != TX_SUCCESS) + { + + printf("Running Byte Memory Long Search Test................................ ERROR #7\n"); + test_control_return(1); + } + } +} + + +static void thread_0_entry(ULONG thread_input) +{ + +UINT status; +UINT i; +TX_NAME_CONST CHAR *name; +ULONG available; +ULONG fragments; + + + /* Inform user. */ + printf("Running Byte Memory Long Search Test................................ "); + + /* Wait for the workers. They do not sleep, so this thread has to. */ + while (workers_done != ((ULONG) WORKERS)) + { + + tx_thread_sleep(1); + } + + for (i = 0; i < WORKERS; i++) + { + + if (search_count[i] != SEARCHES_PER_THREAD) + { + + error++; + } + } + + /* Every search failed, so nothing was taken and nothing was merged: the + pool must look exactly as it did when the workers started. */ + status = tx_byte_pool_info_get(&pool_0, &name, &available, &fragments, TX_NULL, TX_NULL, TX_NULL); + + if ((status != TX_SUCCESS) || (fragments < MINIMUM_FRAGMENTS)) + { + + error++; + } + + if (error) + { + + printf("ERROR #8\n"); + test_control_return(1); + } + else + { + + /* Successful test. */ + printf("SUCCESS!\n"); + test_control_return(0); + } +} + + +static void worker_entry(ULONG thread_input) +{ + +UINT status; +ULONG i; +void *memory_ptr; + + + for (i = 0; i < SEARCHES_PER_THREAD; i++) + { + + status = tx_byte_allocate(&pool_0, &memory_ptr, REQUEST_BYTES, TX_NO_WAIT); + + if (status != TX_NO_MEMORY) + { + + error++; + break; + } + + search_count[thread_input]++; + } + + workers_done++; +} |
