summaryrefslogtreecommitdiff
path: root/test/smp/regression/threadx_byte_memory_long_search_test.c
diff options
context:
space:
mode:
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.c337
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++;
+}