.gitreview                       |    2 
 include/vcl/TaskStopwatch.hxx    |  123 +++++++++++++++++++++++++++++++++++++++
 sw/source/core/inc/layact.hxx    |   27 ++------
 sw/source/core/layout/layact.cxx |   59 ++++++++----------
 sw/source/core/view/viewsh.cxx   |    1 
 vcl/CppunitTest_vcl_timer.mk     |    1 
 vcl/README.scheduler             |   59 +++++++++++++++++-
 vcl/qa/cppunit/timer.cxx         |   77 ++++++++++++++++++++++++
 vcl/source/app/scheduler.cxx     |    3 
 9 files changed, 295 insertions(+), 57 deletions(-)

New commits:
commit 73b9318dd60b191984ee6e9fa12afb25773ad11b
Author:     Samuel Mehrbrodt <samuel.mehrbr...@allotropia.de>
AuthorDate: Tue Jan 18 10:08:24 2022 +0100
Commit:     Samuel Mehrbrodt <samuel.mehrbr...@allotropia.de>
CommitDate: Tue Jan 18 10:08:24 2022 +0100

    Update .gitreview
    
    Change-Id: I360c95365dbe553dc589a0a7da99189f1148a754

diff --git a/.gitreview b/.gitreview
index c4d492adf26d..79f1e7ad3fbf 100644
--- a/.gitreview
+++ b/.gitreview
@@ -3,5 +3,5 @@ host=gerrit.libreoffice.org
 port=29418
 project=core
 defaultremote=logerrit
-defaultbranch=feature/cib_contract57d
+defaultbranch=feature/cib_contract57l
 
commit de28100fe351ded5e0724b98e2ebb398ee21825c
Author:     Jan-Marek Glogowski <jan-marek.glogow...@extern.cib.de>
AuthorDate: Tue Aug 6 17:09:51 2019 +0200
Commit:     Samuel Mehrbrodt <samuel.mehrbr...@allotropia.de>
CommitDate: Tue Jan 18 09:58:02 2022 +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 <glo...@fbihome.de>
    (cherry picked from commit 383032c50a3e3354f04200ce984a47ab9d2c5c67)

diff --git a/sw/source/core/inc/layact.hxx b/sw/source/core/inc/layact.hxx
index 50335077694c..1707383b62d7 100644
--- a/sw/source/core/inc/layact.hxx
+++ b/sw/source/core/inc/layact.hxx
@@ -22,6 +22,7 @@
 #include <sal/config.h>
 
 #include <vcl/inputtypes.hxx>
+#include <vcl/TaskStopwatch.hxx>
 #include <tools/color.hxx>
 
 #include <ctime>
@@ -56,6 +57,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.
@@ -74,7 +76,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
@@ -84,9 +85,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
@@ -119,33 +119,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; }
@@ -182,21 +175,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 96796cca4662..abac3364efd2 100644
--- a/sw/source/core/layout/layact.cxx
+++ b/sw/source/core/layout/layact.cxx
@@ -89,13 +89,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 )
@@ -253,19 +246,19 @@ 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_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;
@@ -280,14 +273,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;
 }
 
@@ -446,7 +443,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))
     {
         // note: this is the only place that consumes and resets 
m_nCheckPageNum
         if ((IsInterrupt() || !pPage) && m_nCheckPageNum != USHRT_MAX)
@@ -570,7 +567,7 @@ void SwLayAction::InternalAction(OutputDevice* 
pRenderContext)
                             pPage->InvalidateFlyLayout();
                             pPage->InvalidateFlyContent();
                             if ( IsBrowseActionStop() )
-                                m_bInput = true;
+                                m_bInterrupt = true;
                         }
                     }
                     if( bNoLoop )
@@ -588,7 +585,8 @@ void SwLayAction::InternalAction(OutputDevice* 
pRenderContext)
                 pPage->ValidateFlyLayout();
                 pPage->ValidateFlyContent();
             }
-            if ( !IsInterrupt() )
+
+            if (!IsInterrupt())
             {
                 SetNextCycle( false );
 
@@ -629,8 +627,8 @@ void SwLayAction::InternalAction(OutputDevice* 
pRenderContext)
                 if( bNoLoop )
                     rLayoutAccess.GetLayouter()->LoopControl( pPage );
             }
-            CheckIdleEnd();
         }
