xref: /core/vcl/source/app/scheduler.cxx (revision e89c4d15)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sal/config.h>
21 
22 #include <cassert>
23 #include <cstdlib>
24 #include <exception>
25 #include <typeinfo>
26 
27 #include <com/sun/star/uno/Exception.hpp>
28 #include <sal/log.hxx>
29 #include <sal/types.h>
30 #include <svdata.hxx>
31 #include <tools/time.hxx>
32 #include <tools/debug.hxx>
33 #include <comphelper/diagnose_ex.hxx>
34 #include <comphelper/configuration.hxx>
35 #include <vcl/TaskStopwatch.hxx>
36 #include <vcl/scheduler.hxx>
37 #include <vcl/idle.hxx>
38 #include <saltimer.hxx>
39 #include <salinst.hxx>
40 #include <comphelper/profilezone.hxx>
41 #include <schedulerimpl.hxx>
42 
43 namespace {
44 
45 template< typename charT, typename traits >
operator <<(std::basic_ostream<charT,traits> & stream,const Task & task)46 std::basic_ostream<charT, traits> & operator <<(
47     std::basic_ostream<charT, traits> & stream, const Task& task )
48 {
49     stream << "a: " << task.IsActive() << " p: " << static_cast<int>(task.GetPriority());
50     const char *name = task.GetDebugName();
51     if( nullptr == name )
52         return stream << " (nullptr)";
53     else
54         return stream << " " << name;
55 }
56 
57 /**
58  * clang won't compile this in the Timer.hxx header, even with a class Idle
59  * forward definition, due to the incomplete Idle type in the template.
60  * Currently the code is just used in the Scheduler, so we keep it local.
61  *
62  * @see http://clang.llvm.org/compatibility.html#undep_incomplete
63  */
64 template< typename charT, typename traits >
operator <<(std::basic_ostream<charT,traits> & stream,const Timer & timer)65 std::basic_ostream<charT, traits> & operator <<(
66     std::basic_ostream<charT, traits> & stream, const Timer& timer )
67 {
68     bool bIsIdle = (dynamic_cast<const Idle*>( &timer ) != nullptr);
69     stream << (bIsIdle ? "Idle " : "Timer")
70            << " a: " << timer.IsActive() << " p: " << static_cast<int>(timer.GetPriority());
71     const char *name = timer.GetDebugName();
72     if ( nullptr == name )
73         stream << " (nullptr)";
74     else
75         stream << " " << name;
76     if ( !bIsIdle )
77         stream << " " << timer.GetTimeout() << "ms";
78     stream << " (" << &timer << ")";
79     return stream;
80 }
81 
82 template< typename charT, typename traits >
operator <<(std::basic_ostream<charT,traits> & stream,const Idle & idle)83 std::basic_ostream<charT, traits> & operator <<(
84     std::basic_ostream<charT, traits> & stream, const Idle& idle )
85 {
86     return stream << static_cast<const Timer*>( &idle );
87 }
88 
89 template< typename charT, typename traits >
operator <<(std::basic_ostream<charT,traits> & stream,const ImplSchedulerData & data)90 std::basic_ostream<charT, traits> & operator <<(
91     std::basic_ostream<charT, traits> & stream, const ImplSchedulerData& data )
92 {
93     stream << " i: " << data.mbInScheduler;
94     return stream;
95 }
96 
97 } // end anonymous namespace
98 
99 unsigned int TaskStopwatch::m_nTimeSlice = TaskStopwatch::nDefaultTimeSlice;
100 
ImplDeInitScheduler()101 void Scheduler::ImplDeInitScheduler()
102 {
103     ImplSVData* pSVData = ImplGetSVData();
104     assert( pSVData != nullptr );
105     ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx;
106 
107     DBG_TESTSOLARMUTEX();
108 
109     SchedulerGuard aSchedulerGuard;
110 
111     int nTaskPriority = 0;
112 #if OSL_DEBUG_LEVEL > 0
113     sal_uInt32 nTasks = 0;
114     for (nTaskPriority = 0; nTaskPriority < PRIO_COUNT; ++nTaskPriority)
115     {
116         ImplSchedulerData* pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority];
117         while ( pSchedulerData )
118         {
119             ++nTasks;
120             pSchedulerData = pSchedulerData->mpNext;
121         }
122     }
123     SAL_INFO( "vcl.schedule.deinit",
124               "DeInit the scheduler - pending tasks: " << nTasks );
125 
126     // clean up all Idles
127     Unlock();
128     ProcessEventsToIdle();
129     Lock();
130 #endif
131     rSchedCtx.mbActive = false;
132 
133     assert( nullptr == rSchedCtx.mpSchedulerStack || (!rSchedCtx.mpSchedulerStack->mpTask && !rSchedCtx.mpSchedulerStack->mpNext) );
134 
135     if (rSchedCtx.mpSalTimer) rSchedCtx.mpSalTimer->Stop();
136     delete rSchedCtx.mpSalTimer;
137     rSchedCtx.mpSalTimer = nullptr;
138 
139 #if OSL_DEBUG_LEVEL > 0
140     sal_uInt32 nActiveTasks = 0, nIgnoredTasks = 0;
141 #endif
142     nTaskPriority = 0;
143     ImplSchedulerData* pSchedulerData = nullptr;
144 
145 next_priority:
146     pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority];
147     while ( pSchedulerData )
148     {
149         Task *pTask = pSchedulerData->mpTask;
150         if ( pTask )
151         {
152             if ( pTask->mbActive )
153             {
154 #if OSL_DEBUG_LEVEL > 0
155                 const char *sIgnored = "";
156                 ++nActiveTasks;
157                 // TODO: shutdown these timers before Scheduler de-init
158                 // TODO: remove Task from static object
159                 if ( pTask->GetDebugName() && ( false
160                         || !strcmp( pTask->GetDebugName(), "desktop::Desktop m_firstRunTimer" )
161                         || !strcmp( pTask->GetDebugName(), "DrawWorkStartupTimer" )
162                         || !strcmp( pTask->GetDebugName(), "editeng::ImpEditEngine aOnlineSpellTimer" )
163                         || !strcmp( pTask->GetDebugName(), "sc ScModule IdleTimer" )
164                         || !strcmp( pTask->GetDebugName(), "sd::CacheConfiguration maReleaseTimer" )
165                         || !strcmp( pTask->GetDebugName(), "svtools::GraphicCache maReleaseTimer" )
166                         || !strcmp( pTask->GetDebugName(), "svtools::GraphicObject mpSwapOutTimer" )
167                         || !strcmp( pTask->GetDebugName(), "svx OLEObjCache pTimer UnloadCheck" )
168                         || !strcmp( pTask->GetDebugName(), "vcl SystemDependentDataBuffer aSystemDependentDataBuffer" )
169                         ))
170                 {
171                     sIgnored = " (ignored)";
172                     ++nIgnoredTasks;
173                 }
174                 const Timer *timer = dynamic_cast<Timer*>( pTask );
175                 if ( timer )
176                     SAL_WARN( "vcl.schedule.deinit", "DeInit task: " << *timer << sIgnored );
177                 else
178                     SAL_WARN( "vcl.schedule.deinit", "DeInit task: " << *pTask << sIgnored );
179 #endif
180                 pTask->mbActive = false;
181             }
182             pTask->mpSchedulerData = nullptr;
183             pTask->SetStatic();
184         }
185         ImplSchedulerData* pDeleteSchedulerData = pSchedulerData;
186         pSchedulerData = pSchedulerData->mpNext;
187         delete pDeleteSchedulerData;
188     }
189 
190     ++nTaskPriority;
191     if (nTaskPriority < PRIO_COUNT)
192         goto next_priority;
193 
194 #if OSL_DEBUG_LEVEL > 0
195     SAL_INFO( "vcl.schedule.deinit", "DeInit the scheduler - finished" );
196     SAL_WARN_IF( 0 != nActiveTasks, "vcl.schedule.deinit", "DeInit active tasks: "
197         << nActiveTasks << " (ignored: " << nIgnoredTasks << ")" );
198 //    assert( nIgnoredTasks == nActiveTasks );
199 #endif
200 
201     for (nTaskPriority = 0; nTaskPriority < PRIO_COUNT; ++nTaskPriority)
202     {
203         rSchedCtx.mpFirstSchedulerData[nTaskPriority] = nullptr;
204         rSchedCtx.mpLastSchedulerData[nTaskPriority]  = nullptr;
205     }
206     rSchedCtx.mnTimerPeriod        = InfiniteTimeoutMs;
207 }
208 
Lock()209 void Scheduler::Lock()
210 {
211     ImplSVData* pSVData = ImplGetSVData();
212     assert( pSVData != nullptr );
213     pSVData->maSchedCtx.maMutex.lock();
214 }
215 
Unlock()216 void Scheduler::Unlock()
217 {
218     ImplSVData* pSVData = ImplGetSVData();
219     assert( pSVData != nullptr );
220     pSVData->maSchedCtx.maMutex.unlock();
221 }
222 
223 /**
224  * Start a new timer if we need to for nMS duration.
225  *
226  * if this is longer than the existing duration we're
227  * waiting for, do nothing - unless bForce - which means
228  * to reset the minimum period; used by the scheduled itself.
229  */
ImplStartTimer(sal_uInt64 nMS,bool bForce,sal_uInt64 nTime)230 void Scheduler::ImplStartTimer(sal_uInt64 nMS, bool bForce, sal_uInt64 nTime)
231 {
232     ImplSVData* pSVData = ImplGetSVData();
233     ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx;
234     if ( !rSchedCtx.mbActive )
235         return;
236 
237     if (!rSchedCtx.mpSalTimer)
238     {
239         rSchedCtx.mnTimerStart = 0;
240         rSchedCtx.mnTimerPeriod = InfiniteTimeoutMs;
241         rSchedCtx.mpSalTimer = pSVData->mpDefInst->CreateSalTimer();
242         rSchedCtx.mpSalTimer->SetCallback(Scheduler::CallbackTaskScheduling);
243     }
244 
245     assert(SAL_MAX_UINT64 - nMS >= nTime);
246 
247     sal_uInt64 nProposedTimeout = nTime + nMS;
248     sal_uInt64 nCurTimeout = ( rSchedCtx.mnTimerPeriod == InfiniteTimeoutMs )
249         ? SAL_MAX_UINT64 : rSchedCtx.mnTimerStart + rSchedCtx.mnTimerPeriod;
250 
251     // Only if smaller timeout, to avoid skipping.
252     // Force instant wakeup on 0ms, if the previous period was not 0ms
253     if (bForce || nProposedTimeout < nCurTimeout || (!nMS && rSchedCtx.mnTimerPeriod))
254     {
255         SAL_INFO( "vcl.schedule", "  Starting scheduler system timer (" << nMS << "ms)" );
256         rSchedCtx.mnTimerStart = nTime;
257         rSchedCtx.mnTimerPeriod = nMS;
258         rSchedCtx.mpSalTimer->Start( nMS );
259     }
260 }
261 
262 static bool g_bDeterministicMode = false;
263 
SetDeterministicMode(bool bDeterministic)264 void Scheduler::SetDeterministicMode(bool bDeterministic)
265 {
266     g_bDeterministicMode = bDeterministic;
267 }
268 
GetDeterministicMode()269 bool Scheduler::GetDeterministicMode()
270 {
271     return g_bDeterministicMode;
272 }
273 
IdlesLockGuard()274 Scheduler::IdlesLockGuard::IdlesLockGuard()
275 {
276     ImplSVData* pSVData = ImplGetSVData();
277     ImplSchedulerContext& rSchedCtx = pSVData->maSchedCtx;
278     osl_atomic_increment(&rSchedCtx.mnIdlesLockCount);
279     if (!Application::IsMainThread())
280     {
281         // Make sure that main thread has reached the main message loop, so no idles are executing.
282         // It is important to ensure this, because e.g. ProcessEventsToIdle could be executed in a
283         // nested event loop, while an active processed idle in the main thread is waiting for some
284         // condition to proceed. Only main thread returning to Application::Execute guarantees that
285         // the flag really took effect.
286         pSVData->m_inExecuteCondtion.reset();
287         // Put an empty event to the application's queue, to make sure that it loops through the
288         // code that sets the condition, even when there's no other events in the queue
289         Application::PostUserEvent({});
290         SolarMutexReleaser releaser;
291         pSVData->m_inExecuteCondtion.wait();
292     }
293 }
294 
~IdlesLockGuard()295 Scheduler::IdlesLockGuard::~IdlesLockGuard()
296 {
297     ImplSchedulerContext& rSchedCtx = ImplGetSVData()->maSchedCtx;
298     osl_atomic_decrement(&rSchedCtx.mnIdlesLockCount);
299 }
300 
UpdateSystemTimer(ImplSchedulerContext & rSchedCtx,const sal_uInt64 nMinPeriod,const bool bForce,const sal_uInt64 nTime)301 inline void Scheduler::UpdateSystemTimer( ImplSchedulerContext &rSchedCtx,
302                                           const sal_uInt64 nMinPeriod,
303                                           const bool bForce, const sal_uInt64 nTime )
304 {
305     if ( InfiniteTimeoutMs == nMinPeriod )
306     {
307         SAL_INFO("vcl.schedule", "  Stopping system timer");
308         if ( rSchedCtx.mpSalTimer )
309             rSchedCtx.mpSalTimer->Stop();
310         rSchedCtx.mnTimerPeriod = nMinPeriod;
311     }
312     else
313         Scheduler::ImplStartTimer( nMinPeriod, bForce, nTime );
314 }
315 
AppendSchedulerData(ImplSchedulerContext & rSchedCtx,ImplSchedulerData * const pSchedulerData)316 static void AppendSchedulerData( ImplSchedulerContext &rSchedCtx,
317                                         ImplSchedulerData * const pSchedulerData)
318 {
319     assert(pSchedulerData->mpTask);
320     pSchedulerData->mePriority = pSchedulerData->mpTask->GetPriority();
321     pSchedulerData->mpNext = nullptr;
322 
323     const int nTaskPriority = static_cast<int>(pSchedulerData->mePriority);
324     if (!rSchedCtx.mpLastSchedulerData[nTaskPriority])
325     {
326         rSchedCtx.mpFirstSchedulerData[nTaskPriority] = pSchedulerData;
327         rSchedCtx.mpLastSchedulerData[nTaskPriority] = pSchedulerData;
328     }
329     else
330     {
331         rSchedCtx.mpLastSchedulerData[nTaskPriority]->mpNext = pSchedulerData;
332         rSchedCtx.mpLastSchedulerData[nTaskPriority] = pSchedulerData;
333     }
334 }
335 
DropSchedulerData(ImplSchedulerContext & rSchedCtx,ImplSchedulerData * const pPrevSchedulerData,const ImplSchedulerData * const pSchedulerData,const int nTaskPriority)336 static ImplSchedulerData* DropSchedulerData(
337     ImplSchedulerContext &rSchedCtx, ImplSchedulerData * const pPrevSchedulerData,
338     const ImplSchedulerData * const pSchedulerData, const int nTaskPriority)
339 {
340     assert( pSchedulerData );
341     if ( pPrevSchedulerData )
342         assert( pPrevSchedulerData->mpNext == pSchedulerData );
343     else
344         assert(rSchedCtx.mpFirstSchedulerData[nTaskPriority] == pSchedulerData);
345 
346     ImplSchedulerData * const pSchedulerDataNext = pSchedulerData->mpNext;
347     if ( pPrevSchedulerData )
348         pPrevSchedulerData->mpNext = pSchedulerDataNext;
349     else
350         rSchedCtx.mpFirstSchedulerData[nTaskPriority] = pSchedulerDataNext;
351     if ( !pSchedulerDataNext )
352         rSchedCtx.mpLastSchedulerData[nTaskPriority] = pPrevSchedulerData;
353     return pSchedulerDataNext;
354 }
355 
CallbackTaskScheduling()356 void Scheduler::CallbackTaskScheduling()
357 {
358     ImplSVData *pSVData = ImplGetSVData();
359     ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx;
360 
361     DBG_TESTSOLARMUTEX();
362 
363     SchedulerGuard aSchedulerGuard;
364     if ( !rSchedCtx.mbActive || InfiniteTimeoutMs == rSchedCtx.mnTimerPeriod )
365         return;
366 
367     sal_uInt64 nTime = tools::Time::GetSystemTicks();
368     // Allow for decimals, so subtract in the compare (needed at least on iOS)
369     if ( nTime < rSchedCtx.mnTimerStart + rSchedCtx.mnTimerPeriod -1)
370     {
371         int nSleep = rSchedCtx.mnTimerStart + rSchedCtx.mnTimerPeriod - nTime;
372         UpdateSystemTimer(rSchedCtx, nSleep, true, nTime);
373         return;
374     }
375 
376     ImplSchedulerData* pSchedulerData = nullptr;
377     ImplSchedulerData* pPrevSchedulerData = nullptr;
378     ImplSchedulerData *pMostUrgent = nullptr;
379     ImplSchedulerData *pPrevMostUrgent = nullptr;
380     int                nMostUrgentPriority = 0;
381     sal_uInt64         nMinPeriod = InfiniteTimeoutMs;
382     sal_uInt64         nReadyPeriod = InfiniteTimeoutMs;
383     unsigned           nTasks = 0;
384     int                nTaskPriority = 0;
385 
386     for (; nTaskPriority < PRIO_COUNT; ++nTaskPriority)
387     {
388         // Related: tdf#152703 Eliminate potential blocking during live resize
389         // Only higher priority tasks need to be fired to redraw the window
390         // so skip firing potentially long-running tasks, such as the Writer
391         // idle layout timer, when a window is in live resize
392         if ( ImplGetSVData()->mpWinData->mbIsLiveResize && nTaskPriority == static_cast<int>(TaskPriority::LOWEST) )
393             continue;
394 
395         pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority];
396         pPrevSchedulerData = nullptr;
397         while (pSchedulerData)
398         {
399             ++nTasks;
400             const Timer *timer = dynamic_cast<Timer*>( pSchedulerData->mpTask );
401             if ( timer )
402                 SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
403                         << pSchedulerData << " " << *pSchedulerData << " " << *timer );
404             else if ( pSchedulerData->mpTask )
405                 SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
406                         << pSchedulerData << " " << *pSchedulerData
407                         << " " << *pSchedulerData->mpTask );
408             else
409                 SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
410                         << pSchedulerData << " " << *pSchedulerData << " (to be deleted)" );
411 
412             // Should the Task be released from scheduling?
413             assert(!pSchedulerData->mbInScheduler);
414             if (!pSchedulerData->mpTask || !pSchedulerData->mpTask->IsActive())
415             {
416                 ImplSchedulerData * const pSchedulerDataNext =
417                     DropSchedulerData(rSchedCtx, pPrevSchedulerData, pSchedulerData, nTaskPriority);
418                 if ( pSchedulerData->mpTask )
419                     pSchedulerData->mpTask->mpSchedulerData = nullptr;
420                 delete pSchedulerData;
421                 pSchedulerData = pSchedulerDataNext;
422                 continue;
423             }
424 
425             assert(pSchedulerData->mpTask);
426             if (pSchedulerData->mpTask->IsActive())
427             {
428                 nReadyPeriod = pSchedulerData->mpTask->UpdateMinPeriod( nTime );
429                 if (ImmediateTimeoutMs == nReadyPeriod)
430                 {
431                     if (!pMostUrgent)
432                     {
433                         pPrevMostUrgent = pPrevSchedulerData;
434                         pMostUrgent = pSchedulerData;
435                         nMostUrgentPriority = nTaskPriority;
436                     }
437                     else
438                     {
439                         nMinPeriod = ImmediateTimeoutMs;
440                         break;
441                     }
442                 }
443                 else if (nMinPeriod > nReadyPeriod)
444                     nMinPeriod = nReadyPeriod;
445             }
446 
447             pPrevSchedulerData = pSchedulerData;
448             pSchedulerData = pSchedulerData->mpNext;
449         }
450 
451         if (ImmediateTimeoutMs == nMinPeriod)
452             break;
453     }
454 
455     if (InfiniteTimeoutMs != nMinPeriod)
456         SAL_INFO("vcl.schedule",
457                  "Calculated minimum timeout as " << nMinPeriod << " of " << nTasks << " tasks");
458     UpdateSystemTimer(rSchedCtx, nMinPeriod, true, nTime);
459 
460     if ( !pMostUrgent )
461         return;
462 
463     SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
464               << pMostUrgent << "  invoke-in  " << *pMostUrgent->mpTask );
465 
466     Task *pTask = pMostUrgent->mpTask;
467 
468     comphelper::ProfileZone aZone( pTask->GetDebugName() );
469 
470     assert(!pMostUrgent->mbInScheduler);
471     pMostUrgent->mbInScheduler = true;
472 
473     // always push the stack, as we don't traverse the whole list to push later
474     DropSchedulerData(rSchedCtx, pPrevMostUrgent, pMostUrgent, nMostUrgentPriority);
475     pMostUrgent->mpNext = rSchedCtx.mpSchedulerStack;
476     rSchedCtx.mpSchedulerStack = pMostUrgent;
477     rSchedCtx.mpSchedulerStackTop = pMostUrgent;
478 
479     bool bIsHighPriorityIdle = pMostUrgent->mePriority >= TaskPriority::HIGH_IDLE;
480 
481     // invoke the task
482     Unlock();
483 
484     // Delay invoking tasks with idle priorities as long as there are user input or repaint events
485     // in the OS event queue. This will often effectively compress such events and repaint only
486     // once at the end, improving performance in cases such as repeated zooming with a complex document.
487     bool bDelayInvoking
488         = bIsHighPriorityIdle
489           && (rSchedCtx.mnIdlesLockCount > 0
490               || Application::AnyInput(VclInputFlags::MOUSE | VclInputFlags::KEYBOARD | VclInputFlags::PAINT));
491 
492     /*
493     * Current policy is that scheduler tasks aren't allowed to throw an exception.
494     * Because otherwise the exception is caught somewhere totally unrelated.
495     * TODO Ideally we could capture a proper backtrace and feed this into breakpad,
496     *   which is do-able, but requires writing some assembly.
497     * See also SalUserEventList::DispatchUserEvents
498     */
499     try
500     {
501         if (bDelayInvoking)
502             SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks()
503                       << " idle priority task " << pTask->GetDebugName()
504                       << " delayed, system events pending" );
505         else
506         {
507             // prepare Scheduler object for deletion after handling
508             pTask->SetDeletionFlags();
509             pTask->Invoke();
510         }
511     }
512     catch (css::uno::Exception&)
513     {
514         TOOLS_WARN_EXCEPTION("vcl.schedule", "Uncaught");
515         std::abort();
516     }
517     catch (std::exception& e)
518     {
519         SAL_WARN("vcl.schedule", "Uncaught " << typeid(e).name() << " " << e.what());
520         std::abort();
521     }
522     catch (...)
523     {
524         SAL_WARN("vcl.schedule", "Uncaught exception during Task::Invoke()!");
525         std::abort();
526     }
527     Lock();
528 
529     assert(pMostUrgent->mbInScheduler);
530     pMostUrgent->mbInScheduler = false;
531 
532     SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " "
533               << pMostUrgent << "  invoke-out" );
534 
535     // pop the scheduler stack
536     pSchedulerData = rSchedCtx.mpSchedulerStack;
537     assert(pSchedulerData == pMostUrgent);
538     rSchedCtx.mpSchedulerStack = pSchedulerData->mpNext;
539 
540     // coverity[check_after_deref : FALSE] - pMostUrgent->mpTask is initially pMostUrgent->mpTask, but Task::Invoke can clear it
541     const bool bTaskAlive = pMostUrgent->mpTask && pMostUrgent->mpTask->IsActive();
542     if (!bTaskAlive)
543     {
544         if (pMostUrgent->mpTask)
545             pMostUrgent->mpTask->mpSchedulerData = nullptr;
546         delete pMostUrgent;
547     }
548     else
549         AppendSchedulerData(rSchedCtx, pMostUrgent);
550 
551     // this just happens for nested calls, which renders all accounting
552     // invalid, so we just enforce a rescheduling!
553     if (rSchedCtx.mpSchedulerStackTop != pSchedulerData)
554     {
555         UpdateSystemTimer( rSchedCtx, ImmediateTimeoutMs, true,
556                            tools::Time::GetSystemTicks() );
557     }
558     else if (bTaskAlive)
559     {
560         pMostUrgent->mnUpdateTime = nTime;
561         nReadyPeriod = pMostUrgent->mpTask->UpdateMinPeriod( nTime );
562         if ( nMinPeriod > nReadyPeriod )
563             nMinPeriod = nReadyPeriod;
564         UpdateSystemTimer( rSchedCtx, nMinPeriod, false, nTime );
565     }
566 }
567 
Wakeup()568 void Scheduler::Wakeup()
569 {
570     Scheduler::ImplStartTimer( 0, false, tools::Time::GetSystemTicks() );
571 }
572 
StartTimer(sal_uInt64 nMS)573 void Task::StartTimer( sal_uInt64 nMS )
574 {
575     Scheduler::ImplStartTimer( nMS, false, tools::Time::GetSystemTicks() );
576 }
577 
SetDeletionFlags()578 void Task::SetDeletionFlags()
579 {
580     mbActive = false;
581 }
582 
Start(const bool bStartTimer)583 void Task::Start(const bool bStartTimer)
584 {
585     ImplSVData *const pSVData = ImplGetSVData();
586     ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx;
587 
588     SchedulerGuard aSchedulerGuard;
589     if ( !rSchedCtx.mbActive )
590         return;
591 
592     // is the task scheduled in the correct priority queue?
593     // if not we have to get a new data object, as we don't want to traverse
594     // the whole list to move the data to the correct list, as the task list
595     // is just single linked.
596     // Task priority doesn't change that often AFAIK, or we might need to
597     // start caching ImplSchedulerData objects.
598     if (mpSchedulerData && mpSchedulerData->mePriority != mePriority)
599     {
600         mpSchedulerData->mpTask = nullptr;
601         mpSchedulerData = nullptr;
602     }
603     mbActive = true;
604 
605     if ( !mpSchedulerData )
606     {
607         // insert Task
608         ImplSchedulerData* pSchedulerData = new ImplSchedulerData;
609         pSchedulerData->mpTask            = this;
610         pSchedulerData->mbInScheduler     = false;
611         // mePriority is set in AppendSchedulerData
612         mpSchedulerData = pSchedulerData;
613 
614         AppendSchedulerData( rSchedCtx, pSchedulerData );
615         SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks()
616                   << " " << mpSchedulerData << "  added      " << *this );
617     }
618     else
619         SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks()
620                   << " " << mpSchedulerData << "  restarted  " << *this );
621 
622     mpSchedulerData->mnUpdateTime  = tools::Time::GetSystemTicks();
623 
624     if (bStartTimer)
625         Task::StartTimer(0);
626 }
627 
Stop()628 void Task::Stop()
629 {
630     SAL_INFO_IF( mbActive, "vcl.schedule", tools::Time::GetSystemTicks()
631                   << " " << mpSchedulerData << "  stopped    " << *this );
632     mbActive = false;
633 }
634 
SetPriority(TaskPriority ePriority)635 void Task::SetPriority(TaskPriority ePriority)
636 {
637     // you don't actually need to call Stop() before but Start() after, but we
638     // can't check that and don't know when Start() should be called.
639     SAL_WARN_IF(mpSchedulerData && mbActive, "vcl.schedule",
640                 "Stop the task before changing the priority, as it will just "
641                 "change after the task was scheduled with the old prio!");
642     mePriority = ePriority;
643 }
644 
operator =(const Task & rTask)645 Task& Task::operator=( const Task& rTask )
646 {
647     if(this == &rTask)
648         return *this;
649 
650     if ( IsActive() )
651         Stop();
652 
653     mbActive = false;
654     mePriority = rTask.mePriority;
655 
656     if ( rTask.IsActive() )
657         Start();
658 
659     return *this;
660 }
661 
Task(const char * pDebugName)662 Task::Task( const char *pDebugName )
663     : mpSchedulerData( nullptr )
664     , mpDebugName( pDebugName )
665     , mePriority( TaskPriority::DEFAULT )
666     , mbActive( false )
667     , mbStatic( false )
668 {
669     assert(mpDebugName);
670 }
671 
Task(const Task & rTask)672 Task::Task( const Task& rTask )
673     : mpSchedulerData( nullptr )
674     , mpDebugName( rTask.mpDebugName )
675     , mePriority( rTask.mePriority )
676     , mbActive( false )
677     , mbStatic( false )
678 {
679     assert(mpDebugName);
680     if ( rTask.IsActive() )
681         Start();
682 }
683 
~Task()684 Task::~Task() COVERITY_NOEXCEPT_FALSE
685 {
686     if ( !IsStatic() )
687     {
688         SchedulerGuard aSchedulerGuard;
689         if ( mpSchedulerData )
690             mpSchedulerData->mpTask = nullptr;
691     }
692     else
693         assert(nullptr == mpSchedulerData || comphelper::IsFuzzing());
694 }
695 
696 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
697