[Libreoffice-commits] core.git: Branch 'feature/cib_contract57c' - 2 commits - include/vcl sw/source vcl/CppunitTest_vcl_timer.mk vcl/qa vcl/README.scheduler vcl/source
Jan-Marek Glogowski (via logerrit)
logerrit at kemper.freedesktop.org
Wed Nov 11 08:14:56 UTC 2020
include/vcl/TaskStopwatch.hxx | 123 +++++++++++++++++++++++++++++++++++++++
sw/source/core/inc/layact.hxx | 28 +++-----
sw/source/core/layout/layact.cxx | 59 ++++++++----------
sw/source/core/view/viewsh.cxx | 1
vcl/CppunitTest_vcl_timer.mk | 1
vcl/README.scheduler | 61 +++++++++++++++++--
vcl/qa/cppunit/timer.cxx | 78 ++++++++++++++++++++++++
vcl/source/app/scheduler.cxx | 3
8 files changed, 297 insertions(+), 57 deletions(-)
New commits:
commit 911a15707f058fee50497702be11044638c76d13
Author: Jan-Marek Glogowski <jan-marek.glogowski at extern.cib.de>
AuthorDate: Tue Aug 6 17:09:51 2019 +0200
Commit: Samuel Mehrbrodt <Samuel.Mehrbrodt at cib.de>
CommitDate: Wed Nov 11 08:59:55 2020 +0100
tdf#123583 use TaskStopwatch for Writer Idle loop
I don't see much of a point in the extra CheckIdleEnd() function.
We already check IsInterrupt() almost everywhere, so move that
check in there.
An other strange thing is the Idle job, which should just be
interrupted by keyboard events (using SetInputType(, which this
patch removes). Unlucky for me this code was there in the initial
import. I can just say that othing obvious breaks...
Change-Id: Ia5955d1eaf2ab612f2c4b63b0e458ed92507b75c
Reviewed-on: https://gerrit.libreoffice.org/77040
Tested-by: Jenkins
Reviewed-by: Jan-Marek Glogowski <glogow at fbihome.de>
(cherry picked from commit 383032c50a3e3354f04200ce984a47ab9d2c5c67)
diff --git a/sw/source/core/inc/layact.hxx b/sw/source/core/inc/layact.hxx
index 9240ef9d2a98..7a843e60f0d1 100644
--- a/sw/source/core/inc/layact.hxx
+++ b/sw/source/core/inc/layact.hxx
@@ -21,6 +21,8 @@
#include <sal/config.h>
+#include <vcl/TaskStopwatch.hxx>
+
#include <ctime>
#include <swtypes.hxx>
@@ -52,6 +54,7 @@ class SwLayAction
{
SwRootFrame *m_pRoot;
SwViewShellImp *m_pImp; // here the action logs in and off
+ TaskStopwatch* m_pWatch;
// For the sake of optimization, so that the tables stick a bit better to
// the Cursor when hitting return/backspace in front of one.
@@ -70,7 +73,6 @@ class SwLayAction
std::clock_t m_nStartTicks; // The Action's starting time; if too much time passes the
// WaitCursor can be enabled via CheckWaitCursor()
- VclInputFlags m_nInputType; // Which input should terminate processing
sal_uInt16 m_nEndPage; // StatBar control
sal_uInt16 m_nCheckPageNum; // CheckPageDesc() was delayed if != USHRT_MAX
// check from this page onwards
@@ -80,9 +82,8 @@ class SwLayAction
bool m_bCalcLayout; // Complete reformatting?
bool m_bAgain; // For the automatically repeated Action if Pages are deleted
bool m_bNextCycle; // Reset on the first invalid Page
- bool m_bInput; // For terminating processing on input
- bool m_bIdle; // True if the LayAction was triggered by the Idler
bool m_bReschedule; // Call Reschedule depending on Progress?
+ bool m_bInterrupt; // For termination the layouting
bool m_bCheckPages; // Run CheckPageDescs() or delay it
bool m_bUpdateExpFields; // Is set if, after Formatting, we need to do another round for ExpField
bool m_bBrowseActionStop; // Terminate Action early (as per bInput) and leave the rest to the Idler
@@ -115,33 +116,26 @@ class SwLayAction
bool RemoveEmptyBrowserPages();
- inline void CheckIdleEnd();
-
public:
- SwLayAction( SwRootFrame *pRt, SwViewShellImp *pImp );
+ SwLayAction(SwRootFrame *pRt, SwViewShellImp *pImp, TaskStopwatch* pWatch = nullptr);
~SwLayAction();
- void SetIdle ( bool bNew ) { m_bIdle = bNew; }
void SetCheckPages ( bool bNew ) { m_bCheckPages = bNew; }
void SetBrowseActionStop( bool bNew ) { m_bBrowseActionStop = bNew; }
void SetNextCycle ( bool bNew ) { m_bNextCycle = bNew; }
bool IsWaitAllowed() const { return m_bWaitAllowed; }
bool IsNextCycle() const { return m_bNextCycle; }
- bool IsInput() const { return m_bInput; }
bool IsPaint() const { return m_bPaint; }
- bool IsIdle() const { return m_bIdle; }
bool IsReschedule() const { return m_bReschedule; }
- bool IsPaintExtraData() const { return m_bPaintExtraData;}
- bool IsInterrupt() const { return IsInput(); }
-
- VclInputFlags GetInputType() const { return m_nInputType; }
+ bool IsIdle() const { return m_pWatch != nullptr; }
+ bool IsPaintExtraData() const { return m_bPaintExtraData; }
+ bool IsInterrupt();
// adjusting Action to the wanted behaviour
void SetPaint ( bool bNew ) { m_bPaint = bNew; }
void SetComplete ( bool bNew ) { m_bComplete = bNew; }
void SetStatBar ( bool bNew );
- void SetInputType ( VclInputFlags nNew ) { m_nInputType = nNew; }
void SetCalcLayout ( bool bNew ) { m_bCalcLayout = bNew; }
void SetReschedule ( bool bNew ) { m_bReschedule = bNew; }
void SetWaitAllowed ( bool bNew ) { m_bWaitAllowed = bNew; }
@@ -178,21 +172,19 @@ public:
class SwLayIdle
{
-
+ TaskStopwatch m_aWatch;
SwRootFrame *pRoot;
SwViewShellImp *pImp; // The Idler registers and deregisters here
SwContentNode *pContentNode; // The current cursor position is saved here
sal_Int32 nTextPos;
bool bPageValid; // Were we able to evaluate everything on the whole page?
-
#ifdef DBG_UTIL
bool m_bIndicator;
-#endif
-#ifdef DBG_UTIL
void ShowIdle( Color eName );
#endif
+ bool IsInterrupt();
enum IdleJobType{ ONLINE_SPELLING, AUTOCOMPLETE_WORDS, WORD_COUNT, SMART_TAGS };
bool DoIdleJob_( const SwContentFrame*, IdleJobType );
bool DoIdleJob( IdleJobType, bool bVisAreaOnly );
diff --git a/sw/source/core/layout/layact.cxx b/sw/source/core/layout/layact.cxx
index 30f6499b01f9..f4c4b26941f1 100644
--- a/sw/source/core/layout/layact.cxx
+++ b/sw/source/core/layout/layact.cxx
@@ -82,13 +82,6 @@ void SwLayAction::CheckWaitCursor()
}
}
-// Time over already?
-inline void SwLayAction::CheckIdleEnd()
-{
- if ( !IsInput() )
- m_bInput = bool(GetInputType()) && Application::AnyInput( GetInputType() );
-}
-
void SwLayAction::SetStatBar( bool bNew )
{
if ( bNew )
@@ -246,20 +239,20 @@ void SwLayAction::PaintContent( const SwContentFrame *pCnt,
}
}
-SwLayAction::SwLayAction( SwRootFrame *pRt, SwViewShellImp *pI ) :
- m_pRoot( pRt ),
+SwLayAction::SwLayAction(SwRootFrame *pRt, SwViewShellImp *pI, TaskStopwatch* pWatch)
+ : m_pRoot(pRt),
m_pImp( pI ),
+ m_pWatch(pWatch),
m_pOptTab( nullptr ),
m_pWait( nullptr ),
m_nPreInvaPage( USHRT_MAX ),
m_nStartTicks( std::clock() ),
- m_nInputType( VclInputFlags::NONE ),
m_nEndPage( USHRT_MAX ),
m_nCheckPageNum( USHRT_MAX )
{
m_bPaintExtraData = ::IsExtraData( m_pImp->GetShell()->GetDoc() );
m_bPaint = m_bComplete = m_bWaitAllowed = m_bCheckPages = true;
- m_bInput = m_bAgain = m_bNextCycle = m_bCalcLayout = m_bIdle = m_bReschedule =
+ m_bInterrupt = m_bAgain = m_bNextCycle = m_bCalcLayout = m_bReschedule =
m_bUpdateExpFields = m_bBrowseActionStop = m_bActionInProgress = false;
// init new flag <mbFormatContentOnInterrupt>.
mbFormatContentOnInterrupt = false;
@@ -274,14 +267,18 @@ SwLayAction::~SwLayAction()
m_pImp->m_pLayAction = nullptr; // unregister
}
+bool SwLayAction::IsInterrupt()
+{
+ return m_bInterrupt || (m_pWatch && m_pWatch->exceededRuntime());
+}
+
void SwLayAction::Reset()
{
m_pOptTab = nullptr;
m_nStartTicks = std::clock();
- m_nInputType = VclInputFlags::NONE;
m_nEndPage = m_nPreInvaPage = m_nCheckPageNum = USHRT_MAX;
m_bPaint = m_bComplete = m_bWaitAllowed = m_bCheckPages = true;
- m_bInput = m_bAgain = m_bNextCycle = m_bCalcLayout = m_bIdle = m_bReschedule =
+ m_bInterrupt = m_bAgain = m_bNextCycle = m_bCalcLayout = m_bReschedule =
m_bUpdateExpFields = m_bBrowseActionStop = false;
}
@@ -442,7 +439,7 @@ void SwLayAction::InternalAction(OutputDevice* pRenderContext)
IDocumentLayoutAccess& rLayoutAccess = m_pRoot->GetFormat()->getIDocumentLayoutAccess();
bool bNoLoop = pPage && SwLayouter::StartLoopControl( m_pRoot->GetFormat()->GetDoc(), pPage );
sal_uInt16 nPercentPageNum = 0;
- while ( (pPage && !IsInterrupt()) || m_nCheckPageNum != USHRT_MAX )
+ while ((!IsInterrupt() && pPage) || (m_nCheckPageNum != USHRT_MAX))
{
if ( !pPage && m_nCheckPageNum != USHRT_MAX &&
(!pPage || pPage->GetPhyPageNum() >= m_nCheckPageNum) )
@@ -565,7 +562,7 @@ void SwLayAction::InternalAction(OutputDevice* pRenderContext)
pPage->InvalidateFlyLayout();
pPage->InvalidateFlyContent();
if ( IsBrowseActionStop() )
- m_bInput = true;
+ m_bInterrupt = true;
}
}
if( bNoLoop )
@@ -583,7 +580,8 @@ void SwLayAction::InternalAction(OutputDevice* pRenderContext)
pPage->ValidateFlyLayout();
pPage->ValidateFlyContent();
}
- if ( !IsInterrupt() )
+
+ if (!IsInterrupt())
{
SetNextCycle( false );
@@ -624,8 +622,8 @@ void SwLayAction::InternalAction(OutputDevice* pRenderContext)
if( bNoLoop )
rLayoutAccess.GetLayouter()->LoopControl( pPage );
}
- CheckIdleEnd();
}
+
if ( !pPage && !IsInterrupt() &&
(m_pRoot->IsSuperfluous() || m_pRoot->IsAssertFlyPages()) )
{
@@ -651,6 +649,7 @@ void SwLayAction::InternalAction(OutputDevice* pRenderContext)
pPage = static_cast<SwPageFrame*>(pPage->GetNext());
}
}
+
if ( IsInterrupt() && pPage )
{
// If we have input, we don't want to format content anymore, but
@@ -679,7 +678,7 @@ void SwLayAction::InternalAction(OutputDevice* pRenderContext)
pPg = pPg ? static_cast<SwPageFrame*>(pPg->GetPrev()) : pPage;
// set flag for interrupt content formatting
- mbFormatContentOnInterrupt = IsInput();
+ mbFormatContentOnInterrupt = IsInterrupt();
long nBottom = rVis.Bottom();
// #i42586# - format current page, if idle action is active
// This is an optimization for the case that the interrupt is created by
@@ -773,7 +772,6 @@ void SwLayAction::InternalAction(OutputDevice* pRenderContext)
bool SwLayAction::TurboAction_( const SwContentFrame *pCnt )
{
-
const SwPageFrame *pPage = nullptr;
if ( !pCnt->isFrameAreaDefinitionValid() || pCnt->IsCompletePaint() || pCnt->IsRetouche() )
{
@@ -834,10 +832,7 @@ bool SwLayAction::TurboAction()
if ( m_pRoot->GetTurbo() )
{
if ( !TurboAction_( m_pRoot->GetTurbo() ) )
- {
- CheckIdleEnd();
bRet = false;
- }
m_pRoot->ResetTurbo();
}
else
@@ -1639,7 +1634,6 @@ bool SwLayAction::FormatContent( const SwPageFrame *pPage )
// paragraph has been processed.
if (!pTab || !bInValid)
{
- CheckIdleEnd();
// consider interrupt formatting.
if ( ( IsInterrupt() && !mbFormatContentOnInterrupt ) ||
( !bBrowse && pPage->IsInvalidLayout() ) ||
@@ -1732,7 +1726,6 @@ bool SwLayAction::FormatContent( const SwPageFrame *pPage )
PaintContent( pContent, pPage, pContent->getFrameArea(), pContent->getFrameArea().Bottom());
if ( IsIdle() )
{
- CheckIdleEnd();
// consider interrupt formatting.
if ( IsInterrupt() && !mbFormatContentOnInterrupt )
return false;
@@ -1828,7 +1821,6 @@ void SwLayAction::FormatFlyContent( const SwFlyFrame *pFly )
// If there's input, we interrupt processing.
if ( !pFly->IsFlyInContentFrame() )
{
- CheckIdleEnd();
// consider interrupt formatting.
if ( IsInterrupt() && !mbFormatContentOnInterrupt )
return;
@@ -1838,6 +1830,11 @@ void SwLayAction::FormatFlyContent( const SwFlyFrame *pFly )
CheckWaitCursor();
}
+bool SwLayIdle::IsInterrupt()
+{
+ return m_aWatch.exceededRuntime();
+}
+
bool SwLayIdle::DoIdleJob_( const SwContentFrame *pCnt, IdleJobType eJob )
{
OSL_ENSURE( pCnt->IsTextFrame(), "NoText neighbour of Text" );
@@ -1921,7 +1918,7 @@ bool SwLayIdle::DoIdleJob_( const SwContentFrame *pCnt, IdleJobType eJob )
bPageValid = bPageValid && (SwTextNode::WrongState::TODO != pTextNode->GetWrongDirty());
if ( aRepaint.HasArea() )
pImp->GetShell()->InvalidateWindows( aRepaint );
- if (Application::AnyInput(VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER)))
+ if (IsInterrupt())
return true;
break;
}
@@ -1929,7 +1926,7 @@ bool SwLayIdle::DoIdleJob_( const SwContentFrame *pCnt, IdleJobType eJob )
const_cast<SwTextFrame*>(pTextFrame)->CollectAutoCmplWrds(*pTextNode, nPos);
// note: bPageValid remains true here even if the cursor
// position is skipped, so no PENDING state needed currently
- if (Application::AnyInput(VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER)))
+ if (IsInterrupt())
return true;
break;
case WORD_COUNT :
@@ -1937,7 +1934,7 @@ bool SwLayIdle::DoIdleJob_( const SwContentFrame *pCnt, IdleJobType eJob )
const sal_Int32 nEnd = pTextNode->GetText().getLength();
SwDocStat aStat;
pTextNode->CountWords( aStat, 0, nEnd );
- if ( Application::AnyInput() )
+ if (IsInterrupt())
return true;
break;
}
@@ -1952,7 +1949,7 @@ bool SwLayIdle::DoIdleJob_( const SwContentFrame *pCnt, IdleJobType eJob )
// handle smarttag problems gracefully and provide diagnostics
SAL_WARN( "sw.core", "SMART_TAGS: " << e);
}
- if (Application::AnyInput(VCL_INPUT_ANY & VclInputFlags(~VclInputFlags::TIMER)))
+ if (IsInterrupt())
return true;
break;
}
@@ -2144,9 +2141,7 @@ SwLayIdle::SwLayIdle( SwRootFrame *pRt, SwViewShellImp *pI ) :
bool bInterrupt(false);
{
- SwLayAction aAction( pRoot, pImp );
- aAction.SetInputType( VCL_INPUT_ANY );
- aAction.SetIdle( true );
+ SwLayAction aAction(pRoot, pImp, &m_aWatch);
aAction.SetWaitAllowed( false );
aAction.Action(pImp->GetShell()->GetOut());
bInterrupt = aAction.IsInterrupt();
diff --git a/sw/source/core/view/viewsh.cxx b/sw/source/core/view/viewsh.cxx
index 640db966432e..b5f67116c311 100644
--- a/sw/source/core/view/viewsh.cxx
+++ b/sw/source/core/view/viewsh.cxx
@@ -277,7 +277,6 @@ void SwViewShell::ImplEndAction( const bool bIdleEnd )
aAction.SetComplete( false );
if ( mnLockPaint )
aAction.SetPaint( false );
- aAction.SetInputType( VclInputFlags::KEYBOARD );
aAction.Action(GetWin());
}
commit df63728d9cfbb673cf68ba792ed4db8725188aee
Author: Samuel Mehrbrodt <Samuel.Mehrbrodt at cib.de>
AuthorDate: Wed Nov 11 08:59:48 2020 +0100
Commit: Samuel Mehrbrodt <Samuel.Mehrbrodt at cib.de>
CommitDate: Wed Nov 11 08:59:48 2020 +0100
Add a TaskStopwatch to interrupt idle loops
If we have multiple pending Idles, they will interrupt / starve
each other, because there will be an instant pending timeout for
the next Idle.
This patch introduces a time slice to tasks, so long running events
can use a TaskStopwatch to do the real interrupt after running out
of their time slice. Apart from the time, this breaks when AnyInput
is available, except for the timer event.
This class just helps to track the time, as the scheduler is coop,
not preemptive.
Change-Id: I9d0b4a5aa388ebdf496b355d100152d890224524
Reviewed-on: https://gerrit.libreoffice.org/75568
Tested-by: Jenkins
Reviewed-by: Jan-Marek Glogowski <glogow at fbihome.de>
(cherry picked from commit 6e13585508ca3c9b66c6571ad1eb42bfcb66ef0b)
diff --git a/include/vcl/TaskStopwatch.hxx b/include/vcl/TaskStopwatch.hxx
new file mode 100644
index 000000000000..c98dcc6f5527
--- /dev/null
+++ b/include/vcl/TaskStopwatch.hxx
@@ -0,0 +1,123 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_VCL_TASK_STOPWATCH_HXX
+#define INCLUDED_VCL_TASK_STOPWATCH_HXX
+
+#include <tools/time.hxx>
+#include <vcl/dllapi.h>
+#include <vcl/inputtypes.hxx>
+#include <vcl/svapp.hxx>
+
+/**
+ * Helper class primary used to track time of long running iterating tasks.
+ *
+ * Normally it should be sufficiant to instanciate the watch object before
+ * starting the iteration and query continueIter() at the end of each.
+ *
+ * Called Stopwatch, because there is already a Timer class in the Scheduler.
+ *
+ * TODO: merge into the general Scheduler, so this can also be used to track
+ * Task runtimes in a more general way.
+ * TODO: handle fast iterations, where continueIter is called multiple times
+ * per tick, by counting the iterations per tick and use that for appoximation.
+ **/
+class VCL_DLLPUBLIC TaskStopwatch
+{
+ static constexpr VclInputFlags eDefaultInputStop = VCL_INPUT_ANY & ~VclInputFlags::TIMER;
+ static constexpr unsigned int nDefaultTimeSlice = 50;
+ static unsigned int m_nTimeSlice;
+
+ sal_uInt64 m_nStartTicks;
+ sal_uInt64 m_nIterStartTicks;
+ bool m_bConsiderLastIterTime;
+ VclInputFlags m_eInputStop;
+
+ bool nextIter(bool bQueryOnly)
+ {
+ sal_uInt64 nCurTicks = tools::Time::GetSystemTicks();
+ // handle system ticks wrap as exceeded time slice
+ if (nCurTicks < m_nStartTicks)
+ return false;
+
+ if (!bQueryOnly && m_bConsiderLastIterTime)
+ {
+ // based on the last iter runtime, we don't expect to finish in time
+ // m_nTimeSlice < (nCurTicks - m_nStartTicks) + (nCurTicks - m_nIterStartTicks)
+ if (m_nTimeSlice < 2 * nCurTicks - m_nIterStartTicks - m_nStartTicks)
+ return false;
+ }
+ // time slice exceeded
+ else if (m_nTimeSlice < nCurTicks - m_nStartTicks)
+ return false;
+
+ if (!bQueryOnly)
+ m_nIterStartTicks = nCurTicks;
+
+ return !Application::AnyInput(m_eInputStop);
+ }
+
+public:
+ /**
+ * Per default the watch consideres the last iter time when asking for an
+ * other iteration, so considers Scheduler::acceptableTaskTime as a
+ * maximum value.
+ *
+ * If you already know your iter time vary in a large range, consider
+ * setting bConciderLastIterTime to false, so Scheduler::acceptableTaskTime
+ * will be used as a mimimum time slot.
+ **/
+ TaskStopwatch(bool bConciderLastIterTime = true)
+ : m_nStartTicks(tools::Time::GetSystemTicks())
+ , m_nIterStartTicks(m_nStartTicks)
+ , m_bConsiderLastIterTime(bConciderLastIterTime)
+ , m_eInputStop(eDefaultInputStop)
+ {
+ }
+
+ /**
+ * Returns true, if the time slot is already exceeded
+ **/
+ bool exceededRuntime() { return !nextIter(true); }
+
+ /**
+ * Returns true, if an other iteration will probably pass in the time slot
+ **/
+ bool continueIter() { return nextIter(false); }
+
+ /**
+ * Reset the stopwatch
+ **/
+ void reset()
+ {
+ m_nStartTicks = tools::Time::GetSystemTicks();
+ m_nIterStartTicks = m_nStartTicks;
+ }
+
+ /**
+ * Sets the input events, which should also "exceed" the stopwatch.
+ *
+ * Per default this ignores the VclInputFlags::TIMER.
+ */
+ void setInputStop(VclInputFlags eInputStop = eDefaultInputStop) { m_eInputStop = eInputStop; }
+ VclInputFlags inputStop() const { return m_eInputStop; }
+
+ /**
+ * Sets the time considered the acceptable maximum for a task to run
+ *
+ * This is an orientation for long time background jobs to yield to
+ * the scheduler, so Idle task don't starve each other too much.
+ **/
+ static unsigned int timeSlice() { return m_nTimeSlice; }
+ static void setTimeSlice(unsigned int nTimeSlice) { m_nTimeSlice = nTimeSlice; }
+};
+
+#endif // INCLUDED_VCL_TASK_STOPWATCH_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/CppunitTest_vcl_timer.mk b/vcl/CppunitTest_vcl_timer.mk
index b17bfc3a27f6..a89e4e070ec8 100644
--- a/vcl/CppunitTest_vcl_timer.mk
+++ b/vcl/CppunitTest_vcl_timer.mk
@@ -27,6 +27,7 @@ $(eval $(call gb_CppunitTest_use_libraries,vcl_timer, \
sal \
salhelper \
test \
+ tl \
unotest \
vcl \
))
diff --git a/vcl/README.scheduler b/vcl/README.scheduler
index 80c14b032c54..b8b85586def6 100644
--- a/vcl/README.scheduler
+++ b/vcl/README.scheduler
@@ -26,8 +26,7 @@ C.2. Tasks should be split into sensible blocks
C.3. This is not an OS scheduler
There is no real way to "fix" B.2. and B.3.
If you need to do a preemptive task, use a thread!
- Otherwise make your task suspendable and check SalInstance::AnyInput
- or call Application::Reschedule regularly.
+ Otherwise make your task suspendable.
= Driving the scheduler AKA the system timer =
@@ -43,8 +42,7 @@ Every time a task is started, the scheduler timer is adjusted. When the timer
fires, it posts an event to the system message queue. If the next most
important task is an Idle (AKA instant, 0ms timeout), the event is pushed to
the back of the queue, so we don't starve system messages, otherwise to the
-front. This is especially important to get a correct SalInstance::AnyInput
-handling, as this is used to suspend long background Idle tasks.
+front.
Every time the scheduler is invoked it searches for the next task to process,
restarts the timer with the timeout for the next event and then invokes the
@@ -120,7 +118,7 @@ bool DoYield( bool bWait, bool bAllCurrent )
== General: main thread deferral ==
-Currently for Mac and Windows, we run main thread deferrals by disabling the
+In almost all VCL backends, we run main thread deferrals by disabling the
SolarMutex using a boolean. In the case of the redirect, this makes
tryToAcquire and doAcquire return true or 1, while a release is ignored.
Also the IsCurrentThread() mutex check function will act accordingly, so all
@@ -176,6 +174,52 @@ To prevent these problems, we don't even try to remove these events, but
invalidate them by versioning the timer events. Timer events with invalid
versions are processed but simply don't run the scheduler.
+== General: track time of long running tasks ==
+
+There is TaskStopwatch class. It'll track the time and report a timout either
+when the tasks time slice is finished or some system event did occur.
+
+Eventually it will be merged into the main scheduler, so each invoked task can
+easily track it's runtime and eventually this can be used to "blame" / find
+other long running tasks, so interactivity can be improved.
+
+There were some questions coming up when implementing it:
+
+=== Why does the scheduler not detect that we only have idle tasks pending,
+and skip the instant timeout? ===
+
+You never know how long a task will run. Currently the scheduler simply asks
+each task when it'll be ready to run, until two runable tasks are found.
+Normally this is very quick, as LO has a lot of one-shot instant tasks / Idles
+and just a very few long term pending Timers.
+
+Especially UNO calls add a lot of Idles to the task list, which just need to
+be processed in order.
+
+=== Why not use things like Linux timer wheels? ===
+
+LO has relatively few timers and a lot one-shot Idles. 99% of time the search
+for the next task is quick, because there are just ~5 long term timers per
+document (cache invalidation, cursor blinking etc.).
+
+This might become a problem, if you have a lot of open documents, so the long
+term timer list increases AKA for highly loaded LOOL instances.
+
+But the Linux timer wheel mainly relies on the facts that the OS timers are
+expected to not expire, as they are use to catch "error" timeouts, which rarely
+happen, so this definitly not matches LO's usage.
+
+=== Not really usable to find misbehaving tasks ===
+
+The TaskStopwatch class is just a little time keeper + detecting of input
+events. This is not about misbehaving Tasks, but long running tasks, which
+have to yield to the Scheduler, so other Tasks and System events can be
+processed.
+
+There is the TODO to merge the functionality into the Scheduler itself, at
+which point we can think about profiling individual Tasks to improve
+interactivity.
+
== macOS implementation details ==
Generally the Scheduler is handled as expected, except on resize, which is
@@ -342,4 +386,9 @@ priority idle in the event loop.
A few layers of indirection make this code hard to follow. The SalXLib::Yield
and SalX11Display::Yield architecture makes it impossible to process just the
current events. This really needs a refactoring and rearchitecture step, which
-will also affect the Gtk+ and KDE4 backend for the user event handling.
+will also affect the Gtk+ and KDE backend for the user event handling.
+
+== Merge TaskStopwatch functionality into the Scheduler ==
+
+This way it can be easier used to profile Tasks, eventually to improve LO's
+interactivity.
diff --git a/vcl/qa/cppunit/timer.cxx b/vcl/qa/cppunit/timer.cxx
index aaaf9ec8d86e..ede3c35df2c5 100644
--- a/vcl/qa/cppunit/timer.cxx
+++ b/vcl/qa/cppunit/timer.cxx
@@ -16,6 +16,7 @@
#include <salhelper/thread.hxx>
#include <chrono>
+#include <vcl/TaskStopwatch.hxx>
#include <vcl/timer.hxx>
#include <vcl/idle.hxx>
#include <vcl/svapp.hxx>
@@ -75,6 +76,8 @@ public:
void testPriority();
void testRoundRobin();
+ void testStopwatch();
+
CPPUNIT_TEST_SUITE(TimerTest);
CPPUNIT_TEST(testIdle);
#ifndef WIN32
@@ -96,6 +99,8 @@ public:
CPPUNIT_TEST(testPriority);
CPPUNIT_TEST(testRoundRobin);
+ CPPUNIT_TEST(testStopwatch);
+
CPPUNIT_TEST_SUITE_END();
};
@@ -534,6 +539,79 @@ void TimerTest::testRoundRobin()
CPPUNIT_ASSERT( 3 == nCount1 && 3 == nCount2 );
}
+class StopwatchIdle : public AutoIdle
+{
+ sal_uInt32 m_nIters;
+ sal_uInt64 m_nBusyTicks;
+
+public:
+ StopwatchIdle(sal_uInt64 nBusyTicks)
+ : AutoIdle("StopwatchIdle")
+ , m_nIters(0)
+ , m_nBusyTicks(nBusyTicks)
+ {
+ if (m_nBusyTicks > 0)
+ Start();
+ }
+
+ virtual void Invoke() override
+ {
+ TaskStopwatch aWatch;
+ // ignore all system events
+ aWatch.setInputStop(VclInputFlags::NONE);
+
+ sal_uInt64 nStartTicks = tools::Time::GetSystemTicks();
+ sal_uInt64 nCurTicks = nStartTicks;
+
+ while (!aWatch.exceededRuntime())
+ {
+ nCurTicks = tools::Time::GetSystemTicks();
+ if (nCurTicks - nStartTicks >= m_nBusyTicks)
+ {
+ nCurTicks = nStartTicks + m_nBusyTicks;
+ break;
+ }
+ }
+
+ assert(m_nBusyTicks >= (nCurTicks - nStartTicks));
+ m_nBusyTicks -= (nCurTicks - nStartTicks);
+ m_nIters++;
+
+ if (m_nBusyTicks == 0)
+ Stop();
+ }
+
+ bool isDone(sal_uInt32 &nIters)
+ {
+ nIters = m_nIters;
+ return (m_nBusyTicks == 0);
+ }
+};
+
+void TimerTest::testStopwatch()
+{
+ TaskStopwatch::setTimeSlice(10);
+
+ StopwatchIdle a1Idle(100);
+ StopwatchIdle a2Idle(100);
+
+ sal_uInt32 n1Iter, n2Iter;
+ while (true)
+ {
+ Application::Reschedule();
+
+ bool b1Done = a1Idle.isDone(n1Iter);
+ bool b2Done = a2Idle.isDone(n2Iter);
+ CPPUNIT_ASSERT(n1Iter >= n2Iter);
+
+ if (b1Done && b2Done)
+ break;
+ }
+
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(10), n1Iter);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt32(10), n2Iter);
+}
+
CPPUNIT_TEST_SUITE_REGISTRATION(TimerTest);
CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/vcl/source/app/scheduler.cxx b/vcl/source/app/scheduler.cxx
index c6a0c3a2e1fc..a6d7d03abf17 100644
--- a/vcl/source/app/scheduler.cxx
+++ b/vcl/source/app/scheduler.cxx
@@ -31,6 +31,7 @@
#include <svdata.hxx>
#include <tools/time.hxx>
#include <unotools/configmgr.hxx>
+#include <vcl/TaskStopwatch.hxx>
#include <vcl/scheduler.hxx>
#include <vcl/idle.hxx>
#include <saltimer.hxx>
@@ -94,6 +95,8 @@ inline std::basic_ostream<charT, traits> & operator <<(
} // end anonymous namespace
+unsigned int TaskStopwatch::m_nTimeSlice = TaskStopwatch::nDefaultTimeSlice;
+
void Scheduler::ImplDeInitScheduler()
{
ImplSVData* pSVData = ImplGetSVData();
More information about the Libreoffice-commits
mailing list