summaryrefslogtreecommitdiff
path: root/test/smp/regression/threadx_object_delete_suspension_test.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/smp/regression/threadx_object_delete_suspension_test.c')
-rw-r--r--test/smp/regression/threadx_object_delete_suspension_test.c481
1 files changed, 481 insertions, 0 deletions
diff --git a/test/smp/regression/threadx_object_delete_suspension_test.c b/test/smp/regression/threadx_object_delete_suspension_test.c
new file mode 100644
index 00000000..20633f08
--- /dev/null
+++ b/test/smp/regression/threadx_object_delete_suspension_test.c
@@ -0,0 +1,481 @@
+/***************************************************************************
+ * 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 deletes a block pool, a byte pool, an event flags group and a
+ queue while two threads are suspended on each of them, and checks that every
+ suspended thread is released with TX_DELETED.
+
+ Each of those four delete routines carries the same loop, and in this suite
+ the whole loop body was uncovered -- thirty-six lines across the four files,
+ the largest single gap in the SMP coverage report:
+
+ while (suspended_count != TX_NO_SUSPENSIONS)
+ {
+ suspended_count--;
+ TX_DISABLE
+ thread_ptr -> tx_thread_suspend_cleanup = TX_NULL;
+ thread_ptr -> tx_thread_suspend_status = TX_DELETED;
+ next_thread = thread_ptr -> tx_thread_suspended_next;
+ ...
+ _tx_thread_system_resume(thread_ptr);
+ thread_ptr = next_thread;
+ }
+
+ The reason it was uncovered is worth writing down, because it is not that
+ the suite never deletes these objects -- it deletes all four after every
+ single test. test_control_cleanup() in the ThreadX suite deletes the
+ application's objects first and its threads last, so a test that ends with a
+ thread parked on a queue has that thread walked out of the queue by
+ tx_queue_delete. The SMP suite's cleanup deletes the threads first, on
+ purpose: it was changed so that no application-owned object is still
+ referenced when the object loops run, which is what stopped a class of
+ teardown hang. Correct, and the side effect is that every one of those
+ deletes now runs against an empty suspension list.
+
+ So the coverage has to come from a test that means to delete a busy object
+ rather than from teardown that happens to. threadx_semaphore_delete_test
+ already does exactly this for semaphores, which is why tx_semaphore_delete.c
+ is the one member of the family that was fully covered; this is the same
+ idea for the other four.
+
+ Two waiters per object rather than one, so the loop runs twice and its back
+ edge is taken as well as its body.
+
+ The threads are not confined to a core. On this port they start on separate
+ cores and suspend in whatever order they get scheduled, so the control
+ thread waits on the object's own suspended count through tx_*_info_get
+ rather than on an ordering it cannot guarantee. The wait is bounded: a
+ suspension that never arrives fails the test instead of hanging it. */
+
+#include <stdio.h>
+#include "tx_api.h"
+#include "threadx_test_port.h"
+
+
+void test_control_return(UINT status);
+
+
+/* The four objects, deleted in this order. */
+
+#define STAGE_BLOCK_POOL ((UINT) 0)
+#define STAGE_BYTE_POOL ((UINT) 1)
+#define STAGE_EVENT_FLAGS ((UINT) 2)
+#define STAGE_QUEUE ((UINT) 3)
+#define STAGE_COUNT ((UINT) 4)
+
+/* Number of threads that suspend on each object. */
+
+#define WAITERS ((ULONG) 2)
+
+/* How many ticks the control thread will wait for a suspension before giving
+ up. Generous, because it is only ever reached when the test is failing --
+ and finite, because the alternative is a run that hangs instead of one that
+ reports. */
+
+#define WAIT_LIMIT_TICKS ((UINT) 200)
+
+
+static TX_THREAD thread_0;
+static TX_THREAD thread_1;
+static TX_THREAD thread_2;
+
+static TX_BLOCK_POOL block_pool_0;
+static TX_BYTE_POOL byte_pool_0;
+static TX_EVENT_FLAGS_GROUP group_0;
+static TX_QUEUE queue_0;
+
+/* Eight blocks of 16 bytes, all of them taken before any waiter runs. */
+
+static UCHAR block_pool_area[TX_TEST_BLOCK_POOL_BYTES(16, 8)];
+static UCHAR byte_pool_area[TX_TEST_BYTE_POOL_BYTES(256)];
+static ULONG queue_area[16];
+
+/* Counts of waiters that saw TX_DELETED, one entry per stage. */
+
+static volatile ULONG deleted_count[STAGE_COUNT];
+
+static UINT error = 0;
+
+
+static void thread_0_entry(ULONG thread_input);
+static void waiter_entry(ULONG thread_input);
+
+
+/* Define what the initial system looks like. */
+
+#ifdef CTEST
+void test_application_define(void *first_unused_memory)
+#else
+void threadx_object_delete_suspension_application_define(void *first_unused_memory)
+#endif
+{
+
+UINT status;
+CHAR *pointer;
+void *block_ptr;
+void *bytes_ptr;
+UINT i;
+
+
+ /* Setup a pointer. */
+ pointer = (CHAR *) first_unused_memory;
+
+ for (i = 0; i < STAGE_COUNT; i++)
+ {
+
+ deleted_count[i] = ((ULONG) 0);
+ }
+
+ /* Create the block pool and take every block, so that a waiter has nothing
+ to be given and suspends instead. */
+ status = tx_block_pool_create(&block_pool_0, "delete suspension block pool",
+ 16, block_pool_area, sizeof(block_pool_area));
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Object Delete Suspension Test............................... ERROR #1\n");
+ test_control_return(1);
+ }
+
+ while (tx_block_allocate(&block_pool_0, &block_ptr, TX_NO_WAIT) == TX_SUCCESS)
+ {
+
+ /* Nothing to do with the block; the point is that the pool is empty. */
+ }
+
+ /* Same idea for the byte pool: take enough of it that the 128-byte requests
+ below cannot be met. */
+ status = tx_byte_pool_create(&byte_pool_0, "delete suspension byte pool",
+ byte_pool_area, sizeof(byte_pool_area));
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Object Delete Suspension Test............................... ERROR #2\n");
+ test_control_return(1);
+ }
+
+ status = tx_byte_allocate(&byte_pool_0, &bytes_ptr, 200, TX_NO_WAIT);
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Object Delete Suspension Test............................... ERROR #3\n");
+ test_control_return(1);
+ }
+
+ /* An event flags group with nothing set. The waiters ask for a flag that is
+ never set by anybody. */
+ status = tx_event_flags_create(&group_0, "delete suspension group");
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Object Delete Suspension Test............................... ERROR #4\n");
+ test_control_return(1);
+ }
+
+ /* An empty queue. The waiters receive from it and nobody ever sends. */
+ status = tx_queue_create(&queue_0, "delete suspension queue", TX_1_ULONG,
+ queue_area, sizeof(queue_area));
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("Running Object Delete Suspension Test............................... ERROR #5\n");
+ test_control_return(1);
+ }
+
+ /* The control thread runs at a higher priority than the waiters, but that
+ decides nothing here -- it waits on the suspended counts. */
+ 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 Object Delete Suspension Test............................... ERROR #6\n");
+ test_control_return(1);
+ }
+
+ status = tx_thread_create(&thread_1, "thread 1", waiter_entry, 1,
+ 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 Object Delete Suspension Test............................... ERROR #7\n");
+ test_control_return(1);
+ }
+
+ status = tx_thread_create(&thread_2, "thread 2", waiter_entry, 2,
+ 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 Object Delete Suspension Test............................... ERROR #8\n");
+ test_control_return(1);
+ }
+}
+
+
+/* Read the number of threads suspended on the object belonging to a stage.
+ Returns TX_TRUE when the count could be read, so that a failing info_get is
+ reported rather than mistaken for "nobody has suspended yet". */
+
+static UINT suspended_on(UINT stage, ULONG *count)
+{
+
+UINT status;
+
+
+ /* Every destination but the suspended count is TX_NULL: each info_get
+ tests its destinations individually and skips the ones it is not given,
+ so there is nothing to declare here to throw away. */
+ switch (stage)
+ {
+
+ case STAGE_BLOCK_POOL:
+
+ status = tx_block_pool_info_get(&block_pool_0, TX_NULL, TX_NULL, TX_NULL,
+ TX_NULL, count, TX_NULL);
+ break;
+
+ case STAGE_BYTE_POOL:
+
+ status = tx_byte_pool_info_get(&byte_pool_0, TX_NULL, TX_NULL, TX_NULL,
+ TX_NULL, count, TX_NULL);
+ break;
+
+ case STAGE_EVENT_FLAGS:
+
+ status = tx_event_flags_info_get(&group_0, TX_NULL, TX_NULL,
+ TX_NULL, count, TX_NULL);
+ break;
+
+ default:
+
+ status = tx_queue_info_get(&queue_0, TX_NULL, TX_NULL, TX_NULL,
+ TX_NULL, count, TX_NULL);
+ break;
+ }
+
+ return((status == TX_SUCCESS) ? ((UINT) TX_TRUE) : ((UINT) TX_FALSE));
+}
+
+
+/* Sleep until every waiter has suspended on this stage's object, or until the
+ budget runs out. Returns TX_TRUE if the waiters arrived. */
+
+static UINT wait_for_waiters(UINT stage)
+{
+
+UINT ticks;
+ULONG count;
+
+
+ count = ((ULONG) 0);
+ for (ticks = 0; ticks < WAIT_LIMIT_TICKS; ticks++)
+ {
+
+ if (suspended_on(stage, &count) == ((UINT) TX_FALSE))
+ {
+
+ return((UINT) TX_FALSE);
+ }
+
+ if (count == WAITERS)
+ {
+
+ return((UINT) TX_TRUE);
+ }
+
+ tx_thread_sleep(1);
+ }
+
+ return((UINT) TX_FALSE);
+}
+
+
+/* Sleep until every waiter has reported TX_DELETED for this stage, or until the
+ budget runs out. */
+
+static UINT wait_for_reports(UINT stage)
+{
+
+UINT ticks;
+
+
+ for (ticks = 0; ticks < WAIT_LIMIT_TICKS; ticks++)
+ {
+
+ if (deleted_count[stage] == WAITERS)
+ {
+
+ return((UINT) TX_TRUE);
+ }
+
+ tx_thread_sleep(1);
+ }
+
+ return((UINT) TX_FALSE);
+}
+
+
+static void thread_0_entry(ULONG thread_input)
+{
+
+UINT status;
+UINT stage;
+
+
+ /* Inform user. */
+ printf("Running Object Delete Suspension Test............................... ");
+
+ for (stage = 0; stage < STAGE_COUNT; stage++)
+ {
+
+ /* Both waiters have to be on the list before the delete, or the loop
+ under test walks a shorter list than this test means it to. */
+ if (wait_for_waiters(stage) == ((UINT) TX_FALSE))
+ {
+
+ printf("ERROR #9 (stage %u)\n", stage);
+ test_control_return(1);
+ }
+
+ switch (stage)
+ {
+
+ case STAGE_BLOCK_POOL:
+
+ status = tx_block_pool_delete(&block_pool_0);
+ break;
+
+ case STAGE_BYTE_POOL:
+
+ status = tx_byte_pool_delete(&byte_pool_0);
+ break;
+
+ case STAGE_EVENT_FLAGS:
+
+ status = tx_event_flags_delete(&group_0);
+ break;
+
+ default:
+
+ status = tx_queue_delete(&queue_0);
+ break;
+ }
+
+ if (status != TX_SUCCESS)
+ {
+
+ printf("ERROR #10 (stage %u)\n", stage);
+ test_control_return(1);
+ }
+
+ /* Every waiter must come out of the delete with TX_DELETED, which is
+ the status the loop under test writes into each suspended thread. */
+ if (wait_for_reports(stage) == ((UINT) TX_FALSE))
+ {
+
+ printf("ERROR #11 (stage %u)\n", stage);
+ test_control_return(1);
+ }
+ }
+
+ if (error)
+ {
+
+ printf("ERROR #12\n");
+ test_control_return(1);
+ }
+ else
+ {
+
+ /* Successful test. */
+ printf("SUCCESS!\n");
+ test_control_return(0);
+ }
+}
+
+
+static void waiter_entry(ULONG thread_input)
+{
+
+UINT status;
+UINT stage;
+void *block_ptr;
+void *bytes_ptr;
+ULONG actual_flags;
+ULONG message;
+
+
+ for (stage = 0; stage < STAGE_COUNT; stage++)
+ {
+
+ switch (stage)
+ {
+
+ case STAGE_BLOCK_POOL:
+
+ /* The pool was emptied before any thread ran, so this suspends. */
+ status = tx_block_allocate(&block_pool_0, &block_ptr, TX_WAIT_FOREVER);
+ break;
+
+ case STAGE_BYTE_POOL:
+
+ /* More than is left after the allocation made at define time. */
+ status = tx_byte_allocate(&byte_pool_0, &bytes_ptr, 128, TX_WAIT_FOREVER);
+ break;
+
+ case STAGE_EVENT_FLAGS:
+
+ /* Nobody ever sets this flag. */
+ status = tx_event_flags_get(&group_0, ((ULONG) 0x1), TX_AND_CLEAR,
+ &actual_flags, TX_WAIT_FOREVER);
+ break;
+
+ default:
+
+ /* Nobody ever sends to this queue. */
+ status = tx_queue_receive(&queue_0, &message, TX_WAIT_FOREVER);
+ break;
+ }
+
+ if (status == TX_DELETED)
+ {
+
+ deleted_count[stage]++;
+ }
+ else
+ {
+
+ /* Anything else means the object was not deleted out from under
+ this thread, and the control thread will say so. */
+ error++;
+ return;
+ }
+ }
+}