/*************************************************************************** * 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 #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++; }