+
         if ( !pPage && !IsInterrupt() &&
              (m_pRoot->IsSuperfluous() || m_pRoot->IsAssertFlyPages()) )
         {
@@ -656,6 +654,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
@@ -684,7 +683,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
@@ -778,7 +777,6 @@ void SwLayAction::InternalAction(OutputDevice* 
pRenderContext)
 
 bool SwLayAction::TurboAction_( const SwContentFrame *pCnt )
 {
-
     const SwPageFrame *pPage = nullptr;
     if ( !pCnt->isFrameAreaDefinitionValid() || pCnt->IsCompletePaint() || 
pCnt->IsRetouche() )
     {
@@ -839,10 +837,7 @@ bool SwLayAction::TurboAction()
     if ( m_pRoot->GetTurbo() )
     {
         if ( !TurboAction_( m_pRoot->GetTurbo() ) )
-        {
-            CheckIdleEnd();
             bRet = false;
-        }
         m_pRoot->ResetTurbo();
     }
     else
@@ -1688,7 +1683,6 @@ bool SwLayAction::FormatContent(SwPageFrame *const pPage)
             // paragraph has been processed.
             if (!pTab || !bInValid)
             {
-                CheckIdleEnd();
                 // consider interrupt formatting.
                 if ( ( IsInterrupt() && !mbFormatContentOnInterrupt ) ||
                      ( !bBrowse && pPage->IsInvalidLayout() ) ||
@@ -1781,7 +1775,6 @@ bool SwLayAction::FormatContent(SwPageFrame *const pPage)
                 PaintContent( pContent, pPage, pContent->getFrameArea(), 
pContent->getFrameArea().Bottom());
             if ( IsIdle() )
             {
-                CheckIdleEnd();
                 // consider interrupt formatting.
                 if ( IsInterrupt() && !mbFormatContentOnInterrupt )
                     return false;
@@ -1877,7 +1870,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;
@@ -1887,6 +1879,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" );
@@ -1970,7 +1967,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;
             }
@@ -1978,7 +1975,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 :
@@ -1986,7 +1983,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;
             }
@@ -2001,7 +1998,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;
             }
@@ -2193,9 +2190,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 66775b933a8d..0095d134ba6d 100644
--- a/sw/source/core/view/viewsh.cxx
+++ b/sw/source/core/view/viewsh.cxx
@@ -291,7 +291,6 @@ void SwViewShell::ImplEndAction( const bool bIdleEnd )
         aAction.SetComplete( false );
         if ( mnLockPaint )
             aAction.SetPaint( false );
-        aAction.SetInputType( VclInputFlags::KEYBOARD );
         aAction.Action(GetWin());
     }
 
commit 95a422aa7105a94f6b0b78246d83df3180a74ecc
Author:     Stephan Bergmann <sberg...@redhat.com>
AuthorDate: Fri Aug 16 09:56:29 2019 +0200
Commit:     Samuel Mehrbrodt <samuel.mehrbr...@allotropia.de>
CommitDate: Tue Jan 18 09:57:59 2022 +0100

    Drop bogus check from TimerTest::testStopwatch
    
    (that had been added with 6e13585508ca3c9b66c6571ad1eb42bfcb66ef0b "Add a
    TaskStopwatch to interrupt idle loops").  For each StopwatchIdle, m_nIters
    counts the calls to Invoke before it calls Stop (which it calls based on
    tools::Time::GetSystemTicks calculations).  But the number of such
    GetSystemTicks() spent in each Invoke is nondeterministic (it can e.g. be
    affected by the overall system load), so a2Idle may Stop prior to a1Idle and
    thus have a lower nIter2 than nIter1.
    
    Change-Id: I416eee9774c3605be25e9832b24dec7d9dcb00c2
    Reviewed-on: https://gerrit.libreoffice.org/77561
    Tested-by: Jenkins
    Reviewed-by: Stephan Bergmann <sberg...@redhat.com>
    (cherry picked from commit 92e42a0fde32e3f2dbe2c786a0e41547e4912b4b)

diff --git a/vcl/qa/cppunit/timer.cxx b/vcl/qa/cppunit/timer.cxx
index 0531c0c7f99c..5154c1947a75 100644
--- a/vcl/qa/cppunit/timer.cxx
+++ b/vcl/qa/cppunit/timer.cxx
@@ -603,7 +603,6 @@ void TimerTest::testStopwatch()
 
         bool b1Done = a1Idle.isDone(n1Iter);
         bool b2Done = a2Idle.isDone(n2Iter);
-        CPPUNIT_ASSERT(n1Iter >= n2Iter);
 
         if (b1Done && b2Done)
             break;
commit 7ab267ef5e2d86a2bdb0d67f2ab6294e0ee6fc95
Author:     Jan-Marek Glogowski <glo...@fbihome.de>
AuthorDate: Sun Jul 7 23:09:15 2019 +0200
Commit:     Samuel Mehrbrodt <samuel.mehrbr...@allotropia.de>
CommitDate: Tue Jan 18 09:57:47 2022 +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 <glo...@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 23decf3b7ec2..ebbd7c3682d1 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
@@ -343,3 +387,8 @@ 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 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 01eef078fae8..0531c0c7f99c 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();
 };
 
@@ -535,6 +540,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 23bba5d7a25e..09480a6a05e3 100644
--- a/vcl/source/app/scheduler.cxx
+++ b/vcl/source/app/scheduler.cxx
@@ -33,6 +33,7 @@
 #include <tools/debug.hxx>
 #include <tools/diagnose_ex.h>
 #include <unotools/configmgr.hxx>
+#include <vcl/TaskStopwatch.hxx>
 #include <vcl/scheduler.hxx>
 #include <vcl/idle.hxx>
 #include <saltimer.hxx>
@@ -96,6 +97,8 @@ std::basic_ostream<charT, traits> & operator <<(
 
 } // end anonymous namespace
 
+unsigned int TaskStopwatch::m_nTimeSlice = TaskStopwatch::nDefaultTimeSlice;
+
 void Scheduler::ImplDeInitScheduler()
 {
     ImplSVData* pSVData = ImplGetSVData();

Reply via email to