1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
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++;
}
|