summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/testcase/tstRTCritSect.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/testcase/tstRTCritSect.cpp')
-rw-r--r--src/VBox/Runtime/testcase/tstRTCritSect.cpp543
1 files changed, 543 insertions, 0 deletions
diff --git a/src/VBox/Runtime/testcase/tstRTCritSect.cpp b/src/VBox/Runtime/testcase/tstRTCritSect.cpp
new file mode 100644
index 00000000..b0bc8184
--- /dev/null
+++ b/src/VBox/Runtime/testcase/tstRTCritSect.cpp
@@ -0,0 +1,543 @@
+/* $Id: tstRTCritSect.cpp $ */
+/** @file
+ * IPRT Testcase - Critical Sections.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef TRY_WIN32_CRIT
+# include <iprt/win/windows.h>
+#endif
+#define RTCRITSECT_WITHOUT_REMAPPING
+#include <iprt/critsect.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/initterm.h>
+#include <iprt/getopt.h>
+#include <iprt/cpp/lock.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+
+
+#ifndef TRY_WIN32_CRIT
+# define LOCKERS(sect) ((sect).cLockers)
+#else /* TRY_WIN32_CRIT */
+
+/* This is for comparing with the "real thing". */
+#define RTCRITSECT CRITICAL_SECTION
+#define PRTCRITSECT LPCRITICAL_SECTION
+#define LOCKERS(sect) (*(LONG volatile *)&(sect).LockCount)
+
+DECLINLINE(int) RTCritSectInit(PCRITICAL_SECTION pCritSect)
+{
+ InitializeCriticalSection(pCritSect);
+ return VINF_SUCCESS;
+}
+
+DECLINLINE(int) RTCritSectEnter(PCRITICAL_SECTION pCritSect)
+{
+ EnterCriticalSection(pCritSect);
+ return VINF_SUCCESS;
+}
+
+DECLINLINE(int) RTCritSectLeave(PCRITICAL_SECTION pCritSect)
+{
+ LeaveCriticalSection(pCritSect);
+ return VINF_SUCCESS;
+}
+
+DECLINLINE(int) RTCritSectDelete(PCRITICAL_SECTION pCritSect)
+{
+ DeleteCriticalSection(pCritSect);
+ return VINF_SUCCESS;
+}
+
+#endif /* TRY_WIN32_CRIT */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Arguments to ThreadTest1().
+ */
+typedef struct THREADTEST1ARGS
+{
+ /** The critical section. */
+ PRTCRITSECT pCritSect;
+ /** The thread ordinal. */
+ uint32_t iThread;
+ /** Pointer to the release counter. */
+ uint32_t volatile *pu32Release;
+} THREADTEST1ARGS, *PTHREADTEST1ARGS;
+
+
+/**
+ * Arguments to ThreadTest2().
+ */
+typedef struct THREADTEST2ARGS
+{
+ /** The critical section. */
+ PRTCRITSECT pCritSect;
+ /** The thread ordinal. */
+ uint32_t iThread;
+ /** Pointer to the release counter. */
+ uint32_t volatile *pu32Release;
+ /** Pointer to the alone indicator. */
+ uint32_t volatile *pu32Alone;
+ /** Pointer to the previous thread variable. */
+ uint32_t volatile *pu32Prev;
+ /** Pointer to the sequential enters counter. */
+ uint32_t volatile *pcSeq;
+ /** Pointer to the reordered enters counter. */
+ uint32_t volatile *pcReordered;
+ /** Pointer to the variable counting running threads. */
+ uint32_t volatile *pcThreadRunning;
+ /** Number of times this thread was inside the section. */
+ uint32_t volatile cTimes;
+ /** The number of threads. */
+ uint32_t cThreads;
+ /** Number of iterations (sum of all threads). */
+ uint32_t cIterations;
+ /** Yield while inside the section. */
+ unsigned cCheckLoops;
+ /** Signal this when done. */
+ RTSEMEVENT EventDone;
+} THREADTEST2ARGS, *PTHREADTEST2ARGS;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The test handle. */
+static RTTEST g_hTest;
+
+
+
+/**
+ * Thread which goes to sleep on the critsect and checks that it's released in the right order.
+ */
+static DECLCALLBACK(int) ThreadTest1(RTTHREAD ThreadSelf, void *pvArgs)
+{
+ RT_NOREF1(ThreadSelf);
+ THREADTEST1ARGS Args = *(PTHREADTEST1ARGS)pvArgs;
+ Log2(("ThreadTest1: Start - iThread=%d ThreadSelf=%p\n", Args.iThread, ThreadSelf));
+ RTMemFree(pvArgs);
+
+ /*
+ * Enter it.
+ */
+ int rc = RTCritSectEnter(Args.pCritSect);
+ if (RT_FAILURE(rc))
+ {
+ RTTestFailed(g_hTest, "thread %d: RTCritSectEnter -> %Rrc", Args.iThread, rc);
+ return 1;
+ }
+
+ /*
+ * Check release order.
+ */
+ if (*Args.pu32Release != Args.iThread)
+ RTTestFailed(g_hTest, "thread %d: released as number %d", Args.iThread, *Args.pu32Release);
+ ASMAtomicIncU32(Args.pu32Release);
+
+ /*
+ * Leave it.
+ */
+ rc = RTCritSectLeave(Args.pCritSect);
+ if (RT_FAILURE(rc))
+ {
+ RTTestFailed(g_hTest, "thread %d: RTCritSectEnter -> %Rrc", Args.iThread, rc);
+ return 1;
+ }
+
+ Log2(("ThreadTest1: End - iThread=%d ThreadSelf=%p\n", Args.iThread, ThreadSelf));
+ return 0;
+}
+
+
+static int Test1(unsigned cThreads)
+{
+ RTTestSubF(g_hTest, "Test #1 with %u thread", cThreads);
+
+ /*
+ * Create a critical section.
+ */
+ RTCRITSECT CritSect;
+ RTTEST_CHECK_RC_RET(g_hTest, RTCritSectInit(&CritSect), VINF_SUCCESS, 1);
+
+ /*
+ * Enter, leave and enter again.
+ */
+ RTTEST_CHECK_RC_RET(g_hTest, RTCritSectEnter(&CritSect), VINF_SUCCESS, 1);
+ RTTEST_CHECK_RC_RET(g_hTest, RTCritSectLeave(&CritSect), VINF_SUCCESS, 1);
+ RTTEST_CHECK_RC_RET(g_hTest, RTCritSectEnter(&CritSect), VINF_SUCCESS, 1);
+
+ /*
+ * Now spawn threads which will go to sleep entering the critsect.
+ */
+ uint32_t u32Release = 0;
+ for (uint32_t iThread = 0; iThread < cThreads; iThread++)
+ {
+ PTHREADTEST1ARGS pArgs = (PTHREADTEST1ARGS)RTMemAllocZ(sizeof(*pArgs));
+ pArgs->iThread = iThread;
+ pArgs->pCritSect = &CritSect;
+ pArgs->pu32Release = &u32Release;
+ int32_t iLock = LOCKERS(CritSect);
+ RTTHREAD Thread;
+ RTTEST_CHECK_RC_RET(g_hTest, RTThreadCreateF(&Thread, ThreadTest1, pArgs, 0, RTTHREADTYPE_DEFAULT, 0, "T%d", iThread), VINF_SUCCESS, 1);
+
+ /* wait for it to get into waiting. */
+ while (LOCKERS(CritSect) == iLock)
+ RTThreadSleep(10);
+ RTThreadSleep(20);
+ }
+
+ /*
+ * Now we'll release the threads and wait for all of them to quit.
+ */
+ u32Release = 0;
+ RTTEST_CHECK_RC_RET(g_hTest, RTCritSectLeave(&CritSect), VINF_SUCCESS, 1);
+ while (u32Release < cThreads)
+ RTThreadSleep(10);
+
+ RTTEST_CHECK_RC_RET(g_hTest, RTCritSectDelete(&CritSect), VINF_SUCCESS, 1);
+ return 0;
+}
+
+
+
+/**
+ * Thread which goes to sleep on the critsect and checks
+ * that it's released along and in the right order. This is done a number of times.
+ *
+ */
+static DECLCALLBACK(int) ThreadTest2(RTTHREAD ThreadSelf, void *pvArg)
+{
+ RT_NOREF1(ThreadSelf);
+ PTHREADTEST2ARGS pArgs = (PTHREADTEST2ARGS)pvArg;
+ Log2(("ThreadTest2: Start - iThread=%d ThreadSelf=%p\n", pArgs->iThread, ThreadSelf));
+ uint64_t u64TSStart = 0;
+ ASMAtomicIncU32(pArgs->pcThreadRunning);
+
+ for (unsigned i = 0; *pArgs->pu32Release < pArgs->cIterations; i++)
+ {
+ /*
+ * Enter it.
+ */
+ int rc = RTCritSectEnter(pArgs->pCritSect);
+ if (RT_FAILURE(rc))
+ {
+ RTTestFailed(g_hTest, "thread %d, iteration %d: RTCritSectEnter -> %d", pArgs->iThread, i, rc);
+ return 1;
+ }
+ if (!u64TSStart)
+ u64TSStart = RTTimeNanoTS();
+
+#if 0 /* We just check for sequences. */
+ /*
+ * Check release order.
+ */
+ if ((*pArgs->pu32Release % pArgs->cThreads) != pArgs->iThread)
+ RTTestFailed(g_hTest, "thread %d, iteration %d: released as number %d (%d)",
+ pArgs->iThread, i, *pArgs->pu32Release % pArgs->cThreads, *pArgs->pu32Release);
+ else
+ RTTestPrintf(g_hTest, RTTESTLVL_INFO, "iteration %d: released as number %d (%d)\n",
+ pArgs->iThread, i, *pArgs->pu32Release % pArgs->cThreads, *pArgs->pu32Release);
+#endif
+ pArgs->cTimes++;
+ ASMAtomicIncU32(pArgs->pu32Release);
+
+ /*
+ * Check distribution every now and again.
+ */
+#if 0
+ if (!(*pArgs->pu32Release % 879))
+ {
+ uint32_t u32Perfect = *pArgs->pu32Release / pArgs->cThreads;
+ for (int iThread = 0 ; iThread < (int)pArgs->cThreads; iThread++)
+ {
+ int cDiff = pArgs[iThread - pArgs->iThread].cTimes - u32Perfect;
+ if ((unsigned)RT_ABS(cDiff) > RT_MAX(u32Perfect / 10000, 2))
+ {
+ printf("tstCritSect: FAILURE - bad distribution thread %d u32Perfect=%d cTimes=%d cDiff=%d (runtime)\n",
+ iThread, u32Perfect, pArgs[iThread - pArgs->iThread].cTimes, cDiff);
+ ASMAtomicIncU32(&g_cErrors);
+ }
+ }
+ }
+#endif
+ /*
+ * Check alone and make sure we stay inside here a while
+ * so the other guys can get ready.
+ */
+ uint32_t u32;
+ for (u32 = 0; u32 < pArgs->cCheckLoops; u32++)
+ {
+ if (*pArgs->pu32Alone != ~0U)
+ {
+ RTTestFailed(g_hTest, "thread %d, iteration %d: not alone!!!", pArgs->iThread, i);
+ //AssertReleaseMsgFailed(("Not alone!\n"));
+ return 1;
+ }
+ }
+ ASMAtomicCmpXchgU32(pArgs->pu32Alone, pArgs->iThread, UINT32_MAX);
+ for (u32 = 0; u32 < pArgs->cCheckLoops; u32++)
+ {
+ if (*pArgs->pu32Alone != pArgs->iThread)
+ {
+ RTTestFailed(g_hTest, "thread %d, iteration %d: not alone!!!", pArgs->iThread, i);
+ //AssertReleaseMsgFailed(("Not alone!\n"));
+ return 1;
+ }
+ }
+ ASMAtomicXchgU32(pArgs->pu32Alone, UINT32_MAX);
+
+ /*
+ * Check for sequences.
+ */
+ if (*pArgs->pu32Prev == pArgs->iThread && pArgs->cThreads > 1)
+ ASMAtomicIncU32(pArgs->pcSeq);
+ else if ((*pArgs->pu32Prev + 1) % pArgs->cThreads != pArgs->iThread)
+ ASMAtomicIncU32(pArgs->pcReordered);
+ ASMAtomicXchgU32(pArgs->pu32Prev, pArgs->iThread);
+
+ /*
+ * Leave it.
+ */
+ rc = RTCritSectLeave(pArgs->pCritSect);
+ if (RT_FAILURE(rc))
+ {
+ RTTestFailed(g_hTest, "thread %d, iteration %d: RTCritSectEnter -> %d", pArgs->iThread, i, rc);
+ return 1;
+ }
+ }
+
+ uint64_t u64TSEnd = RTTimeNanoTS(); NOREF(u64TSEnd);
+ ASMAtomicDecU32(pArgs->pcThreadRunning);
+ RTSemEventSignal(pArgs->EventDone);
+ Log2(("ThreadTest2: End - iThread=%d ThreadSelf=%p time=%lld\n", pArgs->iThread, ThreadSelf, u64TSEnd - u64TSStart));
+ return 0;
+}
+
+static int Test2(unsigned cThreads, unsigned cIterations, unsigned cCheckLoops)
+{
+ RTTestSubF(g_hTest, "Test #2 - cThreads=%u cIterations=%u cCheckLoops=%u", cThreads, cIterations, cCheckLoops);
+
+ /*
+ * Create a critical section.
+ */
+ RTCRITSECT CritSect;
+ int rc;
+ RTTEST_CHECK_RC_RET(g_hTest, RTCritSectInit(&CritSect), VINF_SUCCESS, 1);
+
+ /*
+ * Enter, leave and enter again.
+ */
+ RTTEST_CHECK_RC_RET(g_hTest, RTCritSectEnter(&CritSect), VINF_SUCCESS, 1);
+ RTTEST_CHECK_RC_RET(g_hTest, RTCritSectLeave(&CritSect), VINF_SUCCESS, 1);
+ RTTEST_CHECK_RC_RET(g_hTest, RTCritSectEnter(&CritSect), VINF_SUCCESS, 1);
+
+ /*
+ * Now spawn threads which will go to sleep entering the critsect.
+ */
+ PTHREADTEST2ARGS paArgs = (PTHREADTEST2ARGS)RTMemAllocZ(sizeof(THREADTEST2ARGS) * cThreads);
+ RTSEMEVENT EventDone;
+ RTTEST_CHECK_RC_RET(g_hTest, RTSemEventCreate(&EventDone), VINF_SUCCESS, 1);
+ uint32_t volatile u32Release = 0;
+ uint32_t volatile u32Alone = UINT32_MAX;
+ uint32_t volatile u32Prev = UINT32_MAX;
+ uint32_t volatile cSeq = 0;
+ uint32_t volatile cReordered = 0;
+ uint32_t volatile cThreadRunning = 0;
+ unsigned iThread;
+ for (iThread = 0; iThread < cThreads; iThread++)
+ {
+ paArgs[iThread].iThread = iThread;
+ paArgs[iThread].pCritSect = &CritSect;
+ paArgs[iThread].pu32Release = &u32Release;
+ paArgs[iThread].pu32Alone = &u32Alone;
+ paArgs[iThread].pu32Prev = &u32Prev;
+ paArgs[iThread].pcSeq = &cSeq;
+ paArgs[iThread].pcReordered = &cReordered;
+ paArgs[iThread].pcThreadRunning = &cThreadRunning;
+ paArgs[iThread].cTimes = 0;
+ paArgs[iThread].cThreads = cThreads;
+ paArgs[iThread].cIterations = cIterations;
+ paArgs[iThread].cCheckLoops = cCheckLoops;
+ paArgs[iThread].EventDone = EventDone;
+ int32_t iLock = LOCKERS(CritSect);
+ char szThread[17];
+ RTStrPrintf(szThread, sizeof(szThread), "T%d", iThread);
+ RTTHREAD Thread;
+ rc = RTThreadCreate(&Thread, ThreadTest2, &paArgs[iThread], 0, RTTHREADTYPE_DEFAULT, 0, szThread);
+ if (RT_FAILURE(rc))
+ {
+ RTTestFailed(g_hTest, "RTThreadCreate -> %d", rc);
+ return 1;
+ }
+ /* wait for it to get into waiting. */
+ while (LOCKERS(CritSect) == iLock)
+ RTThreadSleep(10);
+ RTThreadSleep(20);
+ }
+ RTTestPrintf(g_hTest, RTTESTLVL_INFO, "threads created...\n");
+
+ /*
+ * Now we'll release the threads and wait for all of them to quit.
+ */
+ u32Release = 0;
+ uint64_t u64TSStart = RTTimeNanoTS();
+ RTTEST_CHECK_RC_RET(g_hTest, RTCritSectLeave(&CritSect), VINF_SUCCESS, 1);
+
+ while (cThreadRunning > 0)
+ RTSemEventWait(EventDone, RT_INDEFINITE_WAIT);
+ uint64_t u64TSEnd = RTTimeNanoTS();
+
+ /*
+ * Clean up and report results.
+ */
+ RTTEST_CHECK_RC(g_hTest, RTCritSectDelete(&CritSect), VINF_SUCCESS);
+
+ /* sequences */
+ if (cSeq > RT_MAX(u32Release / 10000, 1))
+ RTTestFailed(g_hTest, "too many same thread sequences! cSeq=%d\n", cSeq);
+
+ /* distribution caused by sequences / reordering. */
+ unsigned cDiffTotal = 0;
+ uint32_t u32Perfect = (u32Release + cThreads / 2) / cThreads;
+ for (iThread = 0; iThread < cThreads; iThread++)
+ {
+ int cDiff = paArgs[iThread].cTimes - u32Perfect;
+ if ((unsigned)RT_ABS(cDiff) > RT_MAX(u32Perfect / 10000, 2))
+ RTTestFailed(g_hTest, "bad distribution thread %d u32Perfect=%d cTimes=%d cDiff=%d\n",
+ iThread, u32Perfect, paArgs[iThread].cTimes, cDiff);
+ cDiffTotal += RT_ABS(cDiff);
+ }
+
+ uint32_t cMillies = (uint32_t)((u64TSEnd - u64TSStart) / 1000000);
+ RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS,
+ "%d enter+leave in %dms cSeq=%d cReordered=%d cDiffTotal=%d\n",
+ u32Release, cMillies, cSeq, cReordered, cDiffTotal);
+ return 0;
+}
+
+
+int main(int argc, char **argv)
+{
+ RTTEST hTest;
+#ifndef TRY_WIN32_CRT
+ int rc = RTTestInitAndCreate("tstRTCritSect", &hTest);
+#else
+ int rc = RTTestInitAndCreate("tstRTCritSectW32", &hTest);
+#endif
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+ g_hTest = hTest;
+
+ /* parse args. */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--distribution", 'd', RTGETOPT_REQ_NOTHING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING }
+ };
+
+ bool fTestDistribution = false;
+
+ int ch;
+ RTGETOPTUNION ValueUnion;
+ RTGETOPTSTATE GetState;
+ RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0);
+ while ((ch = RTGetOpt(&GetState, &ValueUnion)))
+ {
+ switch (ch)
+ {
+ case 'd':
+ fTestDistribution = true;
+ break;
+
+ case 'h':
+ RTTestIPrintf(RTTESTLVL_ALWAYS, "%s [--help|-h] [--distribution|-d]\n", argv[0]);
+ return 1;
+
+ case 'V':
+ RTPrintf("$Revision: 155244 $\n");
+ return 0;
+
+ default:
+ return RTGetOptPrintError(ch, &ValueUnion);
+ }
+ }
+
+
+ /*
+ * Perform the testing.
+ */
+ if ( !Test1(1)
+ && !Test1(3)
+ && !Test1(10)
+ && !Test1(63))
+ {
+
+ if ( fTestDistribution
+ && !Test2(1, 200000, 1000)
+ && !Test2(2, 200000, 1000)
+ && !Test2(3, 200000, 1000)
+ && !Test2(4, 200000, 1000)
+ && !Test2(5, 200000, 1000)
+ && !Test2(7, 200000, 1000)
+ && !Test2(67, 200000, 1000))
+ {
+ /*nothing*/;
+ }
+ }
+
+ /*
+ * Summary.
+ */
+ return RTTestSummaryAndDestroy(hTest);
+}
+