Files
libreoffice/vcl/qa/cppunit/timer.cxx

369 lines
9.4 KiB
C++
Raw Normal View History

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/*
* Timers are evil beasties across platforms ...
*/
#include <test/bootstrapfixture.hxx>
#include <osl/thread.hxx>
#include <salhelper/thread.hxx>
#include <vcl/timer.hxx>
#include <vcl/idle.hxx>
#include <vcl/svapp.hxx>
#include "svdata.hxx"
#include "salinst.hxx"
// #define TEST_WATCHDOG
// Comment if UT fails randomly.
#define TEST_TIMERPRECISION
/// Avoid our timer tests just wedging the build if they fail.
class WatchDog : public osl::Thread
{
sal_Int32 mnSeconds;
public:
explicit WatchDog(sal_Int32 nSeconds) :
Thread(),
mnSeconds( nSeconds )
{
create();
}
virtual void SAL_CALL run() SAL_OVERRIDE
{
TimeValue aWait;
aWait.Seconds = mnSeconds;
aWait.Nanosec = 1000000; // +1ms
osl::Thread::wait( aWait );
CPPUNIT_ASSERT_MESSAGE("watchdog triggered", false);
}
};
static WatchDog aWatchDog( 12 /* 12 secs should be enough */);
class TimerTest : public test::BootstrapFixture
{
public:
TimerTest() : BootstrapFixture(true, false) {}
void testIdleMainloop();
void testIdle();
#ifdef TEST_WATCHDOG
void testWatchdog();
#endif
void testDurations();
void testAutoTimer();
void testMultiAutoTimers();
void testRecursiveTimer();
void testSlowTimerCallback();
CPPUNIT_TEST_SUITE(TimerTest);
CPPUNIT_TEST(testIdle);
CPPUNIT_TEST(testIdleMainloop);
#ifdef TEST_WATCHDOG
CPPUNIT_TEST(testWatchdog);
#endif
CPPUNIT_TEST(testDurations);
CPPUNIT_TEST(testAutoTimer);
CPPUNIT_TEST(testMultiAutoTimers);
CPPUNIT_TEST(testRecursiveTimer);
CPPUNIT_TEST(testSlowTimerCallback);
CPPUNIT_TEST_SUITE_END();
};
#ifdef TEST_WATCHDOG
void TimerTest::testWatchdog()
{
// out-wait the watchdog.
TimeValue aWait;
aWait.Seconds = 12;
aWait.Nanosec = 0;
osl::Thread::wait( aWait );
}
#endif
// --------------------------------------------------------------------
class IdleBool : public Idle
{
bool &mrBool;
public:
explicit IdleBool( bool &rBool ) :
Idle(), mrBool( rBool )
{
SetPriority( SchedulerPriority::LOWEST );
Start();
mrBool = false;
}
virtual void Invoke() SAL_OVERRIDE
{
mrBool = true;
Application::EndYield();
}
};
void TimerTest::testIdle()
{
bool bTriggered = false;
IdleBool aTest( bTriggered );
Scheduler::ProcessTaskScheduling(false);
CPPUNIT_ASSERT_MESSAGE("idle triggered", bTriggered);
}
// tdf#91727
void TimerTest::testIdleMainloop()
{
#ifndef WNT
bool bTriggered = false;
IdleBool aTest( bTriggered );
// coverity[loop_top] - Application::Yield allows the timer to fire and toggle bDone
while (!bTriggered)
{
ImplSVData* pSVData = ImplGetSVData();
// can't test this via Application::Yield since this
// also processes all tasks directly via the scheduler.
pSVData->maAppData.mnDispatchLevel++;
vcl: fix Win32 deadlocks from SolarMutexReleaser To create and destroy thread-affine Win32 Windows and DCs, non-main threads SendMessage() special messages like SAL_MSG_CREATEFRAME. The main thread must handle these messages and return the result to un-block the other thread. This works fine as long as the main thread is in its message loop anyway and blocked on GetMessage(); however if the main blocks trying to acquire the SolarMutex that is held by the sending thread, deadlock results. In order to work around this, there is some peculiar code in ImplSalYieldMutexAcquireWithWait() to avoid blocking the main thread on mpSalYieldMutex but instead block in GetMessage(). The crucial detail is that GetMessage() will immediately dispatch any message sent via SendMessage(), which avoids the deadlock. https://msdn.microsoft.com/en-us/library/windows/desktop/ms644936.aspx https://msdn.microsoft.com/en-us/library/windows/desktop/ms644927.aspx Most of the Win32 WndProc that acquire SolarMutex do so via ImplSalYieldMutexAcquireWithWait(), but the main thread may also temporarily drop SolarMutex and re-aquire it with the questionable SolarMutexReleaser hack, which calls ImplSalAcquireYieldMutex() instead, which blocks on mpSalYieldMutex. Fix SolarMutexReleaser to call a new function Application::ReAcquireSolarMutex() that does the right thing here: acquire SolarMutex via ImplSalYieldMutexAcquireWithWait(). It turns out that this problem was already fixed before in commit 6a8fd4c76a969ac98d1aff91ff7442f43aee0006 but the problem was insufficiently documented in the commit and it introduced the bug that Application::Reschedule() was called without having the SolarMutex locked, which caused timers to run without SolarMutex, so the commit was reverted in 1ef1781390845d03b6e1518bbac81b818be62f3d. Change-Id: I60aae555a9ee3c6390f584feddbc6b3cb7de0250
2015-06-26 13:01:51 +02:00
pSVData->mpDefInst->DoYield(true, false, 0);
pSVData->maAppData.mnDispatchLevel--;
}
CPPUNIT_ASSERT_MESSAGE("mainloop idle triggered", bTriggered);
#endif
}
// --------------------------------------------------------------------
class TimerBool : public Timer
{
bool &mrBool;
public:
TimerBool( sal_uLong nMS, bool &rBool ) :
Timer(), mrBool( rBool )
{
SetTimeout( nMS );
Start();
mrBool = false;
}
virtual void Invoke() SAL_OVERRIDE
{
mrBool = true;
Application::EndYield();
}
};
void TimerTest::testDurations()
{
static const sal_uLong aDurations[] = { 0, 1, 500, 1000 };
for (size_t i = 0; i < SAL_N_ELEMENTS( aDurations ); i++)
{
bool bDone = false;
TimerBool aTimer( aDurations[i], bDone );
// coverity[loop_top] - Application::Yield allows the timer to fire and toggle bDone
while( !bDone )
{
Application::Yield();
}
}
}
// --------------------------------------------------------------------
class AutoTimerCount : public AutoTimer
{
sal_Int32 &mrCount;
public:
AutoTimerCount( sal_uLong nMS, sal_Int32 &rCount ) :
AutoTimer(), mrCount( rCount )
{
SetTimeout( nMS );
Start();
mrCount = 0;
}
virtual void Invoke() SAL_OVERRIDE
{
mrCount++;
}
};
void TimerTest::testAutoTimer()
{
const sal_Int32 nDurationMs = 30;
const sal_Int32 nEventsCount = 5;
const double exp = (nDurationMs * nEventsCount);
sal_Int32 nCount = 0;
double dur = 0;
std::ostringstream msg;
// Repeat when we have random latencies.
// This is expected on non-realtime OSes.
for (int i = 0; i < 10; ++i)
{
const auto start = std::chrono::high_resolution_clock::now();
nCount = 0;
AutoTimerCount aCount(nDurationMs, nCount);
while (nCount < nEventsCount) {
Application::Yield();
}
const auto end = std::chrono::high_resolution_clock::now();
dur = std::chrono::duration<double, std::milli>(end - start).count();
msg << std::setprecision(2) << std::fixed
<< "periodic multi-timer - dur: "
<< dur << " (" << exp << ") ms." << std::endl;
// +/- 20% should be reasonable enough a margin.
if (dur >= (exp * 0.8) && dur <= (exp * 1.2))
{
// Success.
return;
}
}
#ifdef TEST_TIMERPRECISION
CPPUNIT_FAIL(msg.str().c_str());
#endif
}
void TimerTest::testMultiAutoTimers()
{
// The behavior of the timers change drastically
// when multiple timers are present.
// The worst, in my tests, is when two
// timers with 1ms period exist with a
// third of much longer period.
const sal_Int32 nDurationMsX = 5;
const sal_Int32 nDurationMsY = 10;
const sal_Int32 nDurationMs = 40;
const sal_Int32 nEventsCount = 5;
const double exp = (nDurationMs * nEventsCount);
const double expX = (exp / nDurationMsX);
const double expY = (exp / nDurationMsY);
double dur = 0;
sal_Int32 nCountX = 0;
sal_Int32 nCountY = 0;
sal_Int32 nCount = 0;
std::ostringstream msg;
// Repeat when we have random latencies.
// This is expected on non-realtime OSes.
for (int i = 0; i < 10; ++i)
{
nCountX = 0;
nCountY = 0;
nCount = 0;
const auto start = std::chrono::high_resolution_clock::now();
AutoTimerCount aCountX(nDurationMsX, nCountX);
AutoTimerCount aCountY(nDurationMsY, nCountY);
AutoTimerCount aCount(nDurationMs, nCount);
while (nCount < nEventsCount) {
Application::Yield();
}
const auto end = std::chrono::high_resolution_clock::now();
dur = std::chrono::duration<double, std::milli>(end - start).count();
msg << std::setprecision(2) << std::fixed << "periodic multi-timer - dur: "
<< dur << " (" << exp << ") ms, nCount: " << nCount
<< " (" << nEventsCount << "), nCountX: " << nCountX
<< " (" << expX << "), nCountY: " << nCountY
<< " (" << expY << ")." << std::endl;
// +/- 20% should be reasonable enough a margin.
if (dur >= (exp * 0.8) && dur <= (exp * 1.2) &&
nCountX >= (expX * 0.8) && nCountX <= (expX * 1.2) &&
nCountY >= (expY * 0.8) && nCountY <= (expY * 1.2))
{
// Success.
return;
}
}
#ifdef TEST_TIMERPRECISION
CPPUNIT_FAIL(msg.str().c_str());
#endif
}
// --------------------------------------------------------------------
class YieldTimer : public Timer
{
public:
explicit YieldTimer( sal_uLong nMS ) : Timer()
{
SetTimeout( nMS );
Start();
}
virtual void Invoke() SAL_OVERRIDE
{
for (int i = 0; i < 100; i++)
Application::Yield();
}
};
void TimerTest::testRecursiveTimer()
{
sal_Int32 nCount = 0;
YieldTimer aCount(5);
AutoTimerCount aCountUp( 3, nCount );
// coverity[loop_top] - Application::Yield allows the timer to fire and increment nCount
while (nCount < 20)
Application::Yield();
}
// --------------------------------------------------------------------
class SlowCallbackTimer : public Timer
{
bool &mbSlow;
public:
SlowCallbackTimer( sal_uLong nMS, bool &bBeenSlow ) :
Timer(), mbSlow( bBeenSlow )
{
SetTimeout( nMS );
Start();
mbSlow = false;
}
virtual void Invoke() SAL_OVERRIDE
{
TimeValue aWait;
aWait.Seconds = 1;
aWait.Nanosec = 0;
osl::Thread::wait( aWait );
mbSlow = true;
}
};
void TimerTest::testSlowTimerCallback()
{
bool bBeenSlow = false;
sal_Int32 nCount = 0;
AutoTimerCount aHighFreq(1, nCount);
SlowCallbackTimer aSlow(250, bBeenSlow);
// coverity[loop_top] - Application::Yield allows the timer to fire and toggle bBeenSlow
while (!bBeenSlow)
Application::Yield();
// coverity[loop_top] - Application::Yield allows the timer to fire and increment nCount
while (nCount < 200)
Application::Yield();
}
CPPUNIT_TEST_SUITE_REGISTRATION(TimerTest);
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */