VirtualBox

source: vbox/trunk/src/VBox/VMM/testcase/tstPDMQueue.cpp@ 96945

Last change on this file since 96945 was 96407, checked in by vboxsync, 2 years ago

scm copyright and license note update

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.0 KB
Line 
1/* $Id: tstPDMQueue.cpp 96407 2022-08-22 17:43:14Z vboxsync $ */
2/** @file
3 * PDM Queue Testcase.
4 */
5
6/*
7 * Copyright (C) 2022 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_PDM_QUEUE
33#define VBOX_IN_VMM
34
35#include <VBox/vmm/pdmqueue.h>
36
37#include <VBox/vmm/vm.h>
38#include <VBox/vmm/uvm.h>
39#include <VBox/vmm/vmm.h>
40
41#include <iprt/errcore.h>
42#include <VBox/log.h>
43#include <iprt/assert.h>
44#include <iprt/initterm.h>
45#include <iprt/message.h>
46#include <iprt/rand.h>
47#include <iprt/string.h>
48#include <iprt/thread.h>
49#include <iprt/test.h>
50
51
52/*********************************************************************************************************************************
53* Global Variables *
54*********************************************************************************************************************************/
55static RTTEST g_hTest;
56
57
58/*********************************************************************************************************************************
59* Test #2 - Threading *
60*********************************************************************************************************************************/
61typedef struct TEST2ITEM
62{
63 PDMQUEUEITEMCORE Core;
64 uint32_t iSeqNo;
65 uint32_t iThreadNo;
66 /* Pad it up to two cachelines to reduce noise. */
67 uint8_t abPadding[128 - sizeof(PDMQUEUEITEMCORE) - sizeof(uint32_t) * 2];
68} TEST2ITEM;
69typedef TEST2ITEM *PTEST2ITEM;
70
71static struct TEST2THREAD
72{
73 RTTHREAD hThread;
74 uint32_t iThreadNo;
75 uint32_t cMaxPending;
76 /* Pad one cache line. */
77 uint8_t abPadding1[64];
78 uint32_t volatile cPending;
79 uint32_t volatile iReceiveSeqNo;
80 /* Pad structure size to three cache lines. */
81 uint8_t abPadding2[64 * 2 - sizeof(uint32_t) * 4 - sizeof(RTTHREAD)];
82} g_aTest2Threads[16];
83
84static bool volatile g_fTest2Terminate = false;
85static uint32_t volatile g_cTest2Threads = 0;
86static uint32_t g_cTest2Received = 0;
87static bool volatile g_fTest2PushBack = false;
88static PVM volatile g_pTest2VM = NULL; /**< Volatile to force local copy in thread function. */
89static PDMQUEUEHANDLE volatile g_hTest2Queue = NIL_PDMQUEUEHANDLE; /**< Ditto. */
90
91
92
93/**
94 * @callback_method_impl{FNPDMQUEUEEXT, Consumer callback}
95 */
96static DECLCALLBACK(bool) Test2ConsumerCallback(void *pvUser, PPDMQUEUEITEMCORE pItem)
97{
98 PTEST2ITEM pMyItem = (PTEST2ITEM)pItem;
99 size_t const iThread = pMyItem->iThreadNo;
100 RTTEST_CHECK_RET(g_hTest, iThread < RT_ELEMENTS(g_aTest2Threads), true);
101
102 /*
103 * Start pushing back after the first million or when the
104 * control thread decide it's time for it:
105 */
106 uint32_t cReceived = ++g_cTest2Received;
107 if (g_fTest2PushBack)
108 {
109 if ((cReceived & 3) == 3 && cReceived > _1M)
110 return false;
111 }
112 else if (cReceived < _1M )
113 { /* likely */ }
114 else
115 g_fTest2PushBack = true;
116
117 /*
118 * Process the item:
119 */
120 uint32_t iCallbackNo = ASMAtomicIncU32(&g_aTest2Threads[iThread].iReceiveSeqNo);
121 if (pMyItem->iSeqNo != iCallbackNo)
122 RTTestFailed(g_hTest, "iThread=%#x: iSeqNo=%#x, expected %#x\n", iThread, pMyItem->iSeqNo, iCallbackNo);
123
124 ASMAtomicDecU32(&g_aTest2Threads[iThread].cPending);
125
126 RT_NOREF(pvUser);
127 return true;
128}
129
130
131/**
132 * @callback_method_impl{FNRTTHREAD, Producer thread}
133 */
134static DECLCALLBACK(int) Test2Thread(RTTHREAD hThreadSelf, void *pvUser)
135{
136 PVM const pVM = g_pTest2VM;
137 PDMQUEUEHANDLE const hQueue = g_hTest2Queue;
138 size_t const iThread = (size_t)pvUser;
139 RTTEST_CHECK_RET(g_hTest, iThread < RT_ELEMENTS(g_aTest2Threads), VERR_INVALID_PARAMETER);
140
141 uint32_t iSendSeqNo = 0;
142 uint32_t cSpinLoops = 0;
143 while (!g_fTest2Terminate && iSendSeqNo < _64M)
144 {
145 if (g_aTest2Threads[iThread].cPending < g_aTest2Threads[iThread].cMaxPending)
146 {
147 PTEST2ITEM pMyItem = (PTEST2ITEM)PDMQueueAlloc(pVM, hQueue, pVM);
148 if (pMyItem)
149 {
150 pMyItem->iSeqNo = ++iSendSeqNo;
151 pMyItem->iThreadNo = (uint32_t)iThread;
152 RTTEST_CHECK_RC(g_hTest, PDMQueueInsert(pVM, hQueue, pVM, &pMyItem->Core), VINF_SUCCESS);
153 ASMAtomicIncU32(&g_aTest2Threads[iThread].cPending);
154 }
155 else
156 {
157 RTTestFailed(g_hTest, "iThread=%u: PDMQueueAlloc failed: cPending=%u cMaxPending=%u iSendSeqNo=%u",
158 iThread, g_aTest2Threads[iThread].cPending, g_aTest2Threads[iThread].cMaxPending, iSendSeqNo);
159 ASMAtomicWriteBool(&g_fTest2Terminate, true);
160 break;
161 }
162
163 cSpinLoops = 0;
164 }
165 else if (cSpinLoops++ < 1024)
166 ASMNopPause();
167 else
168 {
169 RTThreadYield();
170 cSpinLoops = 0;
171 }
172 }
173
174 ASMAtomicDecU32(&g_cTest2Threads);
175
176 RT_NOREF(hThreadSelf);
177 return VINF_SUCCESS;
178}
179
180
181/**
182 * @callback_method_impl{FNRTTHREAD, Control thread}
183 */
184static DECLCALLBACK(int) Test2ControlThread(RTTHREAD hThreadSelf, void *pvUser)
185{
186 RT_NOREF(hThreadSelf, pvUser);
187
188 RTThreadSleep(RT_MS_5SEC);
189 ASMAtomicWriteBool(&g_fTest2PushBack, true);
190
191 RTThreadSleep(RT_MS_30SEC);
192 ASMAtomicWriteBool(&g_fTest2Terminate, true);
193
194 ASMAtomicDecU32(&g_cTest2Threads);
195 return VINF_SUCCESS;
196}
197
198
199static DECLCALLBACK(int) Test2Emt(PVM pVM, PUVM pUVM)
200{
201 uint32_t cThreads = 2;
202 RTTestSubF(g_hTest, "%u Threads", cThreads);
203 RTTEST_CHECK_RET(g_hTest, cThreads < RT_ELEMENTS(g_aTest2Threads) /* last entry is control thread*/, VERR_OUT_OF_RANGE);
204
205 PDMQUEUEHANDLE hQueue;
206 RTTEST_CHECK_RC_RET(g_hTest, PDMR3QueueCreateExternal(pVM, sizeof(TEST2ITEM), cThreads * 128 + 16, 0 /*cMilliesInterval*/,
207 Test2ConsumerCallback, pVM /*pvUser*/, "Test2", &hQueue),
208 VINF_SUCCESS, VINF_SUCCESS);
209
210 /* Init the thread data: */
211 g_fTest2Terminate = false;
212 g_pTest2VM = pVM;
213 g_hTest2Queue = hQueue;
214 g_fTest2PushBack = false;
215 g_cTest2Received = 0;
216 for (uint32_t i = 0; i < cThreads; i++)
217 {
218 g_aTest2Threads[i].hThread = NIL_RTTHREAD;
219 g_aTest2Threads[i].iThreadNo = i;
220 g_aTest2Threads[i].cMaxPending = 64 + i % 16;
221 g_aTest2Threads[i].cPending = 0;
222 g_aTest2Threads[i].iReceiveSeqNo = 0;
223 }
224
225 /* Start the threads: */
226 for (uint32_t i = 0; i < cThreads; i++)
227 {
228 RTTEST_CHECK_RC_BREAK(g_hTest, RTThreadCreateF(&g_aTest2Threads[i].hThread, Test2Thread, (void *)(uintptr_t)i, 0,
229 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "test2-t%u", i),
230 VINF_SUCCESS);
231 ASMAtomicIncU32(&g_cTest2Threads);
232 }
233
234 int rc;
235 RTTEST_CHECK_RC(g_hTest, rc = RTThreadCreate(&g_aTest2Threads[cThreads].hThread, Test2ControlThread, NULL, 0,
236 RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "test2-ctl"),
237 VINF_SUCCESS);
238 if (RT_SUCCESS(rc))
239 ASMAtomicIncU32(&g_cTest2Threads);
240
241 /* Process the queue till all threads have quit or termination is triggered: */
242 while ( ASMAtomicUoReadU32(&g_cTest2Threads) != 0
243 && !g_fTest2Terminate)
244 {
245 PDMR3QueueFlushAll(pVM);
246 }
247
248 /* Wait for the threads. */
249 ASMAtomicWriteBool(&g_fTest2Terminate, true);
250 for (uint32_t i = 0; i <= cThreads; i++)
251 {
252 if (g_aTest2Threads[i].hThread != NIL_RTTHREAD)
253 {
254 int rcThread = VERR_GENERAL_FAILURE;
255 RTTEST_CHECK_RC(g_hTest, RTThreadWait(g_aTest2Threads[i].hThread, RT_MS_30SEC, &rcThread), VINF_SUCCESS);
256 RTTEST_CHECK_RC(g_hTest, rcThread, VINF_SUCCESS);
257 }
258 }
259
260 STAMR3Print(pUVM, "/PDM/Queue/Test2/*");
261
262 /* Cleanup: */
263 RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, hQueue, pVM), VINF_SUCCESS);
264 RTTestSubDone(g_hTest);
265 return VINF_SUCCESS;
266}
267
268
269/*********************************************************************************************************************************
270* Test #1 - Basics *
271*********************************************************************************************************************************/
272static uint32_t volatile g_cTest1Callbacks = 0;
273static int32_t volatile g_cTest1Pushback = INT32_MAX;
274
275typedef struct TEST1ITEM
276{
277 PDMQUEUEITEMCORE Core;
278 uint32_t iSeqNo;
279} TEST1ITEM;
280typedef TEST1ITEM *PTEST1ITEM;
281
282
283/** @callback_method_impl{FNPDMQUEUEEXT} */
284static DECLCALLBACK(bool) Test1ConsumerCallback(void *pvUser, PPDMQUEUEITEMCORE pItem)
285{
286 if (ASMAtomicDecS32(&g_cTest1Pushback) < 0)
287 return false;
288
289 PTEST1ITEM pMyItem = (PTEST1ITEM)pItem;
290 uint32_t iCallbackNo = ASMAtomicIncU32(&g_cTest1Callbacks);
291 if (pMyItem->iSeqNo != iCallbackNo)
292 RTTestFailed(g_hTest, "iSeqNo=%#x, expected %#x\n", pMyItem->iSeqNo, iCallbackNo);
293
294 RT_NOREF(pvUser);
295 return true;
296}
297
298
299static DECLCALLBACK(int) Test1Emt(PVM pVM)
300{
301 RTTestSub(g_hTest, "Basics");
302
303 PDMQUEUEHANDLE hQueue;
304 RTTEST_CHECK_RC_RET(g_hTest, PDMR3QueueCreateExternal(pVM, sizeof(TEST1ITEM), 16, 0 /*cMilliesInterval*/,
305 Test1ConsumerCallback, pVM /*pvUser*/, "Test1", &hQueue),
306 VINF_SUCCESS, VINF_SUCCESS);
307
308 PDMQUEUEHANDLE const hQueueFirst = hQueue; /* Save the handle value so we can check that it's correctly reused. */
309
310 /*
311 * Single item:
312 */
313 PTEST1ITEM pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM);
314 RTTEST_CHECK(g_hTest, pMyItem);
315 pMyItem->iSeqNo = 1;
316 RTTEST_CHECK_RC(g_hTest, PDMQueueInsert(pVM, hQueue, pVM, &pMyItem->Core), VINF_SUCCESS);
317
318 PDMR3QueueFlushAll(pVM);
319 RTTEST_CHECK(g_hTest, g_cTest1Callbacks == 1);
320
321 /*
322 * All items:
323 */
324 for (uint32_t i = 0; i < 16; i++)
325 {
326 pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM);
327 RTTEST_CHECK_BREAK(g_hTest, pMyItem);
328 pMyItem->iSeqNo = i + 2;
329 RTTEST_CHECK_RC(g_hTest, PDMQueueInsert(pVM, hQueue, pVM, &pMyItem->Core), VINF_SUCCESS);
330 }
331
332 pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM);
333 RTTEST_CHECK(g_hTest, pMyItem == NULL);
334
335 PDMR3QueueFlushAll(pVM);
336 RTTEST_CHECK(g_hTest, g_cTest1Callbacks == 17);
337
338 /*
339 * Push back.
340 * 1. First queue all items.
341 * 2. Process half of them.
342 * 3. The process one by one.
343 */
344 g_cTest1Callbacks = 0;
345 g_cTest1Pushback = 8;
346
347 for (uint32_t i = 0; i < 16; i++)
348 {
349 pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM);
350 RTTEST_CHECK_BREAK(g_hTest, pMyItem);
351 pMyItem->iSeqNo = i + 1;
352 RTTEST_CHECK_RC(g_hTest, PDMQueueInsert(pVM, hQueue, pVM, &pMyItem->Core), VINF_SUCCESS);
353 }
354
355 pMyItem = (PTEST1ITEM)PDMQueueAlloc(pVM, hQueue, pVM);
356 RTTEST_CHECK(g_hTest, pMyItem == NULL);
357
358 PDMR3QueueFlushAll(pVM);
359 RTTEST_CHECK(g_hTest, g_cTest1Callbacks == 8);
360
361 for (uint32_t i = 0; i < 8; i++)
362 {
363 g_cTest1Pushback = 1;
364 PDMR3QueueFlushAll(pVM);
365 RTTEST_CHECK(g_hTest, g_cTest1Callbacks == 8 + 1 + i);
366 }
367
368 /*
369 * Cleanup.
370 */
371 RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, hQueue, pVM), VINF_SUCCESS);
372
373 /*
374 * Do some creation/deletion ordering checks.
375 */
376 RTTestSub(g_hTest, "Cleanup & handle reuse");
377 PDMQUEUEHANDLE ahQueues[168];
378 for (size_t i = 0; i < RT_ELEMENTS(ahQueues); i++)
379 ahQueues[i] = NIL_PDMQUEUEHANDLE;
380 for (uint32_t i = 0; i < RT_ELEMENTS(ahQueues); i++)
381 {
382 char szQueueNm[32];
383 RTStrPrintf(szQueueNm, sizeof(szQueueNm), "Test1b-%u", i);
384 RTTEST_CHECK_RC(g_hTest, PDMR3QueueCreateExternal(pVM, sizeof(TEST1ITEM), i + 1, 0 /*cMilliesInterval*/,
385 Test1ConsumerCallback, pVM /*pvUser*/, szQueueNm, &ahQueues[i]),
386 VINF_SUCCESS);
387 if (i == 0 && ahQueues[0] != hQueueFirst)
388 RTTestFailed(g_hTest, "Queue handle value not reused: %#RX64, expected %#RX64", ahQueues[0], hQueueFirst);
389 }
390
391 /* Delete them in random order. */
392 for (uint32_t i = 0; i < RT_ELEMENTS(ahQueues); i++)
393 {
394 uint32_t iDelete = RTRandU32Ex(0, RT_ELEMENTS(ahQueues) - 1);
395 if (ahQueues[iDelete] != NIL_PDMQUEUEHANDLE)
396 {
397 RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, ahQueues[iDelete], pVM), VINF_SUCCESS);
398 ahQueues[iDelete] = NIL_PDMQUEUEHANDLE;
399 }
400 }
401
402 /* Delete remainder in ascending order, creating a array shrinking at the end. */
403 for (uint32_t i = 0; i < RT_ELEMENTS(ahQueues); i++)
404 if (ahQueues[i] != NIL_PDMQUEUEHANDLE)
405 {
406 RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, ahQueues[i], pVM), VINF_SUCCESS);
407 ahQueues[i] = NIL_PDMQUEUEHANDLE;
408 }
409
410 /* Create one more queue and check that we get the first queue handle again. */
411 RTTEST_CHECK_RC(g_hTest, PDMR3QueueCreateExternal(pVM, sizeof(TEST1ITEM), 1, 0 /*cMilliesInterval*/,
412 Test1ConsumerCallback, pVM /*pvUser*/, "Test1c", &hQueue), VINF_SUCCESS);
413 if (hQueue != hQueueFirst)
414 RTTestFailed(g_hTest, "Queue handle value not reused: %#RX64, expected %#RX64", hQueue, hQueueFirst);
415 RTTEST_CHECK_RC(g_hTest, PDMR3QueueDestroy(pVM, hQueue, pVM), VINF_SUCCESS);
416
417 RTTestSubDone(g_hTest);
418 return VINF_SUCCESS;
419}
420
421
422static void DoTests(void)
423{
424 PVM pVM;
425 PUVM pUVM;
426 RTTESTI_CHECK_RC_OK_RETV(VMR3Create(1, NULL, NULL, NULL, NULL, NULL, &pVM, &pUVM));
427
428 /*
429 * Do the tests.
430 */
431 RTTESTI_CHECK_RC(VMR3ReqCallWaitU(pUVM, 0, (PFNRT)Test1Emt, 1, pVM), VINF_SUCCESS);
432 if (RTTestErrorCount(g_hTest) == 0)
433 {
434 RTTESTI_CHECK_RC(VMR3ReqCallWaitU(pUVM, 0, (PFNRT)Test2Emt, 2, pVM, pUVM), VINF_SUCCESS);
435 }
436
437 /*
438 * Clean up.
439 */
440 RTTESTI_CHECK_RC_OK_RETV(VMR3PowerOff(pUVM));
441 RTTESTI_CHECK_RC_OK_RETV(VMR3Destroy(pUVM));
442 VMR3ReleaseUVM(pUVM);
443}
444
445
446int main(int argc, char **argv)
447{
448 /*
449 * We run the VMM in driverless mode to avoid needing to hardened the testcase
450 */
451 RTEXITCODE rcExit;
452 int rc = RTR3InitExe(argc, &argv, SUPR3INIT_F_DRIVERLESS << RTR3INIT_FLAGS_SUPLIB_SHIFT);
453 if (RT_SUCCESS(rc))
454 {
455 rc = RTTestCreate("tstPDMQueue", &g_hTest);
456 if (RT_SUCCESS(rc))
457 {
458 RTTestBanner(g_hTest);
459 DoTests();
460 rcExit = RTTestSummaryAndDestroy(g_hTest);
461 }
462 else
463 rcExit = RTMsgErrorExitFailure("RTTestCreate failed: %Rrc", rc);
464 }
465 else
466 rcExit = RTMsgInitFailure(rc);
467 return rcExit;
468}
469
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette