/* -*- 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/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { template< typename charT, typename traits > std::basic_ostream & operator <<( std::basic_ostream & stream, const Task& task ) { stream << "a: " << task.IsActive() << " p: " << static_cast(task.GetPriority()); const char *name = task.GetDebugName(); if( nullptr == name ) return stream << " (nullptr)"; else return stream << " " << name; } /** * clang won't compile this in the Timer.hxx header, even with a class Idle * forward definition, due to the incomplete Idle type in the template. * Currently the code is just used in the Scheduler, so we keep it local. * * @see http://clang.llvm.org/compatibility.html#undep_incomplete */ template< typename charT, typename traits > std::basic_ostream & operator <<( std::basic_ostream & stream, const Timer& timer ) { bool bIsIdle = (dynamic_cast( &timer ) != nullptr); stream << (bIsIdle ? "Idle " : "Timer") << " a: " << timer.IsActive() << " p: " << static_cast(timer.GetPriority()); const char *name = timer.GetDebugName(); if ( nullptr == name ) stream << " (nullptr)"; else stream << " " << name; if ( !bIsIdle ) stream << " " << timer.GetTimeout() << "ms"; stream << " (" << &timer << ")"; return stream; } template< typename charT, typename traits > std::basic_ostream & operator <<( std::basic_ostream & stream, const Idle& idle ) { return stream << static_cast( &idle ); } template< typename charT, typename traits > std::basic_ostream & operator <<( std::basic_ostream & stream, const ImplSchedulerData& data ) { stream << " i: " << data.mbInScheduler; return stream; } } // end anonymous namespace unsigned int TaskStopwatch::m_nTimeSlice = TaskStopwatch::nDefaultTimeSlice; void Scheduler::ImplDeInitScheduler() { ImplSVData* pSVData = ImplGetSVData(); assert( pSVData != nullptr ); ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx; DBG_TESTSOLARMUTEX(); SchedulerGuard aSchedulerGuard; int nTaskPriority = 0; #if OSL_DEBUG_LEVEL > 0 sal_uInt32 nTasks = 0; for (nTaskPriority = 0; nTaskPriority < PRIO_COUNT; ++nTaskPriority) { ImplSchedulerData* pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority]; while ( pSchedulerData ) { ++nTasks; pSchedulerData = pSchedulerData->mpNext; } } SAL_INFO( "vcl.schedule.deinit", "DeInit the scheduler - pending tasks: " << nTasks ); // clean up all Idles Unlock(); ProcessEventsToIdle(); Lock(); #endif rSchedCtx.mbActive = false; assert( nullptr == rSchedCtx.mpSchedulerStack || (!rSchedCtx.mpSchedulerStack->mpTask && !rSchedCtx.mpSchedulerStack->mpNext) ); if (rSchedCtx.mpSalTimer) rSchedCtx.mpSalTimer->Stop(); delete rSchedCtx.mpSalTimer; rSchedCtx.mpSalTimer = nullptr; #if OSL_DEBUG_LEVEL > 0 sal_uInt32 nActiveTasks = 0, nIgnoredTasks = 0; #endif nTaskPriority = 0; ImplSchedulerData* pSchedulerData = nullptr; next_priority: pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority]; while ( pSchedulerData ) { Task *pTask = pSchedulerData->mpTask; if ( pTask ) { if ( pTask->mbActive ) { #if OSL_DEBUG_LEVEL > 0 const char *sIgnored = ""; ++nActiveTasks; // TODO: shutdown these timers before Scheduler de-init // TODO: remove Task from static object if ( pTask->GetDebugName() && ( false || !strcmp( pTask->GetDebugName(), "desktop::Desktop m_firstRunTimer" ) || !strcmp( pTask->GetDebugName(), "DrawWorkStartupTimer" ) || !strcmp( pTask->GetDebugName(), "editeng::ImpEditEngine aOnlineSpellTimer" ) || !strcmp( pTask->GetDebugName(), "sc ScModule IdleTimer" ) || !strcmp( pTask->GetDebugName(), "sd::CacheConfiguration maReleaseTimer" ) || !strcmp( pTask->GetDebugName(), "svtools::GraphicCache maReleaseTimer" ) || !strcmp( pTask->GetDebugName(), "svtools::GraphicObject mpSwapOutTimer" ) || !strcmp( pTask->GetDebugName(), "svx OLEObjCache pTimer UnloadCheck" ) || !strcmp( pTask->GetDebugName(), "vcl SystemDependentDataBuffer aSystemDependentDataBuffer" ) )) { sIgnored = " (ignored)"; ++nIgnoredTasks; } const Timer *timer = dynamic_cast( pTask ); if ( timer ) SAL_WARN( "vcl.schedule.deinit", "DeInit task: " << *timer << sIgnored ); else SAL_WARN( "vcl.schedule.deinit", "DeInit task: " << *pTask << sIgnored ); #endif pTask->mbActive = false; } pTask->mpSchedulerData = nullptr; pTask->SetStatic(); } ImplSchedulerData* pDeleteSchedulerData = pSchedulerData; pSchedulerData = pSchedulerData->mpNext; delete pDeleteSchedulerData; } ++nTaskPriority; if (nTaskPriority < PRIO_COUNT) goto next_priority; #if OSL_DEBUG_LEVEL > 0 SAL_INFO( "vcl.schedule.deinit", "DeInit the scheduler - finished" ); SAL_WARN_IF( 0 != nActiveTasks, "vcl.schedule.deinit", "DeInit active tasks: " << nActiveTasks << " (ignored: " << nIgnoredTasks << ")" ); // assert( nIgnoredTasks == nActiveTasks ); #endif for (nTaskPriority = 0; nTaskPriority < PRIO_COUNT; ++nTaskPriority) { rSchedCtx.mpFirstSchedulerData[nTaskPriority] = nullptr; rSchedCtx.mpLastSchedulerData[nTaskPriority] = nullptr; } rSchedCtx.mnTimerPeriod = InfiniteTimeoutMs; } void Scheduler::Lock() { ImplSVData* pSVData = ImplGetSVData(); assert( pSVData != nullptr ); pSVData->maSchedCtx.maMutex.lock(); } void Scheduler::Unlock() { ImplSVData* pSVData = ImplGetSVData(); assert( pSVData != nullptr ); pSVData->maSchedCtx.maMutex.unlock(); } /** * Start a new timer if we need to for nMS duration. * * if this is longer than the existing duration we're * waiting for, do nothing - unless bForce - which means * to reset the minimum period; used by the scheduled itself. */ void Scheduler::ImplStartTimer(sal_uInt64 nMS, bool bForce, sal_uInt64 nTime) { ImplSVData* pSVData = ImplGetSVData(); ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx; if ( !rSchedCtx.mbActive ) return; if (!rSchedCtx.mpSalTimer) { rSchedCtx.mnTimerStart = 0; rSchedCtx.mnTimerPeriod = InfiniteTimeoutMs; rSchedCtx.mpSalTimer = pSVData->mpDefInst->CreateSalTimer(); rSchedCtx.mpSalTimer->SetCallback(Scheduler::CallbackTaskScheduling); } assert(SAL_MAX_UINT64 - nMS >= nTime); sal_uInt64 nProposedTimeout = nTime + nMS; sal_uInt64 nCurTimeout = ( rSchedCtx.mnTimerPeriod == InfiniteTimeoutMs ) ? SAL_MAX_UINT64 : rSchedCtx.mnTimerStart + rSchedCtx.mnTimerPeriod; // Only if smaller timeout, to avoid skipping. // Force instant wakeup on 0ms, if the previous period was not 0ms if (bForce || nProposedTimeout < nCurTimeout || (!nMS && rSchedCtx.mnTimerPeriod)) { SAL_INFO( "vcl.schedule", " Starting scheduler system timer (" << nMS << "ms)" ); rSchedCtx.mnTimerStart = nTime; rSchedCtx.mnTimerPeriod = nMS; rSchedCtx.mpSalTimer->Start( nMS ); } } static bool g_bDeterministicMode = false; void Scheduler::SetDeterministicMode(bool bDeterministic) { g_bDeterministicMode = bDeterministic; } bool Scheduler::GetDeterministicMode() { return g_bDeterministicMode; } Scheduler::IdlesLockGuard::IdlesLockGuard() { ImplSVData* pSVData = ImplGetSVData(); ImplSchedulerContext& rSchedCtx = pSVData->maSchedCtx; osl_atomic_increment(&rSchedCtx.mnIdlesLockCount); if (!Application::IsMainThread()) { // Make sure that main thread has reached the main message loop, so no idles are executing. // It is important to ensure this, because e.g. ProcessEventsToIdle could be executed in a // nested event loop, while an active processed idle in the main thread is waiting for some // condition to proceed. Only main thread returning to Application::Execute guarantees that // the flag really took effect. pSVData->m_inExecuteCondtion.reset(); // Put an empty event to the application's queue, to make sure that it loops through the // code that sets the condition, even when there's no other events in the queue Application::PostUserEvent({}); SolarMutexReleaser releaser; pSVData->m_inExecuteCondtion.wait(); } } Scheduler::IdlesLockGuard::~IdlesLockGuard() { ImplSchedulerContext& rSchedCtx = ImplGetSVData()->maSchedCtx; osl_atomic_decrement(&rSchedCtx.mnIdlesLockCount); } inline void Scheduler::UpdateSystemTimer( ImplSchedulerContext &rSchedCtx, const sal_uInt64 nMinPeriod, const bool bForce, const sal_uInt64 nTime ) { if ( InfiniteTimeoutMs == nMinPeriod ) { SAL_INFO("vcl.schedule", " Stopping system timer"); if ( rSchedCtx.mpSalTimer ) rSchedCtx.mpSalTimer->Stop(); rSchedCtx.mnTimerPeriod = nMinPeriod; } else Scheduler::ImplStartTimer( nMinPeriod, bForce, nTime ); } static void AppendSchedulerData( ImplSchedulerContext &rSchedCtx, ImplSchedulerData * const pSchedulerData) { assert(pSchedulerData->mpTask); pSchedulerData->mePriority = pSchedulerData->mpTask->GetPriority(); pSchedulerData->mpNext = nullptr; const int nTaskPriority = static_cast(pSchedulerData->mePriority); if (!rSchedCtx.mpLastSchedulerData[nTaskPriority]) { rSchedCtx.mpFirstSchedulerData[nTaskPriority] = pSchedulerData; rSchedCtx.mpLastSchedulerData[nTaskPriority] = pSchedulerData; } else { rSchedCtx.mpLastSchedulerData[nTaskPriority]->mpNext = pSchedulerData; rSchedCtx.mpLastSchedulerData[nTaskPriority] = pSchedulerData; } } static ImplSchedulerData* DropSchedulerData( ImplSchedulerContext &rSchedCtx, ImplSchedulerData * const pPrevSchedulerData, const ImplSchedulerData * const pSchedulerData, const int nTaskPriority) { assert( pSchedulerData ); if ( pPrevSchedulerData ) assert( pPrevSchedulerData->mpNext == pSchedulerData ); else assert(rSchedCtx.mpFirstSchedulerData[nTaskPriority] == pSchedulerData); ImplSchedulerData * const pSchedulerDataNext = pSchedulerData->mpNext; if ( pPrevSchedulerData ) pPrevSchedulerData->mpNext = pSchedulerDataNext; else rSchedCtx.mpFirstSchedulerData[nTaskPriority] = pSchedulerDataNext; if ( !pSchedulerDataNext ) rSchedCtx.mpLastSchedulerData[nTaskPriority] = pPrevSchedulerData; return pSchedulerDataNext; } void Scheduler::CallbackTaskScheduling() { ImplSVData *pSVData = ImplGetSVData(); ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx; DBG_TESTSOLARMUTEX(); SchedulerGuard aSchedulerGuard; if ( !rSchedCtx.mbActive || InfiniteTimeoutMs == rSchedCtx.mnTimerPeriod ) return; sal_uInt64 nTime = tools::Time::GetSystemTicks(); // Allow for decimals, so subtract in the compare (needed at least on iOS) if ( nTime < rSchedCtx.mnTimerStart + rSchedCtx.mnTimerPeriod -1) { int nSleep = rSchedCtx.mnTimerStart + rSchedCtx.mnTimerPeriod - nTime; UpdateSystemTimer(rSchedCtx, nSleep, true, nTime); return; } ImplSchedulerData* pSchedulerData = nullptr; ImplSchedulerData* pPrevSchedulerData = nullptr; ImplSchedulerData *pMostUrgent = nullptr; ImplSchedulerData *pPrevMostUrgent = nullptr; int nMostUrgentPriority = 0; sal_uInt64 nMinPeriod = InfiniteTimeoutMs; sal_uInt64 nReadyPeriod = InfiniteTimeoutMs; unsigned nTasks = 0; int nTaskPriority = 0; for (; nTaskPriority < PRIO_COUNT; ++nTaskPriority) { // Related: tdf#152703 Eliminate potential blocking during live resize // Only higher priority tasks need to be fired to redraw the window // so skip firing potentially long-running tasks, such as the Writer // idle layout timer, when a window is in live resize if ( nTaskPriority == static_cast(TaskPriority::LOWEST) && ( ImplGetSVData()->mpWinData->mbIsLiveResize || ImplGetSVData()->mpWinData->mbIsWaitingForNativeEvent ) ) continue; pSchedulerData = rSchedCtx.mpFirstSchedulerData[nTaskPriority]; pPrevSchedulerData = nullptr; while (pSchedulerData) { ++nTasks; const Timer *timer = dynamic_cast( pSchedulerData->mpTask ); if ( timer ) SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " " << pSchedulerData << " " << *pSchedulerData << " " << *timer ); else if ( pSchedulerData->mpTask ) SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " " << pSchedulerData << " " << *pSchedulerData << " " << *pSchedulerData->mpTask ); else SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " " << pSchedulerData << " " << *pSchedulerData << " (to be deleted)" ); // Should the Task be released from scheduling? assert(!pSchedulerData->mbInScheduler); if (!pSchedulerData->mpTask || !pSchedulerData->mpTask->IsActive()) { ImplSchedulerData * const pSchedulerDataNext = DropSchedulerData(rSchedCtx, pPrevSchedulerData, pSchedulerData, nTaskPriority); if ( pSchedulerData->mpTask ) pSchedulerData->mpTask->mpSchedulerData = nullptr; delete pSchedulerData; pSchedulerData = pSchedulerDataNext; continue; } assert(pSchedulerData->mpTask); if (pSchedulerData->mpTask->IsActive()) { nReadyPeriod = pSchedulerData->mpTask->UpdateMinPeriod( nTime ); if (ImmediateTimeoutMs == nReadyPeriod) { if (!pMostUrgent) { pPrevMostUrgent = pPrevSchedulerData; pMostUrgent = pSchedulerData; nMostUrgentPriority = nTaskPriority; } else { nMinPeriod = ImmediateTimeoutMs; break; } } else if (nMinPeriod > nReadyPeriod) nMinPeriod = nReadyPeriod; } pPrevSchedulerData = pSchedulerData; pSchedulerData = pSchedulerData->mpNext; } if (ImmediateTimeoutMs == nMinPeriod) break; } if (InfiniteTimeoutMs != nMinPeriod) SAL_INFO("vcl.schedule", "Calculated minimum timeout as " << nMinPeriod << " of " << nTasks << " tasks"); UpdateSystemTimer(rSchedCtx, nMinPeriod, true, nTime); if ( !pMostUrgent ) return; SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " " << pMostUrgent << " invoke-in " << *pMostUrgent->mpTask ); Task *pTask = pMostUrgent->mpTask; comphelper::ProfileZone aZone( pTask->GetDebugName() ); assert(!pMostUrgent->mbInScheduler); pMostUrgent->mbInScheduler = true; // always push the stack, as we don't traverse the whole list to push later DropSchedulerData(rSchedCtx, pPrevMostUrgent, pMostUrgent, nMostUrgentPriority); pMostUrgent->mpNext = rSchedCtx.mpSchedulerStack; rSchedCtx.mpSchedulerStack = pMostUrgent; rSchedCtx.mpSchedulerStackTop = pMostUrgent; bool bIsHighPriorityIdle = pMostUrgent->mePriority >= TaskPriority::HIGH_IDLE; // invoke the task Unlock(); // Delay invoking tasks with idle priorities as long as there are user input or repaint events // in the OS event queue. This will often effectively compress such events and repaint only // once at the end, improving performance in cases such as repeated zooming with a complex document. bool bDelayInvoking = bIsHighPriorityIdle && (rSchedCtx.mnIdlesLockCount > 0 || Application::AnyInput(VclInputFlags::MOUSE | VclInputFlags::KEYBOARD | VclInputFlags::PAINT)); /* * Current policy is that scheduler tasks aren't allowed to throw an exception. * Because otherwise the exception is caught somewhere totally unrelated. * TODO Ideally we could capture a proper backtrace and feed this into breakpad, * which is do-able, but requires writing some assembly. * See also SalUserEventList::DispatchUserEvents */ try { if (bDelayInvoking) SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " idle priority task " << pTask->GetDebugName() << " delayed, system events pending" ); else { // prepare Scheduler object for deletion after handling pTask->SetDeletionFlags(); pTask->Invoke(); } } catch (css::uno::Exception&) { TOOLS_WARN_EXCEPTION("vcl.schedule", "Uncaught"); std::abort(); } catch (std::exception& e) { SAL_WARN("vcl.schedule", "Uncaught " << typeid(e).name() << " " << e.what()); std::abort(); } catch (...) { SAL_WARN("vcl.schedule", "Uncaught exception during Task::Invoke()!"); std::abort(); } Lock(); assert(pMostUrgent->mbInScheduler); pMostUrgent->mbInScheduler = false; SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " " << pMostUrgent << " invoke-out" ); // pop the scheduler stack pSchedulerData = rSchedCtx.mpSchedulerStack; assert(pSchedulerData == pMostUrgent); rSchedCtx.mpSchedulerStack = pSchedulerData->mpNext; // coverity[check_after_deref : FALSE] - pMostUrgent->mpTask is initially pMostUrgent->mpTask, but Task::Invoke can clear it const bool bTaskAlive = pMostUrgent->mpTask && pMostUrgent->mpTask->IsActive(); if (!bTaskAlive) { if (pMostUrgent->mpTask) pMostUrgent->mpTask->mpSchedulerData = nullptr; delete pMostUrgent; } else AppendSchedulerData(rSchedCtx, pMostUrgent); // this just happens for nested calls, which renders all accounting // invalid, so we just enforce a rescheduling! if (rSchedCtx.mpSchedulerStackTop != pSchedulerData) { UpdateSystemTimer( rSchedCtx, ImmediateTimeoutMs, true, tools::Time::GetSystemTicks() ); } else if (bTaskAlive) { pMostUrgent->mnUpdateTime = nTime; nReadyPeriod = pMostUrgent->mpTask->UpdateMinPeriod( nTime ); if ( nMinPeriod > nReadyPeriod ) nMinPeriod = nReadyPeriod; UpdateSystemTimer( rSchedCtx, nMinPeriod, false, nTime ); } } void Scheduler::Wakeup() { Scheduler::ImplStartTimer( 0, false, tools::Time::GetSystemTicks() ); } void Task::StartTimer( sal_uInt64 nMS ) { Scheduler::ImplStartTimer( nMS, false, tools::Time::GetSystemTicks() ); } void Task::SetDeletionFlags() { mbActive = false; } void Task::Start(const bool bStartTimer) { ImplSVData *const pSVData = ImplGetSVData(); ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx; SchedulerGuard aSchedulerGuard; if ( !rSchedCtx.mbActive ) return; // is the task scheduled in the correct priority queue? // if not we have to get a new data object, as we don't want to traverse // the whole list to move the data to the correct list, as the task list // is just single linked. // Task priority doesn't change that often AFAIK, or we might need to // start caching ImplSchedulerData objects. if (mpSchedulerData && mpSchedulerData->mePriority != mePriority) { mpSchedulerData->mpTask = nullptr; mpSchedulerData = nullptr; } mbActive = true; if ( !mpSchedulerData ) { // insert Task ImplSchedulerData* pSchedulerData = new ImplSchedulerData; pSchedulerData->mpTask = this; pSchedulerData->mbInScheduler = false; // mePriority is set in AppendSchedulerData mpSchedulerData = pSchedulerData; AppendSchedulerData( rSchedCtx, pSchedulerData ); SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " " << mpSchedulerData << " added " << *this ); } else SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " " << mpSchedulerData << " restarted " << *this ); mpSchedulerData->mnUpdateTime = tools::Time::GetSystemTicks(); if (bStartTimer) Task::StartTimer(0); } void Task::Stop() { SAL_INFO_IF( mbActive, "vcl.schedule", tools::Time::GetSystemTicks() << " " << mpSchedulerData << " stopped " << *this ); mbActive = false; } void Task::SetPriority(TaskPriority ePriority) { // you don't actually need to call Stop() before but Start() after, but we // can't check that and don't know when Start() should be called. SAL_WARN_IF(mpSchedulerData && mbActive, "vcl.schedule", "Stop the task before changing the priority, as it will just " "change after the task was scheduled with the old prio!"); mePriority = ePriority; } Task& Task::operator=( const Task& rTask ) { if(this == &rTask) return *this; if ( IsActive() ) Stop(); mbActive = false; mePriority = rTask.mePriority; if ( rTask.IsActive() ) Start(); return *this; } Task::Task( const char *pDebugName ) : mpSchedulerData( nullptr ) , mpDebugName( pDebugName ) , mePriority( TaskPriority::DEFAULT ) , mbActive( false ) , mbStatic( false ) { assert(mpDebugName); } Task::Task( const Task& rTask ) : mpSchedulerData( nullptr ) , mpDebugName( rTask.mpDebugName ) , mePriority( rTask.mePriority ) , mbActive( false ) , mbStatic( false ) { assert(mpDebugName); if ( rTask.IsActive() ) Start(); } Task::~Task() COVERITY_NOEXCEPT_FALSE { if ( !IsStatic() ) { SchedulerGuard aSchedulerGuard; if ( mpSchedulerData ) mpSchedulerData->mpTask = nullptr; } else assert(nullptr == mpSchedulerData || comphelper::IsFuzzing()); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */