tstRTCritSect.cpp revision bd28966c9d976ffe4518dd2fdf3894dc16b814a1
/* $Id$ */
/** @file
* IPRT Testcase - Critical Sections.
*/
/*
* Copyright (C) 2006-2009 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE 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.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#ifdef TRY_WIN32_CRIT
# include <Windows.h>
#endif
#include <iprt/critsect.h>
#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/ctype.h>
#include <iprt/err.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/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;
}
#undef RTCritSectEnter
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)
{
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)
{
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, ~0);
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, ~0);
/*
* 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 = ~0;
uint32_t volatile u32Prev = ~0;
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 VINF_GETOPT_NOT_OPTION:
RTTestIFailed("%Rrs\n", ch);
return RTTestSummaryAndDestroy(hTest);
default:
if (ch > 0)
{
if (RT_C_IS_GRAPH(ch))
RTTestIFailed("unhandled option: -%c\n", ch);
else
RTTestIFailed("unhandled option: %i\n", ch);
}
else if (ch == VERR_GETOPT_UNKNOWN_OPTION)
RTTestIFailed("unknown option: %s\n", ValueUnion.psz);
else if (ValueUnion.pDef)
RTTestIFailed("%s: %Rrs\n", ValueUnion.pDef->pszLong, ch);
else
RTTestIFailed("%Rrs\n", ch);
return RTTestSummaryAndDestroy(hTest);
}
}
/*
* 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);
}