VirtualBox

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

Last change on this file since 95421 was 93609, checked in by vboxsync, 3 years ago

VMM/PDMQueue: Rewrote the queue code to not use the hyper heap and be a bit safer. Added a testcase (driverless). bugref:10093

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