From c90d7da5b744eaeeb3d580682092ccf46449372a Mon Sep 17 00:00:00 2001 From: Caolán McNamara Date: Jan 18 2018 12:34:52 +0000 Subject: Resolves: rhbz#1534149 glib2-2.54.3 >= triggers 100% cpu --- diff --git a/0001-backport-single-shot-timer-scheduler-changes.patch b/0001-backport-single-shot-timer-scheduler-changes.patch new file mode 100644 index 0000000..7e0b97e --- /dev/null +++ b/0001-backport-single-shot-timer-scheduler-changes.patch @@ -0,0 +1,5238 @@ +From c7a22de8dbab22dfbedb9dc5197dccf1148de684 Mon Sep 17 00:00:00 2001 +From: Jan-Marek Glogowski +Date: Mon, 23 Jan 2017 11:56:41 +0100 +Subject: [PATCH] backport single shot timer scheduler changes +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add some Scheduler unit tests and logging + + 1. calling Start() for invoked tasks + 2. correctly schedule by priority + 3. self-stopping AutoTimer + +This also adds SAL_INFO output to Scheduler and Task to log the +scheduling processing tasks. + +(cherry picked from commit c0710abfebd320903a3edb23d4b1441ea351b0be) + +Refactor Scheduler global data + +Move all Scheduler members of ImplSVData into ImplSchedulerContext +and make ImplSchedulerContext a member of ImplSVData. + +(cherry picked from commit d72aad218c9737fb19d1a835b03c13b7107a96c0) + +Move scheduler task into its own header + +(cherry picked from commit b9ae1505e36157775b1002fdbd178d1c90cd91a9) + +Refactor Scheduler by merging functions + +Merges ImplSchedulerData::Invoke() into ProcessTaskScheduling() +and removes indention levels in CalculateMinimumTimeout by using +goto. This is straight forward. + +(cherry picked from commit eb2035fe20bdebf0f8c926aeb66553fd9db3e6f4) + +Just walk the task list once + +This refactors some more scheduler code by merging the code from +ProcessTaskScheduling into CalculateMinimumTimeout and keeping the +ProcessTaskScheduling name. + +It replace the bHasPendingIdles information with a HasPendingTasks +function, based on the sleep timeout and invoke time. + +To drop IsIdle() we simply account Idles again and stop the system +timer, so we instantly run our application loop again. This makes +all Tasks really run immediate instead of waiting 1ms. + +(cherry picked from commit 66a38d26b0c8de82daacfb0c0ce7d014dc083090) + +Change scheduler list to be queue-like + +The scheduler modifies the SchedulerData list mainly in two ways: + 1. Remove a finished item + 2. Append a new item + +This optimizes the Append operation by keeping a last element +pointer, so we don't have to walk the whole list to find it. This +way this list is converted to a queue-like structure. + +(cherry picked from commit 1782893282a4543e946e6b2c8de863b10fab0c85) + +Drop special idle handling + +Idles are just instant timers, which should most time have a low +priority, By dropping most special idle handling we'll just +schedule by priority. + +This also reverts SalYieldResult back to a bool, which just +indicates if any event was processed. + +(cherry picked from commit d348035a60361a1b9ba9eb7b67013204a24a6633) + +Correctly account starting tasks + +When (re-)starting the system timer for new task, we have to take +the already passed time into account to correctly set the new +timeout time for the system timer. + +(cherry picked from commit d93acb77667ecdb78b53a1be626ca2e000813828) + +Introduce a scheduler stack + +While the stack removes all invoked tasks from the queue, which +actively removes it from scheduling, it also helps to faster handle +nested calls, as we don't have to look for the previous position +to move the task to the end of the queue for the round robin. + +(cherry picked from commit b22526b8ad88f5061b1901914e280ba9c8db1fe7) + +Correctly account invoked task + +Don't account the to-be invoked task before invoking it. +If it happens to be restarted, account it and eventually lower +the system timer timeout. + +(cherry picked from commit 23beae53b43484d82949019a3279362c7e1dfb4b) + +Round-robin invoked tasks + +Add some round-robin to the task processing, so equal priority +(auto) tasks won't always be scheduled, if there are multiple +tasks with the same priority. + +(cherry picked from commit 917be98e3f277960635ac66bcea510c2454c80d6) + +SVP correctly handle sleep time + +Don't add 500 usec for 0ms sleeps and always round up the sleep +time so we won't sleep too short. + +(cherry picked from commit 503eba23c9a199583eddee9e169a4fddbecf416f) + +Run Idle tasks immediatly + +There is really no reason to wait a millisecond for an idle. + +(cherry picked from commit 7a1c1699a61a77d0228417da9922812c9b893b9d) + +Restart the system timer if it returns to early + +At least on Windows our GetSystemTicks() implementation - using +QueryPerformanceCounter - occasionally states, the timer returned +too early, which stops processing further LO events. + +As a workaround we restart the timer, as it's now the only source +of running the LO main loop. + +Probably we should use osl_getSystemTime, for measuring, which uses +GetSystemTimePreciseAsFileTime, if available. Nothing states +anything is monotonic, so in this regard it may be flawed too. + +(cherry picked from commit e613e2ebcd64bbe8a5cdc35750e49e773c5b03f4) + +KDE4 change system timer to be single-shot + +(cherry picked from commit be04f9e3dcbab2dacb63cfaa2e787db9f1aa16dc) + +KDE4 handle timers via queued custom events + +Post the system timer as a custom event to the event queue and also +changes the PostUserEvent froom a timer to a posted custom event. + +(cherry picked from commit 1fedc1c38a141bf071c707fda6a3ae1a5bf8fd41) + +GTK+ simplifiy system timer implementation + +Instead of implementing an own GSource, this implements the glib +based system timer using the g_timeout_source_new() function. + +It removes the vector of GtkSalTimer and changes the remaining +timer to be single-shot, just like the Windows and KDE platforms. + +The ownership handling is a little bit strange and should generally +be changed to use std::shared_ptr as the result of CreateSalTimer +for all backends. + +(cherry picked from commit 3e20ce802ee2ab49c4f2a98880f6e999657686bb) + +WIN cleanup paint message handling + +Makes PAINT handling function use the POSTPAINT handling +function and unifies the locking behaviour, so we now check +the frame before trying to aquire the lock in both functions. + +(cherry picked from commit 51cc064661417134eb4e9c74d2d38d4e1b4e8e0e) + +WIN unify deferred message handling + +Moves the common code into ProcessOrDeferMessage. + +All callers (try to) aquire the mutex before getting the +WinSalFrame pointer, except for ImplHandleDeferredPaintMsg. + +This was probably an oversight, so this reverses the order in this +function, so the GetWindowPtr is now always protected by the Solar +mutex. + +(cherry picked from commit 46c33bf281f7288c8c48ca7e1a2b7c4ad32123a8) + +WIN simplify system timer / LO event handling + +This removes a level of indirection for the timer callback handling. +It also ensures we just have a single timeout message queued. + +Also drops the 16bit MAX duration limit, as CreateTimerQueueTimer +uses a DWORD for the DueTime parameter, which is always UINT32. + +CreateTimerQueueTimer already sets the period to 0, which makes it +a one-shot timer, but there is also the WT_EXECUTEONLYONCE, which +enforces the Period parameter to be 0. + +(cherry picked from commit 5229f15904b42238273d1fa98ca8dcf6efda48f5) + +WIN just wait for the Yield mutex in the timerout + +Don't re-schedule a timeout, simply wait in the timer callback. + +(cherry picked from commit 69977ed917a92d9b07f3429312077357d3aa10fa) + +OSX change to run LO via a single shot timer + +As all other backends, this runs the LO main loop just via the +OSX main loop. + +(cherry picked from commit f0aabfe601223cee214b0be1b2ebf51a80b68f2c) + +OSX fix empty message queue handling + +For some (unknown) reason [NSApp postEvent: ... atStart: NO] +doesn't append the event, if the message queue is empty +(AKA [NSApp nextEventMatchingMask .. ] returns nil). +Due to nextEventMatchingMask usage, these postEvents have to +run in the main thread. + +Using performSelectorOnMainThread deadlocks, since the calling +thread may have locked the Yield mutex, so we simply defer the +call using an NSEvent, like the Windows backend. + +So we have to peek at the queue and if it's empty simply prepend +the event using [.. atStart: YES]. + +In the end this make the vcl_timer unit test pass on OSX. + +(cherry picked from commit 272026d70129603e1824b802a2a6920adcd09dc0) + +Run LO scheduler only via system timer + +(cherry picked from commit 52dfefec8da5d7f25c39218fd890cad6491728ab) + +Remove duplicated delete information + +Task::mbActive already stores the inverse information of +ImplSchedulerData::mbDelete, so we can drop the latter one. + +(cherry picked from commit b4cc0f2d3fb9cc85e5ea7b157ec35b01c1868d50) + +Drop Task::ReadyForSchedule + +All relevant information is also provided by UpdateMinPeriod and +the calculations were even duplicated. This also includes dropping +Scheduler::UpdateMinPeriod, as this is now reduced to a simple +comparison and assignment, as we simply ignore larger returned +sleep times. + +(cherry picked from commit 11ffb51b758cd18a2c61d4bfa694f9f031ecd096) + +Reorganize Scheduler priority classes + +This is based on glibs classification of tasks, but while glib uses +an int for more fine grained priority, we stay with our enum. + +1. Timers start with DEFAULT priority, which directly corresponds + with the previous HIGH priority +2. Idles start with DEFAULT_IDLE priority instead of the previous + HIGH priority, so idle default becomes "really run when idle". + +As RESIZE and REPAINT are special, and the DEFAULTS are set, there +is just one primary decision for the programmer: should my idle +run before paint (AKA HIGH_IDLE)? + +If we really need a more fine-grained classification, we can add it +later, or also switch to a real int. As a result, this drops many +classifications from the code and drastically changes behaviour, +AKA a mail merge from KDE is now as fast as Gtk+ again. + +(cherry picked from commit 00aa0892e7385cd8395dd39814077958be42e720) + +WIN shorten DoYield by using existing functions + +This also adds an sal_uLong nCount parameter to +ImplSalYieldMutexAcquireWithWait, so it can be called like +ImplSalAcquireYieldMutex and actually uses the result from +the deferred DoYield. + +(cherry picked from commit 1b9d48f59e8a04c3b9d829e150a59fc88084445b) + +Revert "GTK+ simplifiy system timer implementation" + +Current LO baseline doesn't have g_source_get_ready_time, +so we keep the old stuff. + +This reverts commit 3e20ce802ee2ab49c4f2a98880f6e999657686bb. + +(cherry picked from commit 9bc95521aaa35b533894b3934519acdbd7a47815) + +GTK+ convert to a single-shot timer + +Simplified version of commit + 3e20ce802ee2ab49c4f2a98880f6e999657686bb + +Since we're missing g_source_get_ready_time in our baseline, this +can't change the complex implementation, but still drops the timer +array and uses a single-shot timer, like all other backends. + +(cherry picked from commit de0acf13b2dee2f7ad25de43f17acab73f1c7eac) + +Fix instant 0ms scheduler wakeup for headless + +This is a regression from commit + 503eba23c9a199583eddee9e169a4fddbecf416f + +Due to rounding errors, as the Scheduler uses milliseconds, but +the headless backend uses nanoseconds the StartTimer assumed it +would wake up this ms, but the headless check function would still +wait. This is more of a workaround, so instant wakeup for the +headless backend works again. + +(cherry picked from commit 1c8c85e3866b1a6b35baafd3482ff7ef494a0f24) + +WIN annotate GdiPlusBuffer Timer + +(cherry picked from commit f0dae5fb2b0628659a121ca3f9ee9bf9039a49dd) + +WIN revert to Sleep(1) for yield + +There are multiple ways on Windows to yield a thread: + +* SwitchToThread: yields to any thread on same processor +* Sleep(0): yields to any thread of same or higher priority + on any processor +* Sleep(1): yields to any thread on any processor + +So we stay with Sleep(1), as it also seems the only call, which +actually does some sleep and is not a busy wait. + +(cherry picked from commit cd1d5f35cb0f9c455787819de56e24cd51e6c5cc) + +Annotate some more Timers and Idles + +Reviewed-on: https://gerrit.libreoffice.org/40188 +Tested-by: Jenkins +Reviewed-by: Jan-Marek Glogowski +(cherry picked from commit 8d4d1e8d1cd4b400bc395dcbb98fb1e82eaf4e0f) + +SalInstance::DoYield: Don't drop SolarMutex when accessing user event queue + + fails with +the assert in the OSL_DEBUG_LEVEL > 0 test code in +Scheduler::ProcessEventsToIdle firing (see below), indicating that (even though +the SolarMutex is locked one frame up in VCLXToolkit::processEventsToIdle), when +Application::Reschedule(true) returned, there are unprocessed events in the +queue. + +That failure is with the headless plugin, and looking at +SvpSalInstance::DoYield, there was a window of time after m_aEventGuard has been +released and before the SolarMutex has been re-acquired, where other threads +could enqueue events. + +If Application::Reschedule(true) can thus not guarantee that the queue is empty +when it returned, that would mean that the assert in +Scheduler::ProcessEventsToIdle (introduced with +c0710abfebd320903a3edb23d4b1441ea351b0be "Add some Scheduler unit tests and +logging") is bogus. + +However, at least the two SalInstance::DoYield implementations +SvpSalInstance::DoYield and AquaSalInstance::DoYield appear to have no reason +in the first place to drop the SolarMutex when accessing the user event queue. +So removing that dropping should make the assert in +Scheduler::ProcessEventsToIdle faithful, at least for these two implementations. + +But I have no idea whether the other three implementations +(X11SalInstance::DoYield in vcl/unx/generic/app/salinst.cxx, +GtkInstance::DoYield in vcl/unx/gtk/gtkinst.cxx, and WinSalInstance::DoYield in +vcl/win/app/salinst.cxx) have similar issues---i.e., whether everything is good +after this change, whether those other implementations need similar changes, or +whether ultimately Application::Reschedule(true) cannot guarantee the queue to +be empty upon return (in which case the assert in Scheduler::ProcessEventsToIdle +would need to go). + +> warn:vcl.schedule:27575:54:vcl/source/app/svapp.cxx:535: Unprocessed Idle: vcl::Window maPaintIdle +> soffice.bin: /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/vcl/source/app/svapp.cxx:540: static void Scheduler::ProcessEventsToIdle(): Assertion `!bAnyIdle' failed. +[...] +> Thread 2 (Thread 0x2b12bf1e4a00 (LWP 27575)): +> #0 0x00002b12bfb1a69d in poll () at /lib64/libc.so.6 +> #1 0x00002b12ca9129e7 in SvpSalInstance::DoReleaseYield(int) (this=0x1be8b40, nTimeoutMS=5) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/vcl/headless/svpinst.cxx:392 +> aPoll = {fd = 6, events = 1, revents = 0} +> nAcquireCount = 1 +> #2 0x00002b12ca9128c9 in SvpSalInstance::DoYield(bool, bool, unsigned long) (this=0x1be8b40, bWait=true, bHandleAllCurrentEvents=false, nReleased=0) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/vcl/headless/svpinst.cxx:375 +> nTimeoutMS = 5 +> __PRETTY_FUNCTION__ = "virtual bool SvpSalInstance::DoYield(bool, bool, sal_uLong)" +> aEvents = empty std::__debug::list +> nAcquireCount = 1 +> bEvent = false +> #3 0x00002b12ca7a98cd in ImplYield(bool, bool, unsigned long) (i_bWait=true, i_bAllEvents=false, nReleased=0) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/vcl/source/app/svapp.cxx:484 +> pSVData = 0x2b12cb2a9e00 ::get()::instance> +> bProcessedEvent = false +> #4 0x00002b12ca7a5796 in Application::Yield() () at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/vcl/source/app/svapp.cxx:555 +> #5 0x00002b12ca7a52f5 in Application::Execute() () at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/vcl/source/app/svapp.cxx:461 +> pSVData = 0x2b12cb2a9e00 ::get()::instance> +> #6 0x00002b12bf69c7e5 in desktop::Desktop::DoExecute() () at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/desktop/source/app/app.cxx:1350 +> #7 0x00002b12bf69d9c4 in desktop::Desktop::Main() (this=0x7fff49ea9ac0) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/desktop/source/app/app.cxx:1735 +> layer2 = {m_aEnvTypeName = "gcc3", m_xPreviousContext = uno::Reference to (desktop::DesktopContext *) 0x1bebdf8} +> rCmdLineArgs = @0x2b12bfa2c6a0: {m_cwdUrl = boost::optional "file:///home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/workdir/JunitTest/sd_unoapi/user", m_minimized = false, m_invisible = false, m_norestore = true, m_headless = true, m_eventtesting = false, m_quickstart = false, m_noquickstart = true, m_terminateafterinit = false, m_nologo = true, m_nolockcheck = false, m_nodefault = false, m_help = false, m_writer = false, m_calc = false, m_draw = false, m_impress = false, m_global = false, m_math = false, m_web = false, m_base = false, m_helpwriter = false, m_helpcalc = false, m_helpdraw = false, m_helpbasic = false, m_helpmath = false, m_helpimpress = false, m_helpbase = false, m_version = false, m_splashpipe = false, m_textcat = false, m_scriptcat = false, m_safemode = false, m_unknown = "", m_bEmpty = false, m_bDocumentArgs = false, m_accept = std::__debug::vector of length 1, capacity 1 = {"pipe,name=oootest16264f8c-0bcc-4a4f-9c1b-52af72b78eb5;urp"}, m_unaccept = std::__debug::vector of length 0, capacity 0, m_openlist = std::__debug::vector of length 0, capacity 0, m_viewlist = std::__debug::vector of length 0, capacity 0, m_startlist = std::__debug::vector of length 0, capacity 0, m_forceopenlist = std::__debug::vector of length 0, capacity 0, m_forcenewlist = std::__debug::vector of length 0, capacity 0, m_printlist = std::__debug::vector of length 0, capacity 0, m_printtolist = std::__debug::vector of length 0, capacity 0, m_printername = "", m_conversionlist = std::__debug::vector of length 0, capacity 0, m_conversionparams = "", m_conversionout = "", m_infilter = std::__debug::vector of length 0, capacity 0, m_language = "", m_pidfile = ""} +> xRestartManager = uno::Reference to (comphelper::OOfficeRestartManager *) 0x29a7bf8 +> layer = {m_aEnvTypeName = "gcc3", m_xPreviousContext = uno::Reference to (DesktopEnvironmentContext *) 0x1bebc38} +> xContext = uno::Reference to (cppu::ComponentContext *) 0x1d200b0 +> aOptions = { = { = {_vptr.ConfigurationBroadcaster = 0x2b12c8a3dfb0 , mpList = std::unique_ptr >> containing 0x0, m_nBroadcastBlocked = 0, m_nBlockedHint = NONE}, = {_vptr.ConfigurationListener = 0x2b12c8a3dfe8 }, }, = {_vptr.SfxListener = 0x2b12c8a3e010 , mpImpl = std::unique_ptr containing 0x2a46530}, static sm_pSingleImplConfig = 0x2a47e60, static sm_nAccessibilityRefCount = 16} +> eStatus = desktop::Desktop::BS_OK +> aUnknown = "" +> inst_fin = desktop::userinstall::EXISTED +> xDesktop = uno::Reference to (framework::Desktop *) 0x29cb3a8 +> aAppearanceCfg = { = { = {_vptr.ConfigurationBroadcaster = 0x2b12c8a3e090 , mpList = std::unique_ptr >> containing 0x0, m_nBroadcastBlocked = 0, m_nBlockedHint = NONE}, sSubTree = "Office.Common/View", m_xHierarchyAccess = uno::Reference to (configmgr::RootAccess *) 0x29ca3f8, xChangeLstnr = empty uno::Reference, m_nMode = DelayedUpdate, m_bIsModified = false, m_bEnableInternalNotification = false, m_nInValueChange = 0}, nDragMode = SystemDep, nSnapMode = NONE, nMiddleMouse = (unknown: 2), nAAMinPixelHeight = 8, bMenuMouseFollow = true, bFontAntialiasing = true, static bInitialized = true} +> #8 0x00002b12ca7b33d0 in ImplSVMain() () at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/vcl/source/app/svmain.cxx:194 +> pSVData = 0x2b12cb2a9e00 ::get()::instance> +> nReturn = 1 +> bInit = true +> #9 0x00002b12ca7b3539 in SVMain() () at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/vcl/source/app/svmain.cxx:232 +> nRet = 0 +> #10 0x00002b12bf6f1ff1 in soffice_main() () at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/desktop/source/app/sofficemain.cxx:163 +> bSuccess = true +> aDesktop = { = {_vptr.Application = 0x2b12bfa269b0 }, m_rSplashScreen = empty uno::Reference, m_bCleanedExtensionCache = false, m_bServicesRegistered = true, m_aBootstrapError = desktop::Desktop::BE_OK, m_aBootstrapErrorMessage = "", m_aBootstrapStatus = desktop::Desktop::BS_OK, m_xLockfile = std::unique_ptr containing 0x29c9b10, m_firstRunTimer = { = {_vptr.Task = 0x2b12cb2911b0 , mpSchedulerData = 0x0, mpDebugName = 0x2b12bf777570 "desktop::Desktop m_firstRunTimer", mePriority = DEFAULT, mbActive = false}, maInvokeHandler = {function_ = 0x2b12bf6a2c6c , instance_ = 0x7fff49ea9ac0}, mnTimeout = 3000, mbAuto = false}, m_aUpdateThread = {_M_id = {_M_thread = 0}}, static pResMgr = 0x0} +> rCmdLineArgs = @0x2b12bfa2c6a0: {m_cwdUrl = boost::optional "file:///home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/workdir/JunitTest/sd_unoapi/user", m_minimized = false, m_invisible = false, m_norestore = true, m_headless = true, m_eventtesting = false, m_quickstart = false, m_noquickstart = true, m_terminateafterinit = false, m_nologo = true, m_nolockcheck = false, m_nodefault = false, m_help = false, m_writer = false, m_calc = false, m_draw = false, m_impress = false, m_global = false, m_math = false, m_web = false, m_base = false, m_helpwriter = false, m_helpcalc = false, m_helpdraw = false, m_helpbasic = false, m_helpmath = false, m_helpimpress = false, m_helpbase = false, m_version = false, m_splashpipe = false, m_textcat = false, m_scriptcat = false, m_safemode = false, m_unknown = "", m_bEmpty = false, m_bDocumentArgs = false, m_accept = std::__debug::vector of length 1, capacity 1 = {"pipe,name=oootest16264f8c-0bcc-4a4f-9c1b-52af72b78eb5;urp"}, m_unaccept = std::__debug::vector of length 0, capacity 0, m_openlist = std::__debug::vector of length 0, capacity 0, m_viewlist = std::__debug::vector of length 0, capacity 0, m_startlist = std::__debug::vector of length 0, capacity 0, m_forceopenlist = std::__debug::vector of length 0, capacity 0, m_forcenewlist = std::__debug::vector of length 0, capacity 0, m_printlist = std::__debug::vector of length 0, capacity 0, m_printtolist = std::__debug::vector of length 0, capacity 0, m_printername = "", m_conversionlist = std::__debug::vector of length 0, capacity 0, m_conversionparams = "", m_conversionout = "", m_infilter = std::__debug::vector of length 0, capacity 0, m_language = "", m_pidfile = ""} +> aUnknown = "" +> #11 0x0000000000400957 in sal_main () at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/desktop/source/app/main.c:48 +> ret = 0 +> #12 0x000000000040093d in main (argc=8, argv=0x7fff49ea9df8) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/desktop/source/app/main.c:47 +> ret = 0 +> +> Thread 1 (Thread 0x2b12e6862700 (LWP 11663)): +> #0 0x00002b12bfa635f7 in raise () at /lib64/libc.so.6 +> #1 0x00002b12bfa64ce8 in abort () at /lib64/libc.so.6 +> #2 0x00002b12bf406165 in (anonymous namespace)::callSystemHandler(int, siginfo_t*, void*) (signal=6, info=0x2b12e68607f0, context=0x2b12e68606c0) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/sal/osl/unx/signal.cxx:389 +> act = {__sigaction_handler = {sa_handler = 0x0, sa_sigaction = 0x0}, sa_mask = {__val = {0 }}, sa_flags = 0, sa_restorer = 0xe6860b90} +> i = 5 +> #3 0x00002b12bf406472 in (anonymous namespace)::signalHandlerFunction(int, siginfo_t*, void*) (signal=6, info=0x2b12e68607f0, context=0x2b12e68606c0) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/sal/osl/unx/signal.cxx:486 +> act = {__sigaction_handler = {sa_handler = 0x2b12e68607e0, sa_sigaction = 0x2b12e68607e0}, sa_mask = {__val = {0, 18446744069414584320, 0, 0, 61329184, 47360176948768, 47356309405696, 0, 47356309405696, 47356309405696, 4294967295, 47356309405696, 0, 0, 47359528444352, 47360176950400}}, sa_flags = -72537977, sa_restorer = 0x2b12bfdea1c0 <_IO_2_1_stderr_>} +> Info = {Signal = osl_Signal_AccessViolation, UserSignal = 6, UserData = 0x0} +> #4 0x00002b12bfa63670 in () at /lib64/libc.so.6 +> #5 0x00002b12bfa635f7 in raise () at /lib64/libc.so.6 +> #6 0x00002b12bfa64ce8 in abort () at /lib64/libc.so.6 +> #7 0x00002b12bfa5c566 in __assert_fail_base () at /lib64/libc.so.6 +> #8 0x00002b12bfa5c612 in () at /lib64/libc.so.6 +> #9 0x00002b12ca7a56ba in Scheduler::ProcessEventsToIdle() () at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/vcl/source/app/svapp.cxx:540 +> pSVData = 0x2b12cb2a9e00 ::get()::instance> +> bAnyIdle = true +> __PRETTY_FUNCTION__ = "static void Scheduler::ProcessEventsToIdle()" +> nSanity = 1 +> pSchedulerData = 0x0 +> #10 0x00002b12c8c8c554 in (anonymous namespace)::VCLXToolkit::processEventsToIdle() (this=0x3abc2a0) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/toolkit/source/awt/vclxtoolkit.cxx:1897 +> aSolarGuard = {m_solarMutex = @0x1be9610} +> aZone = {m_sProfileId = 0x2b12c8e672a2 "processEvents", m_aCreateTime = 0} +> #11 0x00002b12e317751d in gcc3::callVirtualMethod(void*, unsigned int, void*, _typelib_TypeDescriptionReference*, bool, unsigned long*, unsigned int, unsigned long*, double*) (pThis=0x3abc2f0, nVtableIndex=9, pRegisterReturn=0x0, pReturnTypeRef=0x1c07390, bSimpleReturn=true, pStack=0x2b12e6861170, nStack=0, pGPR=0x2b12e68612d0, pFPR=0x2b12e6861300) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/bridges/source/cpp_uno/gcc3_linux_x86-64/callvirtualmethod.cxx:133 +> data = {pMethod = 47359678006708, pStack = 0x2b12e6861170, nStack = 0, pGPR = 0x2b12e68612d0, pFPR = 0x2b12e6861300, rax = 0, rdx = 47360176951616, xmm0 = 2.3399007939816937e-310, xmm1 = 3.0079874608688941e-316} +> pMethod = 47359683834264 +> #12 0x00002b12e317615c in cpp_call(bridges::cpp_uno::shared::UnoInterfaceProxy*, bridges::cpp_uno::shared::VtableSlot, typelib_TypeDescriptionReference*, sal_Int32, typelib_MethodParameter*, void*, void**, uno_Any**) (pThis=0x3ab9a80, aVtableSlot=..., pReturnTypeRef=0x1c07390, nParams=0, pParams=0x0, pUnoReturn=0x0, pUnoArgs=0x0, ppUnoExc=0x2b12e6861488) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/bridges/source/cpp_uno/gcc3_linux_x86-64/uno2cpp.cxx:237 +> pStack = 0x2b12e6861170 +> pFPR = {2.3399036413000935e-310, 2.3399017635979741e-310, 1.9097962118687451e-313, 2.3399036413020203e-310, 2.9003052604790195e-316, -1.3614915359519845e+193, 2.339903641302465e-310, 0} +> __PRETTY_FUNCTION__ = "void cpp_call(bridges::cpp_uno::shared::UnoInterfaceProxy*, bridges::cpp_uno::shared::VtableSlot, typelib_TypeDescriptionReference*, sal_Int32, typelib_MethodParameter*, void*, void**, uno_Any**)" +> pCppArgs = 0x2b12e6861160 +> pStackStart = 0x2b12e6861170 +> pGPR = {61588208, 9, 47360176952128, 47360119419570, 0, 58702832} +> nTempIndices = 0 +> nFPR = 0 +> pAdjustedThisPtr = 0x3abc2f0 +> ppTempParamTypeDescr = 0x2b12e6861160 +> nGPR = 1 +> pReturnTypeDescr = 0x1c07390 +> pCppReturn = 0x0 +> bSimpleReturn = true +> pTempIndices = 0x2b12e6861160 +> #13 0x00002b12e3176bdb in bridges::cpp_uno::shared::unoInterfaceProxyDispatch(_uno_Interface*, _typelib_TypeDescription const*, void*, void**, _uno_Any**) (pUnoI=0x3ab9a80, pMemberDescr=0x37fbbf0, pReturn=0x0, pArgs=0x0, ppException=0x2b12e6861488) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/bridges/source/cpp_uno/gcc3_linux_x86-64/uno2cpp.cxx:425 +> nMemberPos = 27 +> aVtableSlot = {offset = 0, index = 9} +> pThis = 0x3ab9a80 +> pTypeDescr = 0x2b14740 +> __PRETTY_FUNCTION__ = "void bridges::cpp_uno::shared::unoInterfaceProxyDispatch(uno_Interface*, const typelib_TypeDescription*, void*, void**, uno_Any**)" +> #14 0x00002b12e441cf28 in binaryurp::IncomingRequest::execute_throw(binaryurp::BinaryAny*, std::__debug::vector >*) const (this=0x3a0fd50, returnValue=0x2b12e68618a0, outArguments=0x2b12e6861920) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/binaryurp/source/incomingrequest.cxx:242 +> exc = +> pexc = 0x2b12e68614b0 +> retType = {_pTypeDescr = 0x1c07390} +> nSize = 0 +> retBuf = std::__debug::vector of length 0, capacity 0 +> outBufs = empty std::__debug::list +> args = std::__debug::vector of length 0, capacity 0 +> __PRETTY_FUNCTION__ = "bool binaryurp::IncomingRequest::execute_throw(binaryurp::BinaryAny*, std::__debug::vector*) const" +> isExc = false +> #15 0x00002b12e441bd16 in binaryurp::IncomingRequest::execute() const (this=0x3a0fd50) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/binaryurp/source/incomingrequest.cxx:79 +> resetCc = true +> oldCc = {m_pUnoI = 0x0} +> ret = {data_ = _uno_Any(void)} +> outArgs = std::__debug::vector of length 0, capacity 0 +> isExc = false +> #16 0x00002b12e443c7a4 in binaryurp::(anonymous namespace)::request(void*) (pThreadSpecificData=0x3a0fd50) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/binaryurp/source/reader.cxx:85 +> __PRETTY_FUNCTION__ = "void binaryurp::{anonymous}::request(void*)" +> #17 0x00002b12c2ce1f5f in cppu_threadpool::JobQueue::enter(long, bool) (this=0x3ab9da0, nDisposeId=61386512, bReturnWhenNoJob=true) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/cppu/source/threadpool/jobqueue.cxx:107 +> guard = {pT = 0x3ab9da0} +> job = {pThreadSpecificData = 0x3a0fd50, doRequest = 0x2b12e443c73b } +> pReturn = 0x0 +> #18 0x00002b12c2ce6bfd in cppu_threadpool::ORequestThread::run() (this=0x3a8af10) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/cppu/source/threadpool/thread.cxx:172 +> #19 0x00002b12c2ce712b in osl::threadFunc(void*) (param=0x3a8af20) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/include/osl/thread.hxx:185 +> pObj = 0x3a8af20 +> #20 0x00002b12bf40d7bd in osl_thread_start_Impl(void*) (pData=0x3a4c7e0) at /home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/sal/osl/unx/thread.cxx:237 +> terminate = false +> pImpl = 0x3a4c7e0 +> __PRETTY_FUNCTION__ = "void* osl_thread_start_Impl(void*)" +> #21 0x00002b12bfdf7dc5 in start_thread () at /lib64/libpthread.so.0 +> #22 0x00002b12bfb24ced in clone () at /lib64/libc.so.6 +[...] +> make[1]: *** [/home/tdf/lode/jenkins/workspace/lo_tb_master_linux_dbg/workdir/JunitTest/sd_unoapi/done] Error 1 + +Reviewed-on: https://gerrit.libreoffice.org/40176 +Tested-by: Jenkins +Reviewed-by: Jan-Marek Glogowski +(cherry picked from commit ee299664940139f6f9543592ece3b3c0210b59f4) + +Don't run testIdleMainloop on Windows + +Was just wondering, why the test finished so fast on Windows, +and realized it's actually empty due to "#ifndef WIN32". + +(cherry picked from commit 8f999b555bfc6d4789916d381e38c2eb674fd26e) + +tdf#109123 WIN Run instant timerout with low priority + +This busy-lock happens, because user messages have a higher priority +then some system messages. What happens: + +1. The main system loop picks up the LO scheduler +2. The idle worker (IW) is started +3. IW checks using AnyInput( VCL_INPUT_ANY ) for system events +4. A system event is found +5. The LO scheduler gets posted again +6. The main system loop picks up the LO scheduler instead of the + system message => goto 2 + +Normally it's suggested to use WM_TIMER in this case, as these messages +are supposed to have the lowest priority. But this doesn't work, if +you use PostMessage to generate them and SetTimer doesn't accept a +0ms timeout. At least PeakMessage also picks up the WM_TIMER message +before the system message, probably because PostMessage is somehow +related to the threads queue - who knows. + +In the end this implements a manual, low priority event, which is checked +at the end of the ImplSalYield function. It just runs, if there is +nothing else to do. We still have to emit the timer callback event, +as ImplSalYield may wait in GetMessage, but wParam now indicates, if +it's a wakeup and can be ignored. We use the same event, so it's +easier to filter. + +Thanks to Mike Kaganski for the missing information and ideas for the +final implementation. + +Reviewed-on: https://gerrit.libreoffice.org/40190 +Tested-by: Jenkins +Reviewed-by: Jan-Marek Glogowski +(cherry picked from commit 37436815970b14f8940fc0c547862452a2dc3e1e) + +WIN use Reschedule instead of own dispatch loops + +Since we're filtering the wakeup timer event in the main dispatch +loop, we should use Application::Reschedule in the Backend. + +(cherry picked from commit 221b0ab1245be6dba23b4ef3c516e846d95d2f71) + +Add some const and fix some scheduler logging + +(cherry picked from commit 9dfd1bb102bb08f0651a6921722d731ab973bd08) + +WIN don't yield the scheduler in PeekMessage + +The scheduler is restarting the timer at the end of the most +important task search. It uses PeekMessage PM_REMOVE to remove +old SAL_MSG_TIMER_CALLBACK messages from the queue. + +Without PM_NOYIELD, in combination with an other thread yielding +using SAL_MSG_THREADYIELD, this could re-start scheduling inside +these PeekMessage calls, resulting in various assertions inside +the scheduler code, most time due to the changed ascheduler list +in "assert( pPrevSchedulerData->mpNext == pSchedulerData )". + +(cherry picked from commit 4815b6f7c70cca5a226163caaaab8a227f32be48) + +WIN don't process the SendMessage from DoYield + +Actually we just want to remove the SAL_MSG_TIMER_CALLBACK +messages, but this seems to be impossible using PeekMessage, +without the side effect of processing some messages: +"During this call, the system delivers pending, nonqueued +messages... Then the first queued message that matches the +specified filter is retrieved.". + +But it is actually enought to ignore the SAL_MSG_THREADYIELD +message send using SendMessage from DoYield, which can be +filtered by using PM_QS_POSTMESSAGE. + +Probably this should be resolved not using PeekMessage at all +by using a variable to hold the time of the last posted +SAL_MSG_TIMER_CALLBACK message, so we just run the callback +once, if our time is <= MSG time and ignore the multiple +queued messages. Same for mbOnIdleRunScheduler handling. + +(cherry picked from commit 20ddcfb92afeec2da22cb8fd0feeb89014c2fc89) + +GEN fix timeout result handling + +The GEN VCL backend simply ignored the result of the timeout. + +(cherry picked from commit c23f735eb7579477b5de3850eac075a21329c06b) + +tdf#109997 WIN don't post a callback event directly + +I doesn't seem possible to post an event deterministically to the +end of the Windows message queue and then process this queued +events "in order". + +PeekMessage and now even DispatchMessage process events out of +order - that's how this assert was hit. I was quite sure it would +not hit, but a simple resize proved me wrong. And the assert just +proved that all my assumptions were wrong :-( + +So this gives up the whole idea of a short-circuit message queue +handling on Windows for busy processing of LO Idles and goes back +to some kind of the original "always timer" implementation. Since +the "parallel" processing of LO events after system messages +during DoYield was dropped, this might be slower; or not. + +In the end this simplifies the main loop almost to the starting +point, except for a little busy loop, if we wait for an Idle event +timer - not so busy acually, as we just switch to another local +thread, which hopefully is our idle timer waiting to fire. +A short-circuit with a little detour. + +(cherry picked from commit 50799a721c7ddcf9475a1b79984ed64ddd7cdf57) + +Implement VCL Scheduler locking + +Replces the SolarMutex scheduler locking by using a distinct mutex +included in the scheduler context. + +It should also get rid of the spurious assert( !bAniIdle ) failures, +as the test now holds the Scheduler lock for the whole time. + +This includes reverting the additional scheduler de-init from commit +2e29a518b04250b5f9cc9d0d77da3df076834d60, as I couldn't reproduce +the bug running the unit test in valgrind. + +Reviewed-on: https://gerrit.libreoffice.org/40497 +Tested-by: Jenkins +Reviewed-by: Jan-Marek Glogowski +(cherry picked from commit d55aabfd44563027aceffd0020f55b166184a0ca) + +Add VCL scheduler documentation + +(cherry picked from commit fa1be538cdb0f15aff717aa8583c191194609266) + +The scheduler timer starts without SolarMutex + +The backend is resposible to correctly start the timer. +Otherwise you can't start new Tasks without the SolarMutex. + +(cherry picked from commit 3b9a535ee41e85fd30381432b245252d1e97b8f9) + +Remove 500ns offset from GetSystemTicks() + +This doesn't make any sense. The orignal idea might have been to +somehow be in the middle of 1ms, for whatever reason. + +And it completely breaks, if we want to sleep 0ms. + +(cherry picked from commit 168782a6e48fad98355a72e557402e66f7aa075d) + +Really run the layouter every 50ms + +By Start()-ing the Idle, it's always reset, so with fast +resizes you could actually postpone layouting indefinitly. + +(cherry picked from commit e485703d3e5eaabe5f1bfd900a3ff205a8a84a35) + +SVP always drain the wakeup pipe + +When we have a lot of idle tasks and generate new tasks, the wakeup +pipe fills and is never drained, so OSL_ENSURE in Wakeup() starts +to fail. This simply drains the pipe on every run, which processed +an event. + +(cherry picked from commit cb8bfa9a32799bcde4e960fa56e388d5f7b2a928) + +osl::Mutex::acquire can effectively only fail upon programming errors + +...so simplify the design a bit here (and there was no meaningful failure- +handling code, anyway) + +(cherry picked from commit dab0835c2f37e322cc64e63824bd4ec8f54c5b1d) + +Abort on critical Scheduler problems + +We don't have the possibility to continue processing tasks without +the scheduler lock. Same situation, if a task throws an exception. +In both cases we just abort() instead of using assertions. + +(cherry picked from commit 11403bc18ba1b7c7fd099e9b9aa0b61cefdd1287) + +OSX fix updates during resize + +While resizing MacOS suspends the NSDefaultRunLoopMode. So in this +caae we can't post to the system event loop, but must use timers +to restart ourself. + +Since the timer itself is scheduled on the NSEventTrackingRunLoopMode +it' also triggers on resize events. + +There is still some minor glitch: when resizing too fast some part of +LibreOffice isn't painted, while the left mouse button is down. + +Since there isn't any layouting triggered by the mouse up, there has +to be an other inconsistency. + +(cherry picked from commit 33b094a8949c34756c593bfad52450ec2b7daa54) + +Don't run the OLEObjCache timer for an empty cache + +(cherry picked from commit e824a49a1c12533047d6a5ab8544377e8ff29863) + +Don't poll the extension install progress bar + +Just start the Idle, if actually something changed. + +Quite probably we shouldn't rely on an Idle at all, but this +fixes the busy loop while installing an extension waiting in +a confirmation dialog. + +(cherry picked from commit bc3e0121b47cc601575b0a49f6ba4959130cf96e) + +Workaround static Task destruction error + +A task has to get the SchedulerLock to remove itself from the +Scheduler list. This doesn't work, if the Task is static, as the +static Scheduler might be destroyed earlier. In this case we fail +with the following backtrace: + + #0 SchedulerMutex::acquire + #1 Task::~Task + #2 __run_exit_handlers + +Thanks to Michael Stahl to catching this backtrace. + +As a workaround this marks static tasks, so they ignore the +SchedulerMutex in the destructor, We also mark all scheduled Tasks +as "static" in DeInitScheduler, as their cleanup was already done. + +In the end all Tasks should be removed from static objects. + +Reviewed-on: https://gerrit.libreoffice.org/42574 +Tested-by: Jenkins +Reviewed-by: Jan-Marek Glogowski +(cherry picked from commit 06ce312f79cb0871c0b110ba4bff16f5aaa0f538) + +tdf#111994 WIN workaround PostMessage delays + +Fixes the "Multiple timers in queue" assertion by effectively +removing it. + +When debugging it became obvious, that PostMessage returns, even +if the message was not yet added to the message queue. + +The assert happens, because we start the timer in the Scheduler +before Invoke(), so it fires, if we block in Invoke(), and then +reset the timer after Invoke, if there were changes to the Task +list. + +In this case it fires during Invoke(), the message is added. We +restart the timer, first by stopping it (we wait in +DeleteTimerQueueTimer, to be sure the timer function has either +finished or was not run). And the try to remove the message with +PeekMessageW, which doesn't remove the posted message. + +Then the timer is restarted, and when the event is processed, we +end up with an additional timer event, which was asserted. + +As a fix this adds a (microsecond) timestamp to the timer message, +which is validated in the WinProc function. So if we stop the +timer too fast, the event is ignored based on the timestamp. + +And while at it, the patch moves timer related variables from +SalData into WinSalTimer. + +Reviewed-on: https://gerrit.libreoffice.org/42575 +Tested-by: Jenkins +Reviewed-by: Jan-Marek Glogowski +(cherry picked from commit 448e9da1b440561441602e3a0956218b2702767e) + +tdf#112288 Clarify Reschedule implementations + +Application::Reschedule(true) must just process all currently +pending events and ignore all new events generated while processing +them. In contrast to Scheduler::ProcessEventsToIdle, this way it +can't busy-lock the application with background jobs. + +This way we also can drop nMaxEvents from the Windows backend. This +limit was also never implemented on OSX and for the KDE4 backend +it's actually impossible to handle single events, and a call to +QAbstractEventDispatcher::processEvents works like this. + +Also changes various call sites to just process all pending events +instead of some made up number of times. + +Reviewed-on: https://gerrit.libreoffice.org/42659 +Tested-by: Jenkins +Reviewed-by: Jan-Marek Glogowski +(cherry picked from commit 9679fb26558ea42e47ac9936cef329115a8fdf65) + +tdf#112890 some dialog don't appear under gtk2 + +Reviewed-on: https://gerrit.libreoffice.org/43162 +Reviewed-by: Jan-Marek Glogowski +Tested-by: Jenkins +Reviewed-by: Caolán McNamara +Tested-by: Caolán McNamara +(cherry picked from commit 072a077972ada6500151623642db544ff5cb567e) + +186bebdfb5701543595848968235b5a56b6598e9 +54534787b8cfa4c47dc09dde9c38a7893df9d367 +740b97315df92f8b979089e7e22058e628f95bc0 +690a8754e6b115a53e74ef777e988b66b4e5037f +f7214e1f6016414551abbef11f26f332737f7893 +a0b91b06dffb77af066f01838d8f9483523bf67d +1c1c61b3e54bd14d9451c53150251534b2a960f0 +358cf2492e9630f67685a2b780509edb56691830 +567e3b92c6c2999ce51aecb31f858e51cab6c999 +ce111aa5f85e9181b3ee9799ca4df0d58f210fe9 +28dda435de44031c050b6edbfae1e9d392465d24 +7665d5f2e7d6ba3e01290a692bbc8e42c36b9986 +94eacd8f5bacf277a68575cc8db84653cbc49d12 +21a0a62bd0cd122e0da676579d2b2a93264acdd8 +0b77e0c64fce04b20e82ba8bbf72b7a99b1339af +ea40a6284bdc5c121235af5a6079a92a679391ca +a6bac6e24d95e680830c5405f0ab34cbed6e9688 +cb8f34f7e8716f0faa90a95903b1681f7489aed2 +549142394334bdc098f053b42f222b23cf4fcecd +b46eb6dbf57f29c85ffdbd6492922020f7785d30 +e7562444951e16ff58edcaf6409f32809314c2fa +b41186425b2f76faa0e9f116f47fdcd60d878099 +5283f18aebcd6797ad35771ae8fc4a0f425ff924 +57f8c23ca1eebdeed780a644c83fcbeb9b92cd66 +13852e3e63daead451bf7fcb98be9b1d44bd7abd +498a73fd02d5fb6f5d7e9f742f3bce972de9b1f9 +e3b466ad03e7ed8c35a62f41c09d09757865364d +32c39fa70c8d52cbb78620f528d60a986087dfd0 +0641da8d0db71785c505957533a9069924808cd4 +2ba9b4ad2b67ec99eeb4dd098ded6457d3753127 +a7aa081848647314e14b5ccd3063d51700f2b6c6 +85c69b2f32dba9869bbddc1a572b3a63c366c5d1 +c8ca00db385ed59bb16744581f9d7cc0fcbb4f5b +7d034c02a49d315104d8329a4e1119e14e8e5e40 +050b7cb7bd66bdd2a215ef6e7eaf26355e8d962f +b8e4f214ab8d3731d5594d68f38f46982c2eb36d +e02c3533e8a6a7905281f129489e4f6f53f74692 +d20767ff2be34a21896d3ce2b76f3944acdb1b77 +a96b6c0e06ffc3126b1428723b53f4b2112f8a5f +facb20aa38e6b5aca908e5411cf2e100f702ad1f +0c1b34927ba97886cf11b2c2a203c3e82d851dc9 +d63a2a9e2a3b1501ad50a2c6f308a36efe55e68f +f33f815fe86c8f82175e96adceb1084b925319dd +fb2332b6d3c8bf472c684d3a79c861cc9035d246 +4e066fceb513d0de90b58854baf3e45f2b8ff25b +525b70c016876a96aa17edefe8c076b122ee2527 +e90b6f3f378ee8d163f621fce51280e09c826f14 +7b6366b2649133b63138dc77fe51508404132890 +34bb3a7b5fc56f2213a4d22438f0733e1b65fe4b +4d52a6ef0526a1e46b64f9f3a6e0cc1a718618cc +3ccba78bd23ec8526f21e7b93b027f3d3279f901 +210f6bdec14491bea6d15bca133011059091f21b +3abbe2eb6651ce9320ef6e4d9c5251a23ab87216 +38be3206378b9449193efaccbc96896ac8de9478 +b840a421e8bd040d40f39473e1d44491e5b332bd +1ab95df89b079cc8c6319a808194fe3127144d1c +90a59bfdaec0e78d93888e5cc30d1c7fa147a7e7 + +Change-Id: I3c8a708d1fd51c550320f8af3f9486c43c32e358 +--- + avmedia/source/framework/mediacontrol.cxx | 2 +- + avmedia/source/framework/soundhandler.cxx | 2 +- + avmedia/source/opengl/oglplayer.cxx | 2 +- + basctl/source/basicide/baside2b.cxx | 1 - + basctl/source/dlged/dlged.cxx | 1 - + cui/source/dialogs/cuigaldlg.cxx | 3 +- + cui/source/options/optjava.cxx | 1 - + dbaccess/source/ui/browser/brwctrlr.cxx | 1 + + dbaccess/source/ui/querydesign/JoinTableView.cxx | 2 +- + desktop/qa/desktop_lib/test_desktop_lib.cxx | 2 + + desktop/source/deployment/gui/dp_gui_dialog2.cxx | 28 +- + desktop/source/deployment/gui/dp_gui_dialog2.hxx | 4 +- + formula/source/ui/dlg/formula.cxx | 3 +- + formula/source/ui/dlg/funcutl.cxx | 2 - + framework/source/layoutmanager/layoutmanager.cxx | 12 +- + include/svx/svdetc.hxx | 1 - + include/vcl/idle.hxx | 21 +- + include/vcl/scheduler.hxx | 108 ++-- + include/vcl/svapp.hxx | 25 +- + include/vcl/task.hxx | 105 ++++ + include/vcl/timer.hxx | 15 +- + reportdesign/source/ui/report/DesignView.cxx | 1 - + sc/qa/unit/tiledrendering/tiledrendering.cxx | 1 + + sc/qa/unoapi/sc_4.sce | 7 +- + sc/source/core/data/documen2.cxx | 1 - + sc/source/ui/app/scmod.cxx | 1 - + sc/source/ui/miscdlgs/acredlin.cxx | 2 - + sc/source/ui/miscdlgs/anyrefdg.cxx | 1 - + sc/source/ui/miscdlgs/conflictsdlg.cxx | 1 - + sd/qa/unit/misc-tests.cxx | 4 +- + sd/qa/unit/tiledrendering/tiledrendering.cxx | 1 + + sd/source/ui/dlg/filedlg.cxx | 1 - + sd/source/ui/framework/module/ShellStackGuard.cxx | 1 - + sd/source/ui/view/sdview.cxx | 2 - + sfx2/source/appl/appcfg.cxx | 2 +- + sfx2/source/appl/appinit.cxx | 5 - + sfx2/source/appl/newhelp.cxx | 2 - + sfx2/source/control/dispatch.cxx | 6 +- + sfx2/source/view/ipclient.cxx | 1 + + solenv/gdb/libreoffice/vcl.py | 7 +- + svtools/source/contnr/imivctl1.cxx | 6 +- + svx/inc/sdr/contact/objectcontactofpageview.hxx | 3 +- + svx/source/dialog/_contdlg.cxx | 1 - + svx/source/dialog/imapdlg.cxx | 1 - + svx/source/form/fmsrcimp.cxx | 24 +- + svx/source/sdr/contact/objectcontactofpageview.cxx | 10 +- + .../sdr/contact/viewobjectcontactofpageobj.cxx | 2 +- + svx/source/sdr/event/eventhandler.cxx | 2 +- + svx/source/svdraw/sdrpagewindow.cxx | 6 +- + svx/source/svdraw/svdetc.cxx | 24 +- + svx/source/svdraw/svdibrow.cxx | 2 +- + svx/source/tbxctrls/grafctrl.cxx | 1 - + sw/qa/extras/tiledrendering/tiledrendering.cxx | 1 + + sw/qa/extras/uiwriter/uiwriter.cxx | 1 + + sw/source/ui/dbui/addresslistdialog.cxx | 3 +- + sw/source/ui/dbui/mmresultdialogs.cxx | 9 +- + sw/source/uibase/dbui/dbmgr.cxx | 15 +- + sw/source/uibase/docvw/srcedtw.cxx | 1 - + sw/source/uibase/utlui/unotools.cxx | 2 +- + tools/source/datetime/ttime.cxx | 2 +- + vcl/Module_vcl.mk | 6 + + vcl/README.scheduler | 264 ++++++++++ + vcl/headless/svpinst.cxx | 39 +- + vcl/inc/headless/svpinst.hxx | 3 +- + vcl/inc/salinst.hxx | 9 +- + vcl/inc/saltimer.hxx | 23 +- + vcl/inc/salwtype.hxx | 2 +- + vcl/inc/schedulerimpl.hxx | 69 +++ + vcl/inc/svdata.hxx | 30 +- + vcl/inc/unx/gtk/gtkdata.hxx | 4 +- + vcl/inc/unx/gtk/gtkinst.hxx | 8 +- + vcl/inc/unx/saldata.hxx | 2 +- + vcl/inc/unx/saldisp.hxx | 3 +- + vcl/inc/unx/salinst.h | 3 +- + vcl/inc/unx/salunxtime.h | 2 +- + vcl/qa/cppunit/lifecycle.cxx | 3 +- + vcl/qa/cppunit/timer.cxx | 181 ++++++- + vcl/source/app/idle.cxx | 20 +- + vcl/source/app/scheduler.cxx | 547 +++++++++++++-------- + vcl/source/app/svapp.cxx | 86 ++-- + vcl/source/app/svmain.cxx | 5 +- + vcl/source/app/timer.cxx | 25 +- + vcl/source/edit/textdata.cxx | 2 +- + vcl/source/gdi/print2.cxx | 3 +- + vcl/source/uitest/uno/uiobject_uno.cxx | 3 +- + vcl/source/window/dockmgr.cxx | 4 +- + vcl/source/window/dockwin.cxx | 2 +- + vcl/unx/generic/app/saldata.cxx | 40 +- + vcl/unx/generic/app/salinst.cxx | 2 +- + vcl/unx/gtk/gtkdata.cxx | 36 +- + vcl/unx/gtk/gtkinst.cxx | 27 +- + vcl/unx/gtk3/gtk3gtkdata.cxx | 32 +- + vcl/unx/kde4/KDESalInstance.cxx | 7 + + vcl/unx/kde4/KDESalInstance.hxx | 20 +- + vcl/unx/kde4/KDEXLib.cxx | 69 +-- + vcl/unx/kde4/KDEXLib.hxx | 16 +- + 96 files changed, 1369 insertions(+), 732 deletions(-) + create mode 100644 include/vcl/task.hxx + create mode 100644 vcl/README.scheduler + create mode 100644 vcl/inc/schedulerimpl.hxx + +diff --git a/avmedia/source/framework/mediacontrol.cxx b/avmedia/source/framework/mediacontrol.cxx +index be6528b44e8b..74b5bdd16a25 100644 +--- a/avmedia/source/framework/mediacontrol.cxx ++++ b/avmedia/source/framework/mediacontrol.cxx +@@ -114,7 +114,7 @@ MediaControl::MediaControl( vcl::Window* pParent, MediaControlStyle eControlStyl + mpZoomToolBox->SetPaintTransparent( true ); + } + +- maIdle.SetPriority( TaskPriority::LOW ); ++ maIdle.SetPriority( TaskPriority::HIGH_IDLE ); + maIdle.SetInvokeHandler( LINK( this, MediaControl, implTimeoutHdl ) ); + maIdle.Start(); + } +diff --git a/avmedia/source/framework/soundhandler.cxx b/avmedia/source/framework/soundhandler.cxx +index 45f1b61132f7..a2ea3ed52ac0 100644 +--- a/avmedia/source/framework/soundhandler.cxx ++++ b/avmedia/source/framework/soundhandler.cxx +@@ -221,7 +221,7 @@ void SAL_CALL SoundHandler::dispatchWithNotification(const css::util::URL& + // Count this request and initialize self-holder against dying by uno ref count ... + m_xSelfHold.set(static_cast< ::cppu::OWeakObject* >(this), css::uno::UNO_QUERY); + m_xPlayer->start(); +- m_aUpdateIdle.SetPriority( TaskPriority::LOWER ); ++ m_aUpdateIdle.SetPriority( TaskPriority::HIGH_IDLE ); + m_aUpdateIdle.Start(); + } + catch( css::uno::Exception& e ) +diff --git a/avmedia/source/opengl/oglplayer.cxx b/avmedia/source/opengl/oglplayer.cxx +index 18b4e9437d66..b44f77de6caf 100644 +--- a/avmedia/source/opengl/oglplayer.cxx ++++ b/avmedia/source/opengl/oglplayer.cxx +@@ -123,7 +123,7 @@ bool OGLPlayer::create( const OUString& rURL ) + + // Set timer + m_aTimer.SetTimeout(8); // is 125fps enough for anyone ? +- m_aTimer.SetPriority(TaskPriority::LOW); ++ m_aTimer.SetPriority(TaskPriority::HIGH_IDLE); + m_aTimer.SetInvokeHandler(LINK(this,OGLPlayer,TimerHandler)); + + return true; +diff --git a/basctl/source/basicide/baside2b.cxx b/basctl/source/basicide/baside2b.cxx +index 42fa77f9ce78..afe1a8d11d92 100644 +--- a/basctl/source/basicide/baside2b.cxx ++++ b/basctl/source/basicide/baside2b.cxx +@@ -961,7 +961,6 @@ void EditorWindow::CreateEditEngine() + + ImplSetFont(); + +- aSyntaxIdle.SetPriority( TaskPriority::LOWER ); + aSyntaxIdle.SetInvokeHandler( LINK( this, EditorWindow, SyntaxTimerHdl ) ); + + bool bWasDoSyntaxHighlight = bDoSyntaxHighlight; +diff --git a/basctl/source/dlged/dlged.cxx b/basctl/source/dlged/dlged.cxx +index c6987520bc8c..07d4d79e42e0 100644 +--- a/basctl/source/dlged/dlged.cxx ++++ b/basctl/source/dlged/dlged.cxx +@@ -217,7 +217,6 @@ DlgEditor::DlgEditor ( + m_ClipboardDataFlavorsResource[1].HumanPresentableName = "Dialog 8.0" ; + m_ClipboardDataFlavorsResource[1].DataType = cppu::UnoType>::get(); + +- aMarkIdle.SetPriority(TaskPriority::LOW); + aMarkIdle.SetInvokeHandler( LINK( this, DlgEditor, MarkTimeout ) ); + + rWindow.SetMapMode( MapMode( MapUnit::Map100thMM ) ); +diff --git a/cui/source/dialogs/cuigaldlg.cxx b/cui/source/dialogs/cuigaldlg.cxx +index 273363e384cd..95518874ef91 100644 +--- a/cui/source/dialogs/cuigaldlg.cxx ++++ b/cui/source/dialogs/cuigaldlg.cxx +@@ -483,8 +483,7 @@ IMPL_LINK( ActualizeProgress, TimeoutHdl, Timer*, _pTimer, void) + + IMPL_LINK( ActualizeProgress, ActualizeHdl, const INetURLObject&, rURL, void ) + { +- for( long i = 0; i < 128; i++ ) +- Application::Reschedule(); ++ Application::Reschedule( true ); + + Flush(); + +diff --git a/cui/source/options/optjava.cxx b/cui/source/options/optjava.cxx +index 292558a6365d..ce15f2aea01a 100644 +--- a/cui/source/options/optjava.cxx ++++ b/cui/source/options/optjava.cxx +@@ -154,7 +154,6 @@ SvxJavaOptionsPage::SvxJavaOptionsPage( vcl::Window* pParent, const SfxItemSet& + m_pParameterBtn->SetClickHdl( LINK( this, SvxJavaOptionsPage, ParameterHdl_Impl ) ); + m_pClassPathBtn->SetClickHdl( LINK( this, SvxJavaOptionsPage, ClassPathHdl_Impl ) ); + m_aResetIdle.SetInvokeHandler( LINK( this, SvxJavaOptionsPage, ResetHdl_Impl ) ); +- m_aResetIdle.SetPriority(TaskPriority::LOWER); + + m_pExpertConfigBtn->SetClickHdl( LINK( this, SvxJavaOptionsPage, ExpertConfigHdl_Impl) ); + if (!officecfg::Office::Common::Security::EnableExpertConfiguration::get()) +diff --git a/dbaccess/source/ui/browser/brwctrlr.cxx b/dbaccess/source/ui/browser/brwctrlr.cxx +index 209c92dcd8f0..a6c9dbbcc686 100644 +--- a/dbaccess/source/ui/browser/brwctrlr.cxx ++++ b/dbaccess/source/ui/browser/brwctrlr.cxx +@@ -553,6 +553,7 @@ SbaXDataBrowserController::SbaXDataBrowserController(const Reference< css::uno:: + } + osl_atomic_decrement(&m_refCount); + ++ m_aInvalidateClipboard.SetDebugName("dbaui::SbaXDataBrowserController m_aInvalidateClipboard"); + m_aInvalidateClipboard.SetInvokeHandler(LINK(this, SbaXDataBrowserController, OnInvalidateClipboard)); + m_aInvalidateClipboard.SetTimeout(300); + } +diff --git a/dbaccess/source/ui/querydesign/JoinTableView.cxx b/dbaccess/source/ui/querydesign/JoinTableView.cxx +index ff97680c0e89..bb45e803dd9f 100644 +--- a/dbaccess/source/ui/querydesign/JoinTableView.cxx ++++ b/dbaccess/source/ui/querydesign/JoinTableView.cxx +@@ -1063,7 +1063,7 @@ void OJoinTableView::ScrollWhileDragging() + // resetting timer, if still necessary + if (bNeedScrollTimer) + { +- m_aDragScrollIdle.SetPriority(TaskPriority::LOW); ++ m_aDragScrollIdle.SetPriority( TaskPriority::HIGH_IDLE ); + m_aDragScrollIdle.Start(); + } + +diff --git a/desktop/qa/desktop_lib/test_desktop_lib.cxx b/desktop/qa/desktop_lib/test_desktop_lib.cxx +index 79ec0ba54e3b..46d50234a77e 100644 +--- a/desktop/qa/desktop_lib/test_desktop_lib.cxx ++++ b/desktop/qa/desktop_lib/test_desktop_lib.cxx +@@ -39,6 +39,8 @@ + #include + #include + #include ++#include ++#include + + #include + +diff --git a/desktop/source/deployment/gui/dp_gui_dialog2.cxx b/desktop/source/deployment/gui/dp_gui_dialog2.cxx +index ba9cc08f995b..f6abea1c49c6 100644 +--- a/desktop/source/deployment/gui/dp_gui_dialog2.cxx ++++ b/desktop/source/deployment/gui/dp_gui_dialog2.cxx +@@ -539,6 +539,7 @@ ExtMgrDialog::ExtMgrDialog(vcl::Window *pParent, TheExtensionManager *pManager, + } + + m_aIdle.SetPriority(TaskPriority::LOWEST); ++ m_aIdle.SetDebugName( "ExtMgrDialog m_aIdle TimeOutHdl" ); + m_aIdle.SetInvokeHandler( LINK( this, ExtMgrDialog, TimeOutHdl ) ); + } + +@@ -877,14 +878,18 @@ void ExtMgrDialog::showProgress( bool _bStart ) + } + + DialogHelper::PostUserEvent( LINK( this, ExtMgrDialog, startProgress ), reinterpret_cast(bStart) ); ++ m_aIdle.Start(); + } + + + void ExtMgrDialog::updateProgress( const long nProgress ) + { +- ::osl::MutexGuard aGuard( m_aMutex ); +- +- m_nProgress = nProgress; ++ if ( m_nProgress != nProgress ) ++ { ++ ::osl::MutexGuard aGuard( m_aMutex ); ++ m_nProgress = nProgress; ++ m_aIdle.Start(); ++ } + } + + +@@ -896,6 +901,7 @@ void ExtMgrDialog::updateProgress( const OUString &rText, + m_xAbortChannel = xAbortChannel; + m_sProgressText = rText; + m_bProgressChanged = true; ++ m_aIdle.Start(); + } + + +@@ -1013,8 +1019,6 @@ IMPL_LINK_NOARG(ExtMgrDialog, TimeOutHdl, Timer *, void) + + if ( m_pProgressBar->IsVisible() ) + m_pProgressBar->SetValue( (sal_uInt16) m_nProgress ); +- +- m_aIdle.Start(); + } + } + +@@ -1101,6 +1105,7 @@ UpdateRequiredDialog::UpdateRequiredDialog(vcl::Window *pParent, TheExtensionMan + m_pCloseBtn->GrabFocus(); + + m_aIdle.SetPriority( TaskPriority::LOWEST ); ++ m_aIdle.SetDebugName( "UpdateRequiredDialog m_aIdle TimeOutHdl" ); + m_aIdle.SetInvokeHandler( LINK( this, UpdateRequiredDialog, TimeOutHdl ) ); + } + +@@ -1217,14 +1222,18 @@ void UpdateRequiredDialog::showProgress( bool _bStart ) + } + + DialogHelper::PostUserEvent( LINK( this, UpdateRequiredDialog, startProgress ), reinterpret_cast(bStart) ); ++ m_aIdle.Start(); + } + + + void UpdateRequiredDialog::updateProgress( const long nProgress ) + { +- ::osl::MutexGuard aGuard( m_aMutex ); +- +- m_nProgress = nProgress; ++ if ( m_nProgress != nProgress ) ++ { ++ ::osl::MutexGuard aGuard( m_aMutex ); ++ m_nProgress = nProgress; ++ m_aIdle.Start(); ++ } + } + + +@@ -1236,6 +1245,7 @@ void UpdateRequiredDialog::updateProgress( const OUString &rText, + m_xAbortChannel = xAbortChannel; + m_sProgressText = rText; + m_bProgressChanged = true; ++ m_aIdle.Start(); + } + + +@@ -1323,8 +1333,6 @@ IMPL_LINK_NOARG(UpdateRequiredDialog, TimeOutHdl, Timer *, void) + + if ( m_pProgressBar->IsVisible() ) + m_pProgressBar->SetValue( (sal_uInt16) m_nProgress ); +- +- m_aIdle.Start(); + } + } + +diff --git a/desktop/source/deployment/gui/dp_gui_dialog2.hxx b/desktop/source/deployment/gui/dp_gui_dialog2.hxx +index 5b9a8cc403e3..5baa55909b89 100644 +--- a/desktop/source/deployment/gui/dp_gui_dialog2.hxx ++++ b/desktop/source/deployment/gui/dp_gui_dialog2.hxx +@@ -143,7 +143,7 @@ class ExtMgrDialog : public ModelessDialog, + DECL_LINK( HandleCancelBtn, Button*, void ); + DECL_LINK( HandleCloseBtn, Button*, void ); + DECL_LINK( HandleExtTypeCbx, Button*, void ); +- DECL_LINK(TimeOutHdl, Timer *, void); ++ DECL_LINK( TimeOutHdl, Timer *, void ); + DECL_LINK( startProgress, void *, void ); + DECL_STATIC_LINK( ExtMgrDialog, Restart, void *, void ); + +@@ -216,7 +216,7 @@ class UpdateRequiredDialog : public ModalDialog, + DECL_LINK( HandleUpdateBtn, Button*, void ); + DECL_LINK( HandleCloseBtn, Button*, void ); + DECL_LINK( HandleCancelBtn, Button*, void ); +- DECL_LINK(TimeOutHdl, Timer *, void); ++ DECL_LINK( TimeOutHdl, Timer *, void ); + DECL_LINK( startProgress, void *, void ); + + static bool isEnabled( const css::uno::Reference< css::deployment::XPackage > &xPackage ); +diff --git a/formula/source/ui/dlg/formula.cxx b/formula/source/ui/dlg/formula.cxx +index 304b9430478c..d2b0958bf41b 100644 +--- a/formula/source/ui/dlg/formula.cxx ++++ b/formula/source/ui/dlg/formula.cxx +@@ -1802,8 +1802,7 @@ OUString FormulaDlg::GetMeText() const + void FormulaDlg::Update() + { + m_pImpl->Update(); +- m_pImpl->aIdle.SetPriority(TaskPriority::LOWER); +- m_pImpl->aIdle.SetInvokeHandler(LINK( this, FormulaDlg, UpdateFocusHdl)); ++ m_pImpl->aIdle.SetInvokeHandler( LINK( this, FormulaDlg, UpdateFocusHdl)); + m_pImpl->aIdle.Start(); + } + +diff --git a/formula/source/ui/dlg/funcutl.cxx b/formula/source/ui/dlg/funcutl.cxx +index e4e21ab98871..22a458722b73 100644 +--- a/formula/source/ui/dlg/funcutl.cxx ++++ b/formula/source/ui/dlg/funcutl.cxx +@@ -409,7 +409,6 @@ RefEdit::RefEdit( vcl::Window* _pParent, vcl::Window* pShrinkModeLabel, WinBits + , pLabelWidget(pShrinkModeLabel) + { + aIdle.SetInvokeHandler( LINK( this, RefEdit, UpdateHdl ) ); +- aIdle.SetPriority( TaskPriority::LOW ); + } + + VCL_BUILDER_DECL_FACTORY(RefEdit) +@@ -478,7 +477,6 @@ void RefEdit::SetReferences( IControlReferenceHandler* pDlg, vcl::Window* pLabel + if( pDlg ) + { + aIdle.SetInvokeHandler( LINK( this, RefEdit, UpdateHdl ) ); +- aIdle.SetPriority( TaskPriority::LOW ); + } + else + { +diff --git a/framework/source/layoutmanager/layoutmanager.cxx b/framework/source/layoutmanager/layoutmanager.cxx +index f55ac969e568..20fe5176ae6d 100644 +--- a/framework/source/layoutmanager/layoutmanager.cxx ++++ b/framework/source/layoutmanager/layoutmanager.cxx +@@ -139,6 +139,7 @@ LayoutManager::LayoutManager( const Reference< XComponentContext >& xContext ) : + m_xToolbarManager = new ToolbarLayoutManager( xContext, Reference(m_xUIElementFactoryManager, UNO_QUERY_THROW), this ); + } + ++ m_aAsyncLayoutTimer.SetPriority( TaskPriority::HIGH_IDLE ); + m_aAsyncLayoutTimer.SetTimeout( 50 ); + m_aAsyncLayoutTimer.SetInvokeHandler( LINK( this, LayoutManager, AsyncLayoutHdl ) ); + m_aAsyncLayoutTimer.SetDebugName( "framework::LayoutManager m_aAsyncLayoutTimer" ); +@@ -2225,9 +2226,9 @@ void SAL_CALL LayoutManager::unlock() + // conform to documentation: unlock with lock count == 0 means force a layout + + SolarMutexClearableGuard aWriteLock; +- if ( bDoLayout ) +- m_aAsyncLayoutTimer.Stop(); +- aWriteLock.clear(); ++ if ( bDoLayout ) ++ m_aAsyncLayoutTimer.Stop(); ++ aWriteLock.clear(); + + Any a( nLockCount ); + implts_notifyListeners( frame::LayoutManagerEvents::UNLOCK, a ); +@@ -2626,9 +2627,9 @@ void SAL_CALL LayoutManager::windowResized( const awt::WindowEvent& aEvent ) + if ( !m_aAsyncLayoutTimer.IsActive() ) + { + m_aAsyncLayoutTimer.Invoke(); ++ if ( m_nLockCount == 0 ) ++ m_aAsyncLayoutTimer.Start(); + } +- if ( m_nLockCount == 0 ) +- m_aAsyncLayoutTimer.Start(); + } + else if ( m_xFrame.is() && aEvent.Source == m_xFrame->getContainerWindow() ) + { +@@ -2698,7 +2699,6 @@ void SAL_CALL LayoutManager::windowHidden( const lang::EventObject& aEvent ) + IMPL_LINK_NOARG(LayoutManager, AsyncLayoutHdl, Timer *, void) + { + SolarMutexClearableGuard aReadLock; +- m_aAsyncLayoutTimer.Stop(); + + if( !m_xContainerWindow.is() ) + return; +diff --git a/include/svx/svdetc.hxx b/include/svx/svdetc.hxx +index 19ebf31f3468..a11996868084 100644 +--- a/include/svx/svdetc.hxx ++++ b/include/svx/svdetc.hxx +@@ -182,7 +182,6 @@ class OLEObjCache + size_t nSize; + AutoTimer* pTimer; + +- void UnloadOnDemand(); + static bool UnloadObj( SdrOle2Obj* pObj ); + DECL_LINK( UnloadCheckHdl, Timer*, void ); + +diff --git a/include/vcl/idle.hxx b/include/vcl/idle.hxx +index 707cc1248872..18d4e8abaab7 100644 +--- a/include/vcl/idle.hxx ++++ b/include/vcl/idle.hxx +@@ -35,9 +35,8 @@ private: + sal_uInt64 GetTimeout() const = delete; + + protected: +- virtual bool ReadyForSchedule( bool bIdle, sal_uInt64 nTimeNow ) const override; +- virtual bool IsIdle() const override; +- virtual sal_uInt64 UpdateMinPeriod( sal_uInt64 nMinPeriod, sal_uInt64 nTimeNow ) const override; ++ virtual sal_uInt64 UpdateMinPeriod( ++ sal_uInt64 nMinPeriod, sal_uInt64 nTimeNow ) const override; + + Idle( bool bAuto, const sal_Char *pDebugName = nullptr ); + +@@ -47,6 +46,22 @@ public: + virtual void Start() override; + }; + ++/** ++ * An auto-idle is long running task processing small chunks of data, which ++ * is re-scheduled multiple times. ++ * ++ * Remember to stop the Idle when finished, as it would otherwise busy loop the CPU! ++ * ++ * It probably makes sense to re-implement ReadyForSchedule and UpdateMinPeriod, ++ * in case there is a quick check and it can otherwise sleep. ++ */ ++class VCL_DLLPUBLIC AutoIdle : public Idle ++{ ++public: ++ AutoIdle( const sal_Char *pDebugName = nullptr ); ++}; ++ ++ + #endif // INCLUDED_VCL_IDLE_HXX + + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/include/vcl/scheduler.hxx b/include/vcl/scheduler.hxx +index a34c7c14f9ab..cfa4f84b5a09 100644 +--- a/include/vcl/scheduler.hxx ++++ b/include/vcl/scheduler.hxx +@@ -22,33 +22,55 @@ + + #include + ++class SchedulerGuard; + class Task; ++struct TaskImpl; ++struct ImplSchedulerContext; ++struct ImplSchedulerData; + +-class VCL_DLLPUBLIC Scheduler ++class VCL_DLLPUBLIC Scheduler final + { ++ friend class SchedulerGuard; + friend class Task; +- Scheduler() = delete; ++ Scheduler() SAL_DELETED_FUNCTION; ++ ++ static inline bool HasPendingTasks( const ImplSchedulerContext &rSchedCtx, ++ const sal_uInt64 nTime ); ++ ++ static inline void UpdateSystemTimer( ImplSchedulerContext &rSchedCtx, ++ sal_uInt64 nMinPeriod, ++ bool bForce, sal_uInt64 nTime ); ++ ++ static void ImplStartTimer ( sal_uInt64 nMS, bool bForce, sal_uInt64 nTime ); + +-protected: +- static void ImplStartTimer ( sal_uInt64 nMS, bool bForce = false ); ++ static void Lock( sal_uInt32 nLockCount = 1 ); ++ static sal_uInt32 Unlock( bool bUnlockAll = false ); + + public: +- static constexpr sal_uInt64 ImmediateTimeoutMs = 1; +- static constexpr sal_uInt64 InfiniteTimeoutMs = 1000 * 60 * 60 * 24; // 1 day ++ static constexpr sal_uInt64 ImmediateTimeoutMs = 0; ++ static constexpr sal_uInt64 InfiniteTimeoutMs = SAL_MAX_UINT64; + + static void ImplDeInitScheduler(); + + /// Process one pending Timer with highhest priority +- static void CallbackTaskScheduling( bool ignore ); +- /// Calculate minimum timeout - and return its value. +- static sal_uInt64 CalculateMinimumTimeout( bool &bHasActiveIdles ); ++ static void CallbackTaskScheduling(); + /// Process one pending task ahead of time with highest priority. +- static bool ProcessTaskScheduling( bool bIdle ); ++ static bool ProcessTaskScheduling(); ++ /** ++ * Process all events until none is pending ++ * ++ * This can busy-lock, if some task or system event always generates new ++ * events when being processed. Most time it's called in unit tests to ++ * process all pending events. Internally it just calls ++ * Application::Reschedule( true ) until it fails. ++ * ++ * @see Application::Reschedule ++ */ ++ static void ProcessEventsToIdle(); + /** + * Process events until the parameter turns true, + * allows processing until a specific event has been processed + */ +- static void ProcessEventsToIdle(); + static void ProcessEventsToSignal(bool& bSignal); + + /// Control the deterministic mode. In this mode, two subsequent runs of +@@ -58,70 +80,6 @@ public: + static bool GetDeterministicMode(); + }; + +- +-struct ImplSchedulerData; +- +-enum class TaskPriority +-{ +- HIGHEST = 0, +- HIGH = 1, +- RESIZE = 2, +- REPAINT = 3, +- MEDIUM = 3, +- POST_PAINT = 4, +- DEFAULT_IDLE = 5, +- LOW = 6, +- LOWER = 7, +- LOWEST = 8 +-}; +- +-class VCL_DLLPUBLIC Task +-{ +- friend class Scheduler; +- friend struct ImplSchedulerData; +- +- ImplSchedulerData *mpSchedulerData; /// Pointer to the element in scheduler list +- const sal_Char *mpDebugName; /// Useful for debugging +- TaskPriority mePriority; /// Task priority +- bool mbActive; /// Currently in the scheduler +- +-protected: +- static void StartTimer( sal_uInt64 nMS ); +- +- const ImplSchedulerData* GetSchedulerData() const { return mpSchedulerData; } +- +- virtual void SetDeletionFlags(); +- /// Is this item ready to be dispatched at nTimeNow +- virtual bool ReadyForSchedule( bool bIdle, sal_uInt64 nTimeNow ) const = 0; +- /// Schedule only when other timers and events are processed +- virtual bool IsIdle() const = 0; +- /** +- * Adjust nMinPeriod downwards if we want to be notified before +- * then, nTimeNow is the current time. +- */ +- virtual sal_uInt64 UpdateMinPeriod( sal_uInt64 nMinPeriod, sal_uInt64 nTimeNow ) const = 0; +- +-public: +- Task( const sal_Char *pDebugName ); +- Task( const Task& rTask ); +- virtual ~Task(); +- Task& operator=( const Task& rTask ); +- +- void SetPriority(TaskPriority ePriority) { mePriority = ePriority; } +- TaskPriority GetPriority() const { return mePriority; } +- +- void SetDebugName( const sal_Char *pDebugName ) { mpDebugName = pDebugName; } +- const char *GetDebugName() const { return mpDebugName; } +- +- // Call handler +- virtual void Invoke() = 0; +- +- virtual void Start(); +- void Stop(); +- +- bool IsActive() const { return mbActive; } +-}; +- + #endif // INCLUDED_VCL_SCHEDULER_HXX + + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/include/vcl/svapp.hxx b/include/vcl/svapp.hxx +index 0891859c79e1..cf824a1ac837 100644 +--- a/include/vcl/svapp.hxx ++++ b/include/vcl/svapp.hxx +@@ -462,18 +462,27 @@ public: + */ + static void Quit(); + +- /** Attempt to reschedule in processing of current event(s) ++ /** Attempt to process current pending event(s) + +- @param bAllEvents If set to true, then try to process all the +- events. If set to false, then only process the current +- event. Defaults to false. ++ It doesn't sleep if no events are available for processing. ++ This doesn't processs any events generated after invoking the function. ++ So in contrast to Scheduler::ProcessEventsToIdle, this cannot become ++ busy-locked by an event-generating event in the event queue. + +- @see Execute, Quit, Yield, EndYield, GetSolarMutex, +- GetMainThreadIdentifier, ReleaseSolarMutex, AcquireSolarMutex, ++ @param bHandleAllCurrentEvents If set to true, then try to process all ++ the current events. If set to false, then only process one event. ++ Defaults to false. ++ ++ @returns true if any event was processed. ++ ++ @see Yield, Scheduler::ProcessEventsToIdle + */ +- static void Reschedule( bool bAllEvents = false ); ++ static bool Reschedule( bool bHandleAllCurrentEvents = false ); ++ ++ /** Process the next event. + +- /** Allow processing of the next event. ++ It sleeps if no event is available for processing and just returns ++ if an event was processed. + + @see Execute, Quit, Reschedule, EndYield, GetSolarMutex, + GetMainThreadIdentifier, ReleaseSolarMutex, AcquireSolarMutex, +diff --git a/include/vcl/task.hxx b/include/vcl/task.hxx +new file mode 100644 +index 000000000000..fcad370cc707 +--- /dev/null ++++ b/include/vcl/task.hxx +@@ -0,0 +1,105 @@ ++/* -*- 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 . ++ */ ++ ++#ifndef INCLUDED_VCL_TASK_HXX ++#define INCLUDED_VCL_TASK_HXX ++ ++#include ++#include ++ ++class Scheduler; ++struct ImplSchedulerData; ++ ++enum class TaskPriority ++{ ++ HIGHEST, ///< These events should run very fast! ++ DEFAULT, ///< Default priority used, e.g. the default timer priority ++ HIGH_IDLE, ///< Important idle events to be run before processing drawing events ++ RESIZE, ///< Resize runs before repaint, so we won't paint twice ++ REPAINT, ///< All repaint events should go in here ++ POST_PAINT, ///< Everything running directly after painting ++ DEFAULT_IDLE, ///< Default idle priority ++ LOWEST ///< Low, very idle cleanup tasks ++}; ++ ++class VCL_DLLPUBLIC Task ++{ ++ friend class Scheduler; ++ friend struct ImplSchedulerData; ++ ++ ImplSchedulerData *mpSchedulerData; ///< Pointer to the element in scheduler list ++ const sal_Char *mpDebugName; ///< Useful for debugging ++ TaskPriority mePriority; ///< Task priority ++ bool mbActive; ///< Currently in the scheduler ++ bool mbStatic; ///< Is a static object ++ ++protected: ++ static void StartTimer( sal_uInt64 nMS ); ++ ++ const ImplSchedulerData* GetSchedulerData() const { return mpSchedulerData; } ++ ++ virtual void SetDeletionFlags(); ++ ++ /** ++ * How long (in MS) until the Task is ready to be dispatched? ++ * ++ * Simply return Scheduler::ImmediateTimeoutMs if you're ready, like an ++ * Idle. If you have to return Scheduler::InfiniteTimeoutMs, you probably ++ * need an other mechanism to wake up the Scheduler or rely on other ++ * Tasks to be scheduled, or simply use a polling Timer. ++ * ++ * @param nMinPeriod the currently expected sleep time ++ * @param nTimeNow the current time ++ * @return the sleep time of the Task to become ready ++ */ ++ virtual sal_uInt64 UpdateMinPeriod( sal_uInt64 nMinPeriod, sal_uInt64 nTimeNow ) const = 0; ++ ++public: ++ Task( const sal_Char *pDebugName ); ++ Task( const Task& rTask ); ++ virtual ~Task(); ++ Task& operator=( const Task& rTask ); ++ ++ void SetPriority(TaskPriority ePriority) { mePriority = ePriority; } ++ TaskPriority GetPriority() const { return mePriority; } ++ ++ void SetDebugName( const sal_Char *pDebugName ) { mpDebugName = pDebugName; } ++ const char *GetDebugName() const { return mpDebugName; } ++ ++ // Call handler ++ virtual void Invoke() = 0; ++ ++ virtual void Start(); ++ void Stop(); ++ ++ bool IsActive() const { return mbActive; } ++ ++ /** ++ * This function must be called for static tasks, so the Task destructor ++ * ignores the SchedulerMutex, as it may not be available anymore. ++ * The cleanup is still correct, as it has already happened in ++ * DeInitScheduler call well before the static destructor calls. ++ */ ++ void SetStatic() { mbStatic = true; } ++ bool IsStatic() const { return mbStatic; } ++}; ++ ++#endif // INCLUDED_VCL_TASK_HXX ++ ++/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/include/vcl/timer.hxx b/include/vcl/timer.hxx +index c7bf7253a2d3..ad2206f1e41c 100644 +--- a/include/vcl/timer.hxx ++++ b/include/vcl/timer.hxx +@@ -21,7 +21,7 @@ + #define INCLUDED_VCL_TIMER_HXX + + #include +-#include ++#include + + class VCL_DLLPUBLIC Timer : public Task + { +@@ -31,9 +31,8 @@ class VCL_DLLPUBLIC Timer : public Task + + protected: + virtual void SetDeletionFlags() override; +- virtual bool ReadyForSchedule( bool bIdle, sal_uInt64 nTimeNow ) const override; +- virtual bool IsIdle() const override; +- virtual sal_uInt64 UpdateMinPeriod( sal_uInt64 nMinPeriod, sal_uInt64 nTimeNow ) const override; ++ virtual sal_uInt64 UpdateMinPeriod( ++ sal_uInt64 nMinPeriod, sal_uInt64 nTimeNow ) const override; + + Timer( bool bAuto, const sal_Char *pDebugName = nullptr ); + +@@ -60,11 +59,17 @@ public: + + void SetTimeout( sal_uInt64 nTimeoutMs ); + sal_uInt64 GetTimeout() const { return mnTimeout; } ++ /** ++ * Activates the timer task ++ * ++ * If the timer is already active, it's reset! ++ * Check with Task::IsActive() to prevent reset. ++ */ + virtual void Start() override; + }; + + /// An auto-timer is a multi-shot timer re-emitting itself at +-/// interval until destroyed. ++/// interval until destroyed or stopped. + class VCL_DLLPUBLIC AutoTimer : public Timer + { + public: +diff --git a/reportdesign/source/ui/report/DesignView.cxx b/reportdesign/source/ui/report/DesignView.cxx +index 7e57a17aed8c..030122a37269 100644 +--- a/reportdesign/source/ui/report/DesignView.cxx ++++ b/reportdesign/source/ui/report/DesignView.cxx +@@ -115,7 +115,6 @@ ODesignView::ODesignView( vcl::Window* pParent, + m_aSplitWin->SetAlign(WindowAlign::Left); + m_aSplitWin->Show(); + +- m_aMarkIdle.SetPriority( TaskPriority::LOW ); + m_aMarkIdle.SetInvokeHandler( LINK( this, ODesignView, MarkTimeout ) ); + } + +diff --git a/sc/qa/unit/tiledrendering/tiledrendering.cxx b/sc/qa/unit/tiledrendering/tiledrendering.cxx +index 565aa3bfe08c..1abc93b165b9 100644 +--- a/sc/qa/unit/tiledrendering/tiledrendering.cxx ++++ b/sc/qa/unit/tiledrendering/tiledrendering.cxx +@@ -31,6 +31,7 @@ + #include + #include + #include ++#include + + #include + #include +diff --git a/sc/qa/unoapi/sc_4.sce b/sc/qa/unoapi/sc_4.sce +index 0e615432c14e..bf05df96ac04 100644 +--- a/sc/qa/unoapi/sc_4.sce ++++ b/sc/qa/unoapi/sc_4.sce +@@ -30,6 +30,11 @@ + -o sc.ScHeaderFieldsObj + -o sc.ScHeaderFooterContentObj + -o sc.ScHeaderFooterTextCursor +--o sc.ScHeaderFooterTextObj ++# SHF_TextObj is composed of SHF_TextData, which has a weak reference to ++# SHF_ContentObj, which itself has three references to SHF_TextObj. ++# The css::text::XTextRange test fails often when the weak SHF_ContentObj is ++# already gone. If just this test is disabled, later tests of this object fail ++# too, so this disables the whole interface. ++# -o sc.ScHeaderFooterTextObj + -o sc.ScIndexEnumeration_CellAnnotationsEnumeration + -o sc.ScIndexEnumeration_CellAreaLinksEnumeration +diff --git a/sc/source/core/data/documen2.cxx b/sc/source/core/data/documen2.cxx +index 7d089f2a3d79..99697bf61cb7 100644 +--- a/sc/source/core/data/documen2.cxx ++++ b/sc/source/core/data/documen2.cxx +@@ -250,7 +250,6 @@ ScDocument::ScDocument( ScDocumentMode eMode, SfxObjectShell* pDocShell ) : + SetLanguage( ScGlobal::eLnge, ScGlobal::eLnge, ScGlobal::eLnge ); + + aTrackIdle.SetInvokeHandler( LINK( this, ScDocument, TrackTimeHdl ) ); +- aTrackIdle.SetPriority( TaskPriority::LOW ); + } + + sfx2::LinkManager* ScDocument::GetLinkManager() +diff --git a/sc/source/ui/app/scmod.cxx b/sc/source/ui/app/scmod.cxx +index 863f328c28e7..c6572d68e09e 100644 +--- a/sc/source/ui/app/scmod.cxx ++++ b/sc/source/ui/app/scmod.cxx +@@ -180,7 +180,6 @@ ScModule::ScModule( SfxObjectFactory* pFact ) : + ERRCODE_AREA_APP2-1, + GetResMgr() ); + +- aSpellIdle.SetPriority(TaskPriority::LOWER); + aSpellIdle.SetInvokeHandler( LINK( this, ScModule, SpellTimerHdl ) ); + aSpellIdle.SetDebugName( "sc::ScModule aSpellIdle" ); + +diff --git a/sc/source/ui/miscdlgs/acredlin.cxx b/sc/source/ui/miscdlgs/acredlin.cxx +index e7f2c3964722..7f219719e129 100644 +--- a/sc/source/ui/miscdlgs/acredlin.cxx ++++ b/sc/source/ui/miscdlgs/acredlin.cxx +@@ -108,13 +108,11 @@ ScAcceptChgDlg::ScAcceptChgDlg(SfxBindings* pB, SfxChildWindow* pCW, vcl::Window + m_pAcceptChgCtr = VclPtr::Create(get_content_area(), this); + nAcceptCount=0; + nRejectCount=0; +- aReOpenIdle.SetPriority(TaskPriority::MEDIUM); + aReOpenIdle.SetInvokeHandler(LINK( this, ScAcceptChgDlg, ReOpenTimerHdl )); + + pTPFilter=m_pAcceptChgCtr->GetFilterPage(); + pTPView=m_pAcceptChgCtr->GetViewPage(); + pTheView=pTPView->GetTableControl(); +- aSelectionIdle.SetPriority(TaskPriority::LOW); + aSelectionIdle.SetInvokeHandler(LINK( this, ScAcceptChgDlg, UpdateSelectionHdl )); + aSelectionIdle.SetDebugName( "ScAcceptChgDlg aSelectionIdle" ); + +diff --git a/sc/source/ui/miscdlgs/anyrefdg.cxx b/sc/source/ui/miscdlgs/anyrefdg.cxx +index 2dba375feed6..4a77f1ef9529 100644 +--- a/sc/source/ui/miscdlgs/anyrefdg.cxx ++++ b/sc/source/ui/miscdlgs/anyrefdg.cxx +@@ -762,7 +762,6 @@ ScRefHandler::ScRefHandler( vcl::Window &rWindow, SfxBindings* pB, bool bBindRef + pActiveWin(nullptr) + { + m_aHelper.SetWindow(m_rWindow.get()); +- aIdle.SetPriority(TaskPriority::LOWER); + aIdle.SetInvokeHandler(LINK( this, ScRefHandler, UpdateFocusHdl)); + + if( bBindRef ) EnterRefMode(); +diff --git a/sc/source/ui/miscdlgs/conflictsdlg.cxx b/sc/source/ui/miscdlgs/conflictsdlg.cxx +index a37e1226eb0f..bbab72f1827d 100644 +--- a/sc/source/ui/miscdlgs/conflictsdlg.cxx ++++ b/sc/source/ui/miscdlgs/conflictsdlg.cxx +@@ -417,7 +417,6 @@ ScConflictsDlg::ScConflictsDlg( vcl::Window* pParent, ScViewData* pViewData, ScD + m_pLbConflicts->SetSelectionMode( SelectionMode::Multiple ); + m_pLbConflicts->SetHighlightRange(); + +- maSelectionIdle.SetPriority( TaskPriority::LOW ); + maSelectionIdle.SetInvokeHandler( LINK( this, ScConflictsDlg, UpdateSelectionHdl ) ); + maSelectionIdle.SetDebugName( "ScConflictsDlg maSelectionIdle" ); + +diff --git a/sd/qa/unit/misc-tests.cxx b/sd/qa/unit/misc-tests.cxx +index e90deebc059e..60be08f83c09 100644 +--- a/sd/qa/unit/misc-tests.cxx ++++ b/sd/qa/unit/misc-tests.cxx +@@ -105,7 +105,7 @@ sd::DrawDocShellRef SdMiscTest::Load(const OUString& rURL, sal_Int32 nFormat) + for (int i = 0; i < 1000; i++) + { + // Process all Tasks - slide sorter is created here +- while (Scheduler::ProcessTaskScheduling(true)); ++ while (Scheduler::ProcessTaskScheduling()); + if ((pSSVS = sd::slidesorter::SlideSorterViewShell::GetSlideSorter(pViewShell->GetViewShellBase())) != nullptr) + break; + osl::Thread::wait(std::chrono::milliseconds(100)); +@@ -149,7 +149,7 @@ void SdMiscTest::testTdf96708() + + // Now wait for timers to trigger creation of auto-layout + osl::Thread::wait(std::chrono::milliseconds(100)); +- Scheduler::ProcessTaskScheduling(true); ++ Scheduler::ProcessTaskScheduling(); + + rSSController.GetClipboard().DoPaste(); + const sal_uInt16 nMasterPageCnt2 = xDocSh->GetDoc()->GetMasterSdPageCount(PageKind::Standard); +diff --git a/sd/qa/unit/tiledrendering/tiledrendering.cxx b/sd/qa/unit/tiledrendering/tiledrendering.cxx +index 0a2a2c9e7e2f..920e227dcb76 100644 +--- a/sd/qa/unit/tiledrendering/tiledrendering.cxx ++++ b/sd/qa/unit/tiledrendering/tiledrendering.cxx +@@ -46,6 +46,7 @@ + #include + #include + #include ++#include + + #include + +diff --git a/sd/source/ui/dlg/filedlg.cxx b/sd/source/ui/dlg/filedlg.cxx +index c983e5f585b0..58b0e444b7b2 100644 +--- a/sd/source/ui/dlg/filedlg.cxx ++++ b/sd/source/ui/dlg/filedlg.cxx +@@ -130,7 +130,6 @@ IMPL_LINK_NOARG(SdFileDialog_Imp, PlayMusicHdl, void*, void) + { + mxPlayer.set( avmedia::MediaWindow::createPlayer( aUrl, "" ), css::uno::UNO_QUERY_THROW ); + mxPlayer->start(); +- maUpdateIdle.SetPriority( TaskPriority::LOW ); + maUpdateIdle.Start(); + } + catch (const css::uno::Exception&) +diff --git a/sd/source/ui/framework/module/ShellStackGuard.cxx b/sd/source/ui/framework/module/ShellStackGuard.cxx +index 2372158fe950..79171d026bd2 100644 +--- a/sd/source/ui/framework/module/ShellStackGuard.cxx ++++ b/sd/source/ui/framework/module/ShellStackGuard.cxx +@@ -72,7 +72,6 @@ ShellStackGuard::ShellStackGuard (Reference& rxController) + + // Prepare the printer polling. + maPrinterPollingIdle.SetInvokeHandler(LINK(this,ShellStackGuard,TimeoutHandler)); +- maPrinterPollingIdle.SetPriority(TaskPriority::LOWER); + } + } + +diff --git a/sd/source/ui/view/sdview.cxx b/sd/source/ui/view/sdview.cxx +index 8d6543a45744..09c04ebc3ea6 100644 +--- a/sd/source/ui/view/sdview.cxx ++++ b/sd/source/ui/view/sdview.cxx +@@ -143,9 +143,7 @@ View::View(SdDrawDocument& rDrawDoc, OutputDevice* pOutDev, + + // Timer for delayed drop (has to be for MAC) + maDropErrorIdle.SetInvokeHandler( LINK(this, View, DropErrorHdl) ); +- maDropErrorIdle.SetPriority(TaskPriority::MEDIUM); + maDropInsertFileIdle.SetInvokeHandler( LINK(this, View, DropInsertFileHdl) ); +- maDropInsertFileIdle.SetPriority(TaskPriority::MEDIUM); + } + + void View::ImplClearDrawDropMarker() +diff --git a/sfx2/source/appl/appcfg.cxx b/sfx2/source/appl/appcfg.cxx +index c539b659e3ca..52b061f13b79 100644 +--- a/sfx2/source/appl/appcfg.cxx ++++ b/sfx2/source/appl/appcfg.cxx +@@ -110,7 +110,7 @@ SfxEventAsyncer_Impl::SfxEventAsyncer_Impl( const SfxEventHint& rHint ) + StartListening( *rHint.GetObjShell() ); + pIdle.reset( new Idle("SfxEventASyncer") ); + pIdle->SetInvokeHandler( LINK(this, SfxEventAsyncer_Impl, IdleHdl) ); +- pIdle->SetPriority( TaskPriority::HIGHEST ); ++ pIdle->SetPriority( TaskPriority::HIGH_IDLE ); + pIdle->SetDebugName( "sfx::SfxEventAsyncer_Impl pIdle" ); + pIdle->Start(); + } +diff --git a/sfx2/source/appl/appinit.cxx b/sfx2/source/appl/appinit.cxx +index e6e7596a01aa..cd7823b59418 100644 +--- a/sfx2/source/appl/appinit.cxx ++++ b/sfx2/source/appl/appinit.cxx +@@ -45,7 +45,6 @@ + #include + + #include +-#include + + #include + #include "app.hrc" +@@ -105,10 +104,6 @@ void SAL_CALL SfxTerminateListener_Impl::notifyTermination( const EventObject& a + SolarMutexGuard aGuard; + utl::ConfigManager::storeConfigItems(); + +- // Timers may access the SfxApplication and are only deleted in +- // Application::Quit(), which is asynchronous (PostUserEvent) - disable! +- Scheduler::ImplDeInitScheduler(); +- + SfxApplication* pApp = SfxGetpApp(); + pApp->Broadcast( SfxHint( SfxHintId::Deinitializing ) ); + pApp->Get_Impl()->mxAppDispatch->ReleaseAll(); +diff --git a/sfx2/source/appl/newhelp.cxx b/sfx2/source/appl/newhelp.cxx +index 1315b2a5bf13..9ca083af1704 100644 +--- a/sfx2/source/appl/newhelp.cxx ++++ b/sfx2/source/appl/newhelp.cxx +@@ -552,7 +552,6 @@ IndexTabPage_Impl::IndexTabPage_Impl(vcl::Window* pParent, SfxHelpIndexWindow_Im + + m_pOpenBtn->SetClickHdl( LINK( this, IndexTabPage_Impl, OpenHdl ) ); + aFactoryIdle.SetInvokeHandler( LINK(this, IndexTabPage_Impl, IdleHdl )); +- aFactoryIdle.SetPriority(TaskPriority::LOWER); + aKeywordTimer.SetInvokeHandler( LINK( this, IndexTabPage_Impl, TimeoutHdl ) ); + } + +@@ -1441,7 +1440,6 @@ SfxHelpIndexWindow_Impl::SfxHelpIndexWindow_Impl(SfxHelpWindow_Impl* _pParent) + nMinWidth = ( m_pActiveLB->GetSizePixel().Width() / 2 ); + + aIdle.SetInvokeHandler( LINK( this, SfxHelpIndexWindow_Impl, InitHdl ) ); +- aIdle.SetPriority( TaskPriority::LOWER ); + aIdle.Start(); + + Show(); +diff --git a/sfx2/source/control/dispatch.cxx b/sfx2/source/control/dispatch.cxx +index 9e69b4b8b9f7..4972d2ff2b95 100644 +--- a/sfx2/source/control/dispatch.cxx ++++ b/sfx2/source/control/dispatch.cxx +@@ -437,7 +437,7 @@ void SfxDispatcher::Construct_Impl() + + xImp->xPoster = new SfxHintPoster(aGenLink); + +- xImp->aIdle.SetPriority(TaskPriority::MEDIUM); ++ xImp->aIdle.SetPriority(TaskPriority::HIGH_IDLE ); + xImp->aIdle.SetInvokeHandler( LINK(this, SfxDispatcher, EventHdl_Impl ) ); + xImp->aIdle.SetDebugName( "sfx::SfxDispatcher_Impl aIdle" ); + } +@@ -561,8 +561,6 @@ void SfxDispatcher::Pop(SfxShell& rShell, SfxDispatcherPopFlags nMode) + if(!pSfxApp->IsDowning() && !xImp->aToDoStack.empty()) + { + // No immediate update is requested +- xImp->aIdle.SetPriority(TaskPriority::MEDIUM); +- xImp->aIdle.SetInvokeHandler( LINK(this, SfxDispatcher, EventHdl_Impl ) ); + xImp->aIdle.Start(); + } + else +@@ -757,8 +755,6 @@ void SfxDispatcher::DoActivate_Impl(bool bMDI) + if(!xImp->aToDoStack.empty()) + { + // No immediate update is requested +- xImp->aIdle.SetPriority(TaskPriority::MEDIUM); +- xImp->aIdle.SetInvokeHandler( LINK(this, SfxDispatcher, EventHdl_Impl ) ); + xImp->aIdle.Start(); + } + } +diff --git a/sfx2/source/view/ipclient.cxx b/sfx2/source/view/ipclient.cxx +index 60a20a717040..0dd4851e66c4 100644 +--- a/sfx2/source/view/ipclient.cxx ++++ b/sfx2/source/view/ipclient.cxx +@@ -566,6 +566,7 @@ SfxInPlaceClient::SfxInPlaceClient( SfxViewShell* pViewShell, vcl::Window *pDraw + m_xImp->m_aScaleWidth = m_xImp->m_aScaleHeight = Fraction(1,1); + m_xImp->m_xClient = static_cast< embed::XEmbeddedClient* >( m_xImp.get() ); + pViewShell->NewIPClient_Impl(this); ++ m_xImp->m_aTimer.SetDebugName( "sfx::SfxInPlaceClient m_xImpl::m_aTimer" ); + m_xImp->m_aTimer.SetTimeout( SFX_CLIENTACTIVATE_TIMEOUT ); + m_xImp->m_aTimer.SetInvokeHandler( LINK( m_xImp.get(), SfxInPlaceClient_Impl, TimerHdl ) ); + } +diff --git a/solenv/gdb/libreoffice/vcl.py b/solenv/gdb/libreoffice/vcl.py +index 62dc3a06f09b..d8bac0b84331 100644 +--- a/solenv/gdb/libreoffice/vcl.py ++++ b/solenv/gdb/libreoffice/vcl.py +@@ -16,6 +16,11 @@ class ImplSchedulerDataPrinter(object): + + This can be used to dump the current state of the scheduler via: + p *ImplGetSVData()->mpFirstSchedulerData ++ ++ This doesn't include currently invoked tasks AKA the stack. ++ ++ To dump the scheduler stack of invoked tasks use: ++ p *ImplGetSVData()->mpSchedulerStack + ''' + + def __init__(self, typename, value): +@@ -46,7 +51,7 @@ class ImplSchedulerDataPrinter(object): + if (task_type == "Timer"): + res = "{}: {}ms".format(res, timer['mnTimeout']) + else: +- assert 1 == timer['mnTimeout'], "Idle with timeout == {}".format( timer['mnTimeout'] ) ++ assert 0 == timer['mnTimeout'], "Idle with timeout == {}".format( timer['mnTimeout'] ) + return res + else: + assert gdbobj['mbDelete'], "No task set and not marked for deletion!" +diff --git a/svtools/source/contnr/imivctl1.cxx b/svtools/source/contnr/imivctl1.cxx +index d2d34011fb42..ddb9ecd378c0 100644 +--- a/svtools/source/contnr/imivctl1.cxx ++++ b/svtools/source/contnr/imivctl1.cxx +@@ -143,7 +143,7 @@ SvxIconChoiceCtrl_Impl::SvxIconChoiceCtrl_Impl( + aEditIdle.SetInvokeHandler(LINK(this,SvxIconChoiceCtrl_Impl,EditTimeoutHdl)); + aEditIdle.SetDebugName( "svtools::SvxIconChoiceCtrl_Impl aEditIdle" ); + +- aAutoArrangeIdle.SetPriority( TaskPriority::LOW ); ++ aAutoArrangeIdle.SetPriority( TaskPriority::HIGH_IDLE ); + aAutoArrangeIdle.SetInvokeHandler(LINK(this,SvxIconChoiceCtrl_Impl,AutoArrangeHdl)); + aAutoArrangeIdle.SetDebugName( "svtools::SvxIconChoiceCtrl_Impl aAutoArrangeIdle" ); + +@@ -151,11 +151,11 @@ SvxIconChoiceCtrl_Impl::SvxIconChoiceCtrl_Impl( + aCallSelectHdlIdle.SetInvokeHandler( LINK(this,SvxIconChoiceCtrl_Impl,CallSelectHdlHdl)); + aCallSelectHdlIdle.SetDebugName( "svtools::SvxIconChoiceCtrl_Impl aCallSelectHdlIdle" ); + +- aDocRectChangedIdle.SetPriority( TaskPriority::MEDIUM ); ++ aDocRectChangedIdle.SetPriority( TaskPriority::HIGH_IDLE ); + aDocRectChangedIdle.SetInvokeHandler(LINK(this,SvxIconChoiceCtrl_Impl,DocRectChangedHdl)); + aDocRectChangedIdle.SetDebugName( "svtools::SvxIconChoiceCtrl_Impl aDocRectChangedIdle" ); + +- aVisRectChangedIdle.SetPriority( TaskPriority::MEDIUM ); ++ aVisRectChangedIdle.SetPriority( TaskPriority::HIGH_IDLE ); + aVisRectChangedIdle.SetInvokeHandler(LINK(this,SvxIconChoiceCtrl_Impl,VisRectChangedHdl)); + aVisRectChangedIdle.SetDebugName( "svtools::SvxIconChoiceCtrl_Impl aVisRectChangedIdle" ); + +diff --git a/svx/inc/sdr/contact/objectcontactofpageview.hxx b/svx/inc/sdr/contact/objectcontactofpageview.hxx +index 283b7990fcf1..043fed599065 100644 +--- a/svx/inc/sdr/contact/objectcontactofpageview.hxx ++++ b/svx/inc/sdr/contact/objectcontactofpageview.hxx +@@ -48,7 +48,8 @@ namespace sdr + SdrPage* GetSdrPage() const; + + // basic constructor, used from SdrPageView. +- explicit ObjectContactOfPageView(SdrPageWindow& rPageWindow); ++ explicit ObjectContactOfPageView(SdrPageWindow& rPageWindow, ++ const sal_Char *pDebugName = nullptr); + virtual ~ObjectContactOfPageView() override; + + // LazyInvalidate request. This is used from the VOCs to mark that they +diff --git a/svx/source/dialog/_contdlg.cxx b/svx/source/dialog/_contdlg.cxx +index 98dc5efd828d..7343083b823e 100644 +--- a/svx/source/dialog/_contdlg.cxx ++++ b/svx/source/dialog/_contdlg.cxx +@@ -287,7 +287,6 @@ SvxSuperContourDlg::SvxSuperContourDlg(SfxBindings *_pBindings, SfxChildWindow * + + Resize(); + +- aUpdateIdle.SetPriority( TaskPriority::LOW ); + aUpdateIdle.SetInvokeHandler( LINK( this, SvxSuperContourDlg, UpdateHdl ) ); + + aCreateIdle.SetPriority( TaskPriority::RESIZE ); +diff --git a/svx/source/dialog/imapdlg.cxx b/svx/source/dialog/imapdlg.cxx +index f79cb383c7ce..522ba6290033 100644 +--- a/svx/source/dialog/imapdlg.cxx ++++ b/svx/source/dialog/imapdlg.cxx +@@ -204,7 +204,6 @@ SvxIMapDlg::SvxIMapDlg(SfxBindings *_pBindings, SfxChildWindow *pCW, vcl::Window + m_pCbbTarget->Disable(); + pOwnData->bExecState = false; + +- pOwnData->aIdle.SetPriority( TaskPriority::LOW ); + pOwnData->aIdle.SetInvokeHandler( LINK( this, SvxIMapDlg, UpdateHdl ) ); + + m_pTbxIMapDlg1->EnableItem( mnActiveId, false ); +diff --git a/svx/source/form/fmsrcimp.cxx b/svx/source/form/fmsrcimp.cxx +index 8ad18af31eee..f82a8db80938 100644 +--- a/svx/source/form/fmsrcimp.cxx ++++ b/svx/source/form/fmsrcimp.cxx +@@ -335,13 +335,7 @@ FmSearchEngine::SearchResult FmSearchEngine::SearchSpecial(bool _bSearchForNull, + bool bMovedAround(false); + do + { +- Application::Reschedule(); +- Application::Reschedule(); +- // do 2 reschedules because of #70226# : some things done within this loop's body may cause an user event +- // to be posted (deep within vcl), and these user events will be handled before any keyinput or paintings +- // or anything like that. So within each loop we create one user event and handle one user event (and no +- // paintings and these), so the office seems to be frozen while searching. +- // FS - 70226 - 02.12.99 ++ Application::Reschedule( true ); + + // the content to be compared currently + iterFieldLoop->xContents->getString(); // needed for wasNull +@@ -400,13 +394,7 @@ FmSearchEngine::SearchResult FmSearchEngine::SearchWildcard(const OUString& strE + bool bMovedAround(false); + do + { +- Application::Reschedule(); +- Application::Reschedule(); +- // do 2 reschedules because of #70226# : some things done within this loop's body may cause an user event +- // to be posted (deep within vcl), and these user events will be handled before any keyinput or paintings +- // or anything like that. So within each loop we create one user event and handle one user event (and no +- // paintings and these), so the office seems to be frozen while searching. +- // FS - 70226 - 02.12.99 ++ Application::Reschedule( true ); + + // the content to be compared currently + OUString sCurrentCheck; +@@ -500,13 +488,7 @@ FmSearchEngine::SearchResult FmSearchEngine::SearchRegularApprox(const OUString& + bool bMovedAround(false); + do + { +- Application::Reschedule(); +- Application::Reschedule(); +- // do 2 reschedules because of #70226# : some things done within this loop's body may cause an user event +- // to be posted (deep within vcl), and these user events will be handled before any keyinput or paintings +- // or anything like that. So within each loop we create one user event and handle one user event (and no +- // paintings and these), so the office seems to be frozen while searching. +- // FS - 70226 - 02.12.99 ++ Application::Reschedule( true ); + + // the content to be compared currently + OUString sCurrentCheck; +diff --git a/svx/source/sdr/contact/objectcontactofpageview.cxx b/svx/source/sdr/contact/objectcontactofpageview.cxx +index 552ac31866bf..39d141973862 100644 +--- a/svx/source/sdr/contact/objectcontactofpageview.cxx ++++ b/svx/source/sdr/contact/objectcontactofpageview.cxx +@@ -53,15 +53,17 @@ namespace sdr + return GetPageWindow().GetPageView().GetPage(); + } + +- ObjectContactOfPageView::ObjectContactOfPageView(SdrPageWindow& rPageWindow) +- : ObjectContact(), +- mrPageWindow(rPageWindow) ++ ObjectContactOfPageView::ObjectContactOfPageView( ++ SdrPageWindow& rPageWindow, const sal_Char *pDebugName) ++ : ObjectContact() ++ , Idle(pDebugName) ++ , mrPageWindow(rPageWindow) + { + // init PreviewRenderer flag + setPreviewRenderer(((SdrPaintView&)rPageWindow.GetPageView().GetView()).IsPreviewRenderer()); + + // init timer +- SetPriority(TaskPriority::HIGH); ++ SetPriority(TaskPriority::HIGH_IDLE); + Stop(); + } + +diff --git a/svx/source/sdr/contact/viewobjectcontactofpageobj.cxx b/svx/source/sdr/contact/viewobjectcontactofpageobj.cxx +index 3c33a3ee8e93..b54e19145e8e 100644 +--- a/svx/source/sdr/contact/viewobjectcontactofpageobj.cxx ++++ b/svx/source/sdr/contact/viewobjectcontactofpageobj.cxx +@@ -84,7 +84,7 @@ PagePrimitiveExtractor::PagePrimitiveExtractor( + setPreviewRenderer(true); + + // init timer +- SetPriority(TaskPriority::HIGH); ++ SetPriority(TaskPriority::HIGH_IDLE); + Stop(); + } + +diff --git a/svx/source/sdr/event/eventhandler.cxx b/svx/source/sdr/event/eventhandler.cxx +index 9511c623a1a2..8d0e29592c92 100644 +--- a/svx/source/sdr/event/eventhandler.cxx ++++ b/svx/source/sdr/event/eventhandler.cxx +@@ -81,7 +81,7 @@ namespace sdr + + TimerEventHandler::TimerEventHandler() + { +- SetPriority(TaskPriority::HIGH); ++ SetPriority(TaskPriority::HIGH_IDLE); + Stop(); + } + +diff --git a/svx/source/svdraw/sdrpagewindow.cxx b/svx/source/svdraw/sdrpagewindow.cxx +index 85a0117df26f..f6f2edf4720c 100644 +--- a/svx/source/svdraw/sdrpagewindow.cxx ++++ b/svx/source/svdraw/sdrpagewindow.cxx +@@ -456,7 +456,8 @@ void SdrPageWindow::InvalidatePageWindow(const basegfx::B2DRange& rRange) + const sdr::contact::ObjectContact& SdrPageWindow::GetObjectContact() const + { + if (!mpImpl->mpObjectContact) +- mpImpl->mpObjectContact = new sdr::contact::ObjectContactOfPageView(const_cast(*this)); ++ mpImpl->mpObjectContact = new sdr::contact::ObjectContactOfPageView( ++ const_cast(*this), "svx::svdraw::SdrPageWindow mpObjectContact" ); + + return *mpImpl->mpObjectContact; + } +@@ -464,7 +465,8 @@ const sdr::contact::ObjectContact& SdrPageWindow::GetObjectContact() const + sdr::contact::ObjectContact& SdrPageWindow::GetObjectContact() + { + if (!mpImpl->mpObjectContact) +- mpImpl->mpObjectContact = new sdr::contact::ObjectContactOfPageView(*this); ++ mpImpl->mpObjectContact = new sdr::contact::ObjectContactOfPageView( ++ *this, "svx::svdraw::SdrPageWindow mpObjectContact" ); + + return *mpImpl->mpObjectContact; + } +diff --git a/svx/source/svdraw/svdetc.cxx b/svx/source/svdraw/svdetc.cxx +index 3e78145dcf7f..4d06db5142a0 100644 +--- a/svx/source/svdraw/svdetc.cxx ++++ b/svx/source/svdraw/svdetc.cxx +@@ -113,8 +113,7 @@ OLEObjCache::OLEObjCache() + pTimer = new AutoTimer( "svx OLEObjCache pTimer UnloadCheck" ); + pTimer->SetInvokeHandler( LINK(this, OLEObjCache, UnloadCheckHdl) ); + pTimer->SetTimeout(20000); +- pTimer->Invoke(); +- pTimer->Start(); ++ pTimer->SetStatic(); + } + + OLEObjCache::~OLEObjCache() +@@ -123,7 +122,7 @@ OLEObjCache::~OLEObjCache() + delete pTimer; + } + +-void OLEObjCache::UnloadOnDemand() ++IMPL_LINK_NOARG(OLEObjCache, UnloadCheckHdl, Timer*, void) + { + if (nSize >= maObjs.size()) + return; +@@ -191,11 +190,12 @@ void OLEObjCache::InsertObj(SdrOle2Obj* pObj) + // insert object into first position + maObjs.insert(maObjs.begin(), pObj); + +- if ( !bFound ) +- { +- // a new object was inserted, recalculate the cache +- UnloadOnDemand(); +- } ++ // if a new object was inserted, recalculate the cache ++ if (!bFound) ++ pTimer->Invoke(); ++ ++ if (!bFound || !pTimer->IsActive()) ++ pTimer->Start(); + } + + void OLEObjCache::RemoveObj(SdrOle2Obj* pObj) +@@ -203,6 +203,8 @@ void OLEObjCache::RemoveObj(SdrOle2Obj* pObj) + std::vector::iterator it = std::find(maObjs.begin(), maObjs.end(), pObj); + if (it != maObjs.end()) + maObjs.erase(it); ++ if (maObjs.empty()) ++ pTimer->Stop(); + } + + size_t OLEObjCache::size() const +@@ -244,12 +246,6 @@ bool OLEObjCache::UnloadObj(SdrOle2Obj* pObj) + return bUnloaded; + } + +-IMPL_LINK_NOARG(OLEObjCache, UnloadCheckHdl, Timer*, void) +-{ +- UnloadOnDemand(); +-} +- +- + bool GetDraftFillColor(const SfxItemSet& rSet, Color& rCol) + { + drawing::FillStyle eFill=static_cast(rSet.Get(XATTR_FILLSTYLE)).GetValue(); +diff --git a/svx/source/svdraw/svdibrow.cxx b/svx/source/svdraw/svdibrow.cxx +index a83797f3c11a..1412eddb7fed 100644 +--- a/svx/source/svdraw/svdibrow.cxx ++++ b/svx/source/svdraw/svdibrow.cxx +@@ -1099,7 +1099,7 @@ void SdrItemBrowser::SetDirty() + { + if (!bDirty) { + bDirty = true; +- aIdle.SetPriority(TaskPriority::HIGH); ++ aIdle.SetPriority(TaskPriority::HIGH_IDLE); + aIdle.Start(); + } + } +diff --git a/svx/source/tbxctrls/grafctrl.cxx b/svx/source/tbxctrls/grafctrl.cxx +index 71ef9f35b48f..52b99b08e697 100644 +--- a/svx/source/tbxctrls/grafctrl.cxx ++++ b/svx/source/tbxctrls/grafctrl.cxx +@@ -120,7 +120,6 @@ ImplGrafMetricField::ImplGrafMetricField( vcl::Window* pParent, const OUString& + SetSpinSize( 1 ); + } + +- maIdle.SetPriority( TaskPriority::LOW ); + maIdle.SetInvokeHandler( LINK( this, ImplGrafMetricField, ImplModifyHdl ) ); + } + +diff --git a/sw/qa/extras/tiledrendering/tiledrendering.cxx b/sw/qa/extras/tiledrendering/tiledrendering.cxx +index cd67c8b28fcb..f2b841fd5d73 100644 +--- a/sw/qa/extras/tiledrendering/tiledrendering.cxx ++++ b/sw/qa/extras/tiledrendering/tiledrendering.cxx +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + + static const char* const DATA_DIRECTORY = "/sw/qa/extras/tiledrendering/data/"; + +diff --git a/sw/qa/extras/uiwriter/uiwriter.cxx b/sw/qa/extras/uiwriter/uiwriter.cxx +index d73a8aa083cb..ddfcf31a3fef 100644 +--- a/sw/qa/extras/uiwriter/uiwriter.cxx ++++ b/sw/qa/extras/uiwriter/uiwriter.cxx +@@ -104,6 +104,7 @@ + #include + #include + #include ++#include + #include + #include + +diff --git a/sw/source/ui/dbui/addresslistdialog.cxx b/sw/source/ui/dbui/addresslistdialog.cxx +index 617957d5c7a6..161314d5471a 100644 +--- a/sw/source/ui/dbui/addresslistdialog.cxx ++++ b/sw/source/ui/dbui/addresslistdialog.cxx +@@ -487,8 +487,7 @@ IMPL_LINK(SwAddressListDialog, StaticListBoxSelectHdl_Impl, void*, p, void) + m_pListLB->SetEntryText(m_sConnecting, pSelect, ITEMID_TABLE - 1); + // allow painting of the new entry + m_pListLB->Window::Invalidate(InvalidateFlags::Update); +- for (int i = 0; i < 10; ++i) +- Application::Reschedule(); ++ Application::Reschedule( true ); + } + + pUserData = static_cast(pSelect->GetUserData()); +diff --git a/sw/source/ui/dbui/mmresultdialogs.cxx b/sw/source/ui/dbui/mmresultdialogs.cxx +index f1d27c661786..69196b036eef 100644 +--- a/sw/source/ui/dbui/mmresultdialogs.cxx ++++ b/sw/source/ui/dbui/mmresultdialogs.cxx +@@ -720,8 +720,7 @@ IMPL_LINK(SwMMResultSaveDialog, SaveOutputHdl_Impl, Button*, pButton, void) + while(true) + { + //time for other slots is needed +- for(sal_Int16 r = 0; r < 10; ++r) +- Application::Reschedule(); ++ Application::Reschedule( true ); + bool bFailed = false; + try + { +@@ -1091,8 +1090,7 @@ IMPL_LINK(SwMMResultEmailDialog, SendDocumentsHdl_Impl, Button*, pButton, void) + //help to force painting the dialog + //TODO/CLEANUP + //predetermined breaking point +- for ( sal_Int16 i = 0; i < 25; i++) +- Application::Reschedule(); ++ Application::Reschedule( true ); + for(sal_uInt32 nDoc = nBegin; nDoc < nEnd; ++nDoc) + { + SwDocMergeInfo& rInfo = xConfigItem->GetDocumentMergeInfo(nDoc); +@@ -1235,8 +1233,7 @@ IMPL_LINK(SwMMResultEmailDialog, SendDocumentsHdl_Impl, Button*, pButton, void) + aDesc.sBCC = m_sBCC; + pDlg->AddDocument( aDesc ); + //help to force painting the dialog +- for ( sal_Int16 i = 0; i < 25; i++) +- Application::Reschedule(); ++ Application::Reschedule( true ); + //stop creating of data when dialog has been closed + if(!pDlg->IsVisible()) + { +diff --git a/sw/source/uibase/dbui/dbmgr.cxx b/sw/source/uibase/dbui/dbmgr.cxx +index 8da790f098cf..afecb05102d7 100644 +--- a/sw/source/uibase/dbui/dbmgr.cxx ++++ b/sw/source/uibase/dbui/dbmgr.cxx +@@ -148,11 +148,6 @@ using namespace ::com::sun::star; + + namespace { + +-void rescheduleGui() { +- for( sal_uInt16 i = 0; i < 25; i++) +- Application::Reschedule(); +-} +- + void lcl_emitEvent(SfxEventHintId nEventId, sal_Int32 nStrId, SfxObjectShell* pDocShell) + { + SfxGetpApp()->NotifyEvent(SfxEventHint(nEventId, +@@ -1259,7 +1254,7 @@ bool SwDBManager::MergeMailFiles(SwWrtShell* pSourceShell, + pProgressDlg->SetCancelHdl( LINK(this, SwDBManager, PrtCancelHdl) ); + pProgressDlg->Show(); + +- rescheduleGui(); ++ Application::Reschedule( true ); + } + + if( bCreateSingleFile && !pTargetView ) +@@ -1402,7 +1397,7 @@ bool SwDBManager::MergeMailFiles(SwWrtShell* pSourceShell, + pProgressDlg->Update(); + } + +- rescheduleGui(); ++ Application::Reschedule( true ); + + // Create a copy of the source document and work with that one instead of the source. + // If we're not in the single file mode (which requires modifying the document for the merging), +@@ -1573,7 +1568,7 @@ bool SwDBManager::MergeMailFiles(SwWrtShell* pSourceShell, + } + else if( IsMergeOk() ) // && bCreateSingleFile + { +- rescheduleGui(); ++ Application::Reschedule( true ); + + // sw::DocumentLayoutManager::CopyLayoutFormat() did not generate + // unique fly names, do it here once. +@@ -1592,7 +1587,7 @@ bool SwDBManager::MergeMailFiles(SwWrtShell* pSourceShell, + aLayout->AllCheckPageDescs(); + } + +- rescheduleGui(); ++ Application::Reschedule( true ); + + if( IsMergeOk() && bMT_FILE ) + { +@@ -1630,7 +1625,7 @@ bool SwDBManager::MergeMailFiles(SwWrtShell* pSourceShell, + else if( xTargetDocShell.is() ) + xTargetDocShell->DoClose(); + +- rescheduleGui(); ++ Application::Reschedule( true ); + + pProgressDlg.disposeAndClear(); + +diff --git a/sw/source/uibase/docvw/srcedtw.cxx b/sw/source/uibase/docvw/srcedtw.cxx +index dde4240f0262..a321298f568d 100644 +--- a/sw/source/uibase/docvw/srcedtw.cxx ++++ b/sw/source/uibase/docvw/srcedtw.cxx +@@ -534,7 +534,6 @@ void SwSrcEditWindow::CreateTextEngine() + m_pOutWin->SetFont( aFont ); + m_pTextEngine->SetFont( aFont ); + +- m_aSyntaxIdle.SetPriority( TaskPriority::LOWER ); + m_aSyntaxIdle.SetInvokeHandler( LINK( this, SwSrcEditWindow, SyntaxTimerHdl ) ); + + m_pTextEngine->EnableUndo( true ); +diff --git a/sw/source/uibase/utlui/unotools.cxx b/sw/source/uibase/utlui/unotools.cxx +index 571f39ad80fd..6130ee0efa05 100644 +--- a/sw/source/uibase/utlui/unotools.cxx ++++ b/sw/source/uibase/utlui/unotools.cxx +@@ -82,7 +82,7 @@ SwOneExampleFrame::SwOneExampleFrame( vcl::Window& rWin, + + // the controller is asynchronously set + m_aLoadedIdle.SetInvokeHandler(LINK(this, SwOneExampleFrame, TimeoutHdl)); +- m_aLoadedIdle.SetPriority(TaskPriority::HIGH); ++ m_aLoadedIdle.SetPriority(TaskPriority::HIGH_IDLE); + + CreateControl(); + +diff --git a/tools/source/datetime/ttime.cxx b/tools/source/datetime/ttime.cxx +index 2c0ebe4a1b53..83812f72171a 100644 +--- a/tools/source/datetime/ttime.cxx ++++ b/tools/source/datetime/ttime.cxx +@@ -433,7 +433,7 @@ sal_uInt64 tools::Time::GetSystemTicks() + SAL_WARN("tools.datetime", "gettimeofday failed: " << e); + } + return static_cast(tv.tv_sec) * 1000 +- + (static_cast(tv.tv_usec) + 500) / 1000; ++ + static_cast(tv.tv_usec) / 1000; + #endif + } + +diff --git a/vcl/Module_vcl.mk b/vcl/Module_vcl.mk +index a5e4bf5d68d2..a741f25722b3 100644 +--- a/vcl/Module_vcl.mk ++++ b/vcl/Module_vcl.mk +@@ -177,6 +177,12 @@ $(eval $(call gb_Module_add_check_targets,vcl,\ + )) + endif + ++ifeq ($(OS),MACOSX) ++$(eval $(call gb_Module_add_check_targets,vcl,\ ++ CppunitTest_vcl_timer \ ++)) ++endif ++ + # screenshots + $(eval $(call gb_Module_add_screenshot_targets,vcl,\ + CppunitTest_vcl_dialogs_test \ +diff --git a/vcl/README.scheduler b/vcl/README.scheduler +new file mode 100644 +index 000000000000..e46e6035eef3 +--- /dev/null ++++ b/vcl/README.scheduler +@@ -0,0 +1,264 @@ ++= Introduction = ++ ++The VCL scheduler handles LOs primary event queue. It is simple by design, ++currently just a single-linked list, processed in list-order by priority ++using round-robin for reoccuring tasks. ++ ++The scheduler has the following behaviour: ++ ++B.1. Tasks are scheduled just priority based ++B.2. Implicitly cooperative AKA non-preemptive ++B.3. It's not "fair" in any way (a consequence of B.2) ++B.4. Tasks are handled round-robin (per priority) ++B.5. Higher priorities have lower values ++B.6. A small set of priorities instead of an flexible value AKA int ++ ++There are some consequences due to this design. ++ ++C.1. Higher priorority tasks starve lower priority tasks ++ As long as a higher task is available, lower tasks are never run! ++ See Anti-pattern. ++ ++C.2. Tasks should be split into sensible blocks ++ If this can't really be done, process pending tasks by calling ++ Application::Reschedule(). Or use a thread. ++ ++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 an preemptive task, use a thread! ++ Otherwise mke your task suspendable and check SalInstance::AnyInput ++ or call Application::Reschedule regularly. ++ ++ ++= Driving the scheduler AKA the system timer = ++ ++ 1. There is just one system timer, which drives LO event loop ++ 2. The timer has to run in the main window thread ++ 3. The scheduler is run with the Solar mutex acquired ++ 4. The system timer is a single-shot timer ++ 5. The scheduler system event / message has a low system priority. ++ All system events should have a higher priority. ++ ++Everytime 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 ++importent 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 importent to get a correct SalInstance::AnyInput ++handling, as this is used to suspend long background Idle tasks. ++ ++Everytime 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 ++task. After invoking the task and if the task is still active, it is pushed ++to the end of the queue and the timeout is eventually adjusted. ++ ++ ++= Locking = ++ ++The locking is quite primitve: all interaction with internal Scheduler ++structures are locked. This includes the ImplSchedulerContext and the ++Task::mpSchedulerData, which is actually a part of the scheduler. ++Before invoking the task, we have to release the lock, so others can ++Start new Tasks. ++ ++ ++= Lifecycle / thread-safety of Scheduler-based objects = ++ ++A scheduler object it thread-safe in the way, that it can be associated to ++any thread and any thread is free to call any functions on it. The owner must ++guarantee that the Invoke() function can be called, while the Scheduler object ++exists / is not disposed. ++ ++ ++= Anti-pattern: Dependencies via (fine grained) priorities = ++ ++"Idle 1" should run before "Idle 2", therefore give "Idle 1" a higher priority ++then "Idle 2". This just works correct for low frequency idles, but otherwise ++always breaks! ++ ++If you have some longer work - even if it can be split by into schedulable, ++smaller blocks - you normally don't want to schedule it with a non-default ++priority, as it starves all lower priority tasks. Even if a block was processed ++in "Idle 1", it is scheduled with the same (higher) priority again. Changing ++the "Idle" to a "Timer" also won't work, as this breaks the dependency. ++ ++What is needed is task based dependency handling, so if "Task 1" is done, it ++has to start "Task 2" and if "Task 1" is started again, it has to stop ++"Task 2". This currently has to be done by the implementor, but this feature ++can be added to the scheduler reasonably. ++ ++ ++= Implementation details = ++ ++== General: main thread deferral == ++ ++Currently for Mac and Windows, we run main thread deferrals by disabling the ++SolarMutex using a boolean. In the case of the redirect, this makes ++tryToaAcquire and doAcquire return true or 1, while a release is ignored. ++Also the IsCurrentThread() mutex check function will act accordingly, so all ++the DBG_TESTSOLARMUTEX won't fail. ++ ++Since we just disable the locks when we start running the deferred code in the ++main thread, we won't let the main thread run into stuff, where it would ++normally wait for the SolarMutex. ++ ++Eventually this will move into the GenericSolarMutex. KDE / Qt also does main ++thread redirects using Qt::BlockingQueuedConnection. ++ ++== General: processing all current events for DoYield == ++ ++This is easily implemented for all non-priority queue based implementations. ++Windows and MacOS both have a timestamp attached to their events / messages, ++so simply get the current time and just process anything < timestamp. ++For the KDE backend this is already the default behaviour - single event ++processing isn't even supported. The headless backend accomplishes this by ++just processing a copy of the list of current events. ++ ++Problematic in this regard is the Gtk+ backend. g_main_context_iteration ++dispatches "only those highest priority event sources". There is no real way ++to tell, when these became ready. I've added a workaround idea to the TODO ++list. FWIW: Qt runs just a single timer source in the glib main context, ++basically the same we're doing with the LO scheduler as a system event. ++ ++The gen X11 backend has some levels of redirection, but needs quite some work ++to get this fixed. ++ ++== MacOS implementation details == ++ ++Generally the Scheduler is handled as expected, except on resize, which is ++handled with different runloop-modes in MacOS. In case of a resize, the normal ++runloop is suspended in sendEvent, so we can't call the scheduler via posted ++main loop-events. Instead the schedule the timer again. ++ ++There is also a workaround for a problem for pushing tasks to an empty queue, ++as [NSApp postEvent: ... atStart: NO] doesn't append the event, if the ++message queue is empty. ++ ++Probably that's the reason, why some code comments spoke of lost events and ++there was some distinct additional event processing implemented. ++ ++== Windows implementation details == ++ ++Posted or sent event messages often trigger processing of WndProc in ++PeekMessage, GetMessage or DispatchMessage, independently from the message to ++fetch, remove or dispatch ("During this call, the system delivers pending, ++nonqueued messages..."). Additionally messages have an inherited priority ++based on the function used to generate them. Even if WM_TIMER should have been ++the lowest prio, a posted WM_TIMER is processed with the prio of a posted ++message. ++ ++Therefore the current solution always starts a (threaded) timer even for the ++instant Idles and syncs to this timer message in the main dispatch loop. ++Using SwitchToThread(), this seem to work reasonably well. ++ ++An additional workaround is implemented for the delayed queuing of posted ++messages, where PeekMessage in WinSalTimer::Stop() won't be able remove the ++just posted timer callback message. We handle this by adding a timestamp to ++the timer callback message, which is checked before starting the Scheduler. ++This way we can end with multiple timer callback message in the queue, which ++we were asserting. ++ ++== KDE implementation details == ++ ++This inplementation also works as intended. But there is a different Yield ++handling, because Qts QAbstractEventDispatcher::processEvents will allways ++process all pending events. ++ ++ ++= TODOs and ideas = ++ ++== Task dependencies AKA children == ++ ++Every task can have a list of children / a child. ++ ++ * When a task is stopped, the children are started. ++ * When a task is started, the children are stopped. ++ ++This should be easy to implement. ++ ++== Per priority time-sorted queues == ++ ++This would result in O(1) scheduler. It was used in the Linux kernel for some ++time (search Ingo Molinars O(1) scheduler). This can be a scheduling ++optimization, which would prevent walking longer event list. But probably the ++management overhead would be too large, as we have many one-shot events. ++ ++To find the next task the scheduler just walks the (constant) list of priority ++queues and schedules the first ready event of any queue. ++ ++The downside of this approach: Insert / Start / Reschedule(for "auto" tasks) ++now need O(log(n)) to find the position in the queue of the priority. ++ ++== Always process all (higher priority) pending events == ++ ++Currently Application::Reschedule() processes a single event or "all" events, ++with "all" defined as "100 events" in most backends. This already is ignored ++by the KDE4 backend, as Qt defines its QAbstractEventDispatcher::processEvents ++processing all pending events (there are ways to skip event classes, but no ++easy way to process just a single event). ++ ++Since the Scheduler is always handled by the system message queue, there is ++really no more reasoning to stop after 100 events to prevent LO Scheduler ++starvation. ++ ++== Drop static inherited or composed Task objects == ++ ++The sequence of destruction of static objects is not defined. So a static Task ++can not be guaranteed to happen before the Scheduler. When dynamic unloading ++is involved, this becomes an even worse problem. This way we could drop the ++mbStatic workaround from the Task class. ++ ++== Run the LO application in its own thread == ++ ++This would probably get rid of most of the MacOS and Windows implementation ++details / workarounds, but is quite probably a large amount of work. ++ ++Instead of LO running in the main process / thread, we run it in a 2nd thread ++and defer al GUI calls to the main thread. This way it'll hopefully not block ++and can process system events. ++ ++That's just a theory - it definitly needs more analysis before even attemding ++an implementation. ++ ++== Re-evaluate the MacOS ImplNSAppPostEvent == ++ ++Probably a solution comparable to the Windows backends delayed PostMessage ++workaround using a validation timestamp is better then the current peek, ++remove, re-postEvent, which has to run in the main thread. ++ ++Originally I didn't evaluate, if the event is actually lost or just delayed. ++ ++== Drop nMaxEvents from Gtk+ based backends == ++ ++gint last_priority = G_MAXINT; ++bool bWasEvent = false; ++do { ++ gint max_priority; ++ g_main_context_acquire( NULL ); ++ bool bHasPending = g_main_context_prepare( NULL, &max_priority ); ++ g_main_context_release( NULL ); ++ if ( bHasPending ) ++ { ++ if ( last_priority > max_priority ) ++ { ++ bHasPending = g_main_context_iteration( NULL, bWait ); ++ bWasEvent = bWasEvent || bHasPending; ++ } ++ else ++ bHasPending = false; ++ } ++} ++while ( bHasPending ) ++ ++The idea is to use g_main_context_prepare and keep the max_priority as an ++indicator. We cannot prevent running newer lower events, but we can prevent ++running new higher events, which should be sufficient for most stuff. ++ ++This also touches user event processing, which currently runs as a high ++priority idle in the event loop. ++ ++== Drop nMaxEvents from gen (X11) backend == ++ ++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 refactorung and rearchitecture step, which ++will also affect the Gtk+ and KDE4 backend for the user event handling. +diff --git a/vcl/headless/svpinst.cxx b/vcl/headless/svpinst.cxx +index 8e9cdbc9543c..552719aabb28 100644 +--- a/vcl/headless/svpinst.cxx ++++ b/vcl/headless/svpinst.cxx +@@ -180,7 +180,7 @@ bool SvpSalInstance::PostedEventsInQueue() + bool result = false; + { + osl::MutexGuard g(m_aEventGuard); +- result = m_aUserEvents.size() > 0; ++ result = !m_aUserEvents.empty(); + } + return result; + } +@@ -238,11 +238,8 @@ bool SvpSalInstance::CheckTimeout( bool bExecuteTimers ) + + // notify + ImplSVData* pSVData = ImplGetSVData(); +- if( pSVData->mpSalTimer ) +- { +- bool idle = true; // TODO +- pSVData->mpSalTimer->CallCallback( idle ); +- } ++ if( pSVData->maSchedCtx.mpSalTimer ) ++ pSVData->maSchedCtx.mpSalTimer->CallCallback(); + } + } + } +@@ -307,15 +304,13 @@ SalBitmap* SvpSalInstance::CreateSalBitmap() + #endif + } + +-SalYieldResult SvpSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong const nReleased) ++bool SvpSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong const nReleased) + { + (void) nReleased; + assert(nReleased == 0); // not implemented + // first, check for already queued events. + +- // release yield mutex + std::list< SalUserEvent > aEvents; +- sal_uLong nAcquireCount = ReleaseYieldMutex(); + { + osl::MutexGuard g(m_aEventGuard); + if( ! m_aUserEvents.empty() ) +@@ -332,8 +327,6 @@ SalYieldResult SvpSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents, + } + } + } +- // acquire yield mutex again +- AcquireYieldMutex( nAcquireCount ); + + bool bEvent = !aEvents.empty(); + if( bEvent ) +@@ -363,19 +356,28 @@ SalYieldResult SvpSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents, + timeval Timeout; + // determine remaining timeout. + gettimeofday (&Timeout, nullptr); +- nTimeoutMS = (m_aTimeout.tv_sec - Timeout.tv_sec) * 1000 +- + m_aTimeout.tv_usec/1000 - Timeout.tv_usec/1000; +- if( nTimeoutMS < 0 ) +- nTimeoutMS = 0; ++ if ( m_aTimeout > Timeout ) ++ { ++ int nTimeoutMicroS = m_aTimeout.tv_usec - Timeout.tv_usec; ++ nTimeoutMS = (m_aTimeout.tv_sec - Timeout.tv_sec) * 1000 ++ + nTimeoutMicroS / 1000; ++ if ( nTimeoutMicroS % 1000 ) ++ nTimeoutMS += 1; ++ } + } + else + nTimeoutMS = -1; // wait until something happens + + DoReleaseYield(nTimeoutMS); + } ++ else if ( bEvent ) ++ { ++ // Drain the wakeup pipe ++ int buffer; ++ while (read (m_pTimeoutFDS[0], &buffer, sizeof(buffer)) > 0); ++ } + +- return bEvent ? SalYieldResult::EVENT : +- SalYieldResult::TIMEOUT; ++ return bEvent; + } + + void SvpSalInstance::DoReleaseYield( int nTimeoutMS ) +@@ -398,8 +400,7 @@ void SvpSalInstance::DoReleaseYield( int nTimeoutMS ) + if( (aPoll.revents & POLLIN) != 0 ) + { + int buffer; +- while (read (m_pTimeoutFDS[0], &buffer, sizeof(buffer)) > 0) +- continue; ++ while (read (m_pTimeoutFDS[0], &buffer, sizeof(buffer)) > 0); + } + } + +diff --git a/vcl/inc/headless/svpinst.hxx b/vcl/inc/headless/svpinst.hxx +index 731642f5762c..e8bb96ab68ac 100644 +--- a/vcl/inc/headless/svpinst.hxx ++++ b/vcl/inc/headless/svpinst.hxx +@@ -155,8 +155,9 @@ public: + // wait next event and dispatch + // must returned by UserEvent (SalFrame::PostEvent) + // and timer +- virtual SalYieldResult DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong nReleased) override; ++ virtual bool DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong nReleased) override; + virtual bool AnyInput( VclInputFlags nType ) override; ++ virtual bool IsMainThread() const override { return true; } + + // may return NULL to disable session management + virtual SalSession* CreateSalSession() override; +diff --git a/vcl/inc/salinst.hxx b/vcl/inc/salinst.hxx +index ca8017d94513..d8b9b0a32d64 100644 +--- a/vcl/inc/salinst.hxx ++++ b/vcl/inc/salinst.hxx +@@ -58,8 +58,6 @@ class Menu; + enum class VclInputFlags; + enum class SalFrameStyleFlags; + +-enum SalYieldResult { EVENT, TIMEOUT }; +- + typedef struct _cairo_font_options cairo_font_options_t; + + class VCL_PLUGIN_PUBLIC SalInstance +@@ -127,15 +125,16 @@ public: + virtual void AcquireYieldMutex( sal_uLong nCount ) = 0; + // return true, if yield mutex is owned by this thread, else false + virtual bool CheckYieldMutex() = 0; ++ virtual bool IsMainThread() const = 0; + + /** + * Wait for the next event (if bWait) and dispatch it, + * includes posted events, and timers. + * If bHandleAllCurrentEvents - dispatch multiple posted +- * user events. Returns true if events needed processing. ++ * user events. Returns true if events were processed. + */ +- virtual SalYieldResult DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong nReleased) = 0; +- virtual bool AnyInput( VclInputFlags nType ) = 0; ++ virtual bool DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong nReleased) = 0; ++ virtual bool AnyInput( VclInputFlags nType ) = 0; + + // menus + virtual SalMenu* CreateMenu( bool bMenuBar, Menu* pMenu ); +diff --git a/vcl/inc/saltimer.hxx b/vcl/inc/saltimer.hxx +index 4d8541801ef2..9b67e22fc9d4 100644 +--- a/vcl/inc/saltimer.hxx ++++ b/vcl/inc/saltimer.hxx +@@ -21,10 +21,9 @@ + #define INCLUDED_VCL_INC_SALTIMER_HXX + + #include +- + #include +- + #include ++#include + + /* + * note: there will be only a single instance of SalTimer +@@ -49,29 +48,13 @@ public: + m_pProc = pProc; + } + +- void CallCallback( bool idle ) ++ void CallCallback() + { + if( m_pProc ) +- m_pProc( idle ); ++ m_pProc(); + } + }; + +-class Task; +- +-// Internal scheduler record holding intrusive linked list pieces +-struct ImplSchedulerData +-{ +- ImplSchedulerData *mpNext; // Pointer to the next element in list +- Task *mpTask; // Pointer to VCL Task instance +- bool mbDelete; // Destroy this task? +- bool mbInScheduler; // Task currently processed? +- sal_uInt64 mnUpdateTime; // Last Update Time +- +- void Invoke(); +- +- const char *GetDebugName() const; +-}; +- + #endif // INCLUDED_VCL_INC_SALTIMER_HXX + + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/vcl/inc/salwtype.hxx b/vcl/inc/salwtype.hxx +index 12ed894b67f3..5007c25141d8 100644 +--- a/vcl/inc/salwtype.hxx ++++ b/vcl/inc/salwtype.hxx +@@ -260,7 +260,7 @@ struct SalLongPressEvent + long mnY; + }; + +-typedef void (*SALTIMERPROC)( bool idle ); ++typedef void (*SALTIMERPROC)(); + + #endif // INCLUDED_VCL_INC_SALWTYPE_HXX + +diff --git a/vcl/inc/schedulerimpl.hxx b/vcl/inc/schedulerimpl.hxx +new file mode 100644 +index 000000000000..004122932965 +--- /dev/null ++++ b/vcl/inc/schedulerimpl.hxx +@@ -0,0 +1,69 @@ ++/* -*- 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 . ++ */ ++ ++#ifndef INCLUDED_VCL_INC_SCHEDULERIMPL_HXX ++#define INCLUDED_VCL_INC_SCHEDULERIMPL_HXX ++ ++#include ++#include ++#include ++ ++class Task; ++ ++// Internal scheduler record holding intrusive linked list pieces ++struct ImplSchedulerData final ++{ ++ ImplSchedulerData* mpNext; ///< Pointer to the next element in list ++ Task* mpTask; ///< Pointer to VCL Task instance ++ bool mbInScheduler; ///< Task currently processed? ++ sal_uInt64 mnUpdateTime; ///< Last Update Time ++ ++ const char *GetDebugName() const; ++}; ++ ++class SchedulerMutex final ++{ ++ sal_uInt32 mnLockDepth; ++ osl::Mutex maMutex; ++ ++public: ++ SchedulerMutex() : mnLockDepth( 0 ) {} ++ ++ void acquire( sal_uInt32 nLockCount = 1 ); ++ sal_uInt32 release( bool bUnlockAll = false ); ++ sal_uInt32 lockDepth() const { return mnLockDepth; } ++}; ++ ++class SchedulerGuard final ++{ ++public: ++ SchedulerGuard() ++ { ++ Scheduler::Lock(); ++ } ++ ++ ~SchedulerGuard() ++ { ++ Scheduler::Unlock(); ++ } ++}; ++ ++#endif // INCLUDED_VCL_INC_SCHEDULERIMPL_HXX ++ ++/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/vcl/inc/svdata.hxx b/vcl/inc/svdata.hxx +index bc87e6887f1d..3096be5f5233 100644 +--- a/vcl/inc/svdata.hxx ++++ b/vcl/inc/svdata.hxx +@@ -38,6 +38,7 @@ + #include + #include + #include "ControlCacheKey.hxx" ++#include "schedulerimpl.hxx" + + struct ImplPostEventData; + struct ImplTimerData; +@@ -46,7 +47,6 @@ struct ImplConfigData; + class ImplDirectFontSubstitution; + struct ImplHotKey; + struct ImplEventHook; +-struct ImplSchedulerData; + class Point; + class ResMgr; + class ImplAccelManager; +@@ -66,10 +66,6 @@ class Image; + class PopupMenu; + class Application; + class OutputDevice; +-namespace vcl +-{ +- class Window; +-} + class SystemWindow; + class WorkWindow; + class Dialog; +@@ -101,7 +97,13 @@ class OpenGLContext; + #define SV_ICON_ID_DATABASE 12 + #define SV_ICON_ID_FORMULA 13 + +-namespace vcl { class DisplayConnectionDispatch; class SettingsConfigItem; class DeleteOnDeinitBase; } ++namespace vcl ++{ ++ class DisplayConnectionDispatch; ++ class SettingsConfigItem; ++ class DeleteOnDeinitBase; ++ class Window; ++} + + class LocaleConfigurationListener : public utl::ConfigurationListener + { +@@ -322,6 +324,18 @@ struct BlendFrameCache + } + }; + ++struct ImplSchedulerContext ++{ ++ ImplSchedulerData* mpFirstSchedulerData = nullptr; ///< list of all active tasks ++ ImplSchedulerData* mpLastSchedulerData = nullptr; ///< last item of the mpFirstSchedulerData list ++ ImplSchedulerData* mpSchedulerStack = nullptr; ///< stack of invoked tasks ++ SalTimer* mpSalTimer = nullptr; ///< interface to sal event loop / system timer ++ sal_uInt64 mnTimerStart = 0; ///< start time of the timer ++ sal_uInt64 mnTimerPeriod = SAL_MAX_UINT64; ///< current timer period ++ SchedulerMutex maMutex; ///< lock counting mutex for scheduler locking ++ bool mbActive = true; ///< is the scheduler active? ++}; ++ + struct ImplSVData + { + ~ImplSVData(); +@@ -330,12 +344,10 @@ struct ImplSVData + Application* mpApp = nullptr; // pApp + VclPtr mpDefaultWin; // Default-Window + bool mbDeInit = false; // Is VCL deinitializing +- ImplSchedulerData* mpFirstSchedulerData = nullptr; // list of all running tasks +- SalTimer* mpSalTimer = nullptr; // interface to sal event loop/timers + SalI18NImeStatus* mpImeStatus = nullptr; // interface to ime status window + SalSystem* mpSalSystem = nullptr; // SalSystem interface + ResMgr* mpResMgr = nullptr; // SV-Resource-Manager +- sal_uInt64 mnTimerPeriod = 0; // current timer period ++ ImplSchedulerContext maSchedCtx; // indepen data for class Scheduler + ImplSVAppData maAppData; // indepen data for class Application + ImplSVGDIData maGDIData; // indepen data for Output classes + ImplSVWinData maWinData; // indepen data for Windows classes +diff --git a/vcl/inc/unx/gtk/gtkdata.hxx b/vcl/inc/unx/gtk/gtkdata.hxx +index 8e8719a8a174..2582314eb003 100644 +--- a/vcl/inc/unx/gtk/gtkdata.hxx ++++ b/vcl/inc/unx/gtk/gtkdata.hxx +@@ -98,7 +98,6 @@ class GtkData : public SalGenericData + osl::Mutex m_aDispatchMutex; + osl::Condition m_aDispatchCondition; + css::uno::Any m_aException; +- bool blockIdleTimeout; + + public: + GtkData( SalInstance *pInstance ); +@@ -113,14 +112,13 @@ public: + static gboolean userEventFn( gpointer data ); + + void PostUserEvent(); +- SalYieldResult Yield( bool bWait, bool bHandleAllCurrentEvents ); ++ bool Yield( bool bWait, bool bHandleAllCurrentEvents ); + inline GdkDisplay *GetGdkDisplay(); + + virtual void ErrorTrapPush() override; + virtual bool ErrorTrapPop( bool bIgnoreError = true ) override; + + inline GtkSalDisplay *GetGtkDisplay() const; +- bool BlockIdleTimeout() const { return blockIdleTimeout; } + void setException(const css::uno::Any& rException) { m_aException = rException; } + }; + +diff --git a/vcl/inc/unx/gtk/gtkinst.hxx b/vcl/inc/unx/gtk/gtkinst.hxx +index 250fd9b1ca1b..1585c778afac 100644 +--- a/vcl/inc/unx/gtk/gtkinst.hxx ++++ b/vcl/inc/unx/gtk/gtkinst.hxx +@@ -208,8 +208,10 @@ public: + const SystemGraphicsData* = nullptr ) override; + virtual SalBitmap* CreateSalBitmap() override; + +- virtual SalYieldResult DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong nReleased) override; ++ virtual bool DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong nReleased) override; + virtual bool AnyInput( VclInputFlags nType ) override; ++ // impossible to handle correctly, as "main thread" depends on the dispatch mutex ++ virtual bool IsMainThread() const override { return false; } + + virtual GenPspGraphics *CreatePrintGraphics() override; + +@@ -231,12 +233,12 @@ public: + const cairo_font_options_t* GetLastSeenCairoFontOptions(); + void ResetLastSeenCairoFontOptions(); + +- void RemoveTimer (SalTimer *pTimer); ++ void RemoveTimer (); + + std::shared_ptr const & getPrintWrapper() const; + + private: +- std::vector m_aTimers; ++ GtkSalTimer *m_pTimer; + #if GTK_CHECK_VERSION(3,0,0) + std::unordered_map< GdkAtom, css::uno::Reference > m_aClipboards; + #endif +diff --git a/vcl/inc/unx/saldata.hxx b/vcl/inc/unx/saldata.hxx +index 9e8928122a97..e7899c4138f4 100644 +--- a/vcl/inc/unx/saldata.hxx ++++ b/vcl/inc/unx/saldata.hxx +@@ -72,7 +72,7 @@ public: + + SalXLib* GetLib() const { return pXLib_; } + +- static void Timeout( bool idle ); ++ static void Timeout(); + + // X errors + virtual void ErrorTrapPush() override; +diff --git a/vcl/inc/unx/saldisp.hxx b/vcl/inc/unx/saldisp.hxx +index d28326e389bb..c42be21d3dba 100644 +--- a/vcl/inc/unx/saldisp.hxx ++++ b/vcl/inc/unx/saldisp.hxx +@@ -153,7 +153,6 @@ protected: + timeval m_aTimeout; + sal_uLong m_nTimeoutMS; + int m_pTimeoutFDS[2]; +- bool blockIdleTimeout; + + int nFDs_; + fd_set aReadFDS_; +@@ -167,7 +166,7 @@ public: + virtual ~SalXLib(); + virtual void Init(); + +- virtual SalYieldResult Yield( bool bWait, bool bHandleAllCurrentEvents ); ++ virtual bool Yield( bool bWait, bool bHandleAllCurrentEvents ); + virtual void Wakeup(); + virtual void PostUserEvent(); + +diff --git a/vcl/inc/unx/salinst.h b/vcl/inc/unx/salinst.h +index f3ca193df205..848356dff327 100644 +--- a/vcl/inc/unx/salinst.h ++++ b/vcl/inc/unx/salinst.h +@@ -74,8 +74,9 @@ public: + virtual SalSession* CreateSalSession() override; + virtual OpenGLContext* CreateOpenGLContext() override; + +- virtual SalYieldResult DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong nReleased) override; ++ virtual bool DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong nReleased) override; + virtual bool AnyInput( VclInputFlags nType ) override; ++ virtual bool IsMainThread() const override { return true; } + + virtual OUString GetConnectionIdentifier() override; + void SetLib( SalXLib *pXLib ) { mpXLib = pXLib; } +diff --git a/vcl/inc/unx/salunxtime.h b/vcl/inc/unx/salunxtime.h +index e9b4b81af7f6..1ed979e83e5b 100644 +--- a/vcl/inc/unx/salunxtime.h ++++ b/vcl/inc/unx/salunxtime.h +@@ -59,7 +59,7 @@ inline timeval &operator -= ( timeval &t1, const timeval &t2 ) + inline timeval &operator += ( timeval &t1, sal_uIntPtr t2 ) + { + t1.tv_sec += t2 / 1000; +- t1.tv_usec += t2 ? (t2 % 1000) * 1000 : 500; ++ t1.tv_usec += (t2 % 1000) * 1000; + if( t1.tv_usec > 1000000 ) + { + t1.tv_sec++; +diff --git a/vcl/qa/cppunit/lifecycle.cxx b/vcl/qa/cppunit/lifecycle.cxx +index b3a73af757a3..26f8796a57ea 100644 +--- a/vcl/qa/cppunit/lifecycle.cxx ++++ b/vcl/qa/cppunit/lifecycle.cxx +@@ -20,6 +20,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -211,7 +212,7 @@ void LifecycleTest::testFocus() + xWin->Show(); + xChild->GrabFocus(); + // process asynchronous ToTop +- Scheduler::ProcessTaskScheduling( true ); ++ Scheduler::ProcessTaskScheduling(); + // FIXME: really awful to test focus issues without showing windows. + // CPPUNIT_ASSERT(xChild->HasFocus()); + } +diff --git a/vcl/qa/cppunit/timer.cxx b/vcl/qa/cppunit/timer.cxx +index 47439ad19e8b..bed6eb615943 100644 +--- a/vcl/qa/cppunit/timer.cxx ++++ b/vcl/qa/cppunit/timer.cxx +@@ -19,6 +19,7 @@ + #include + #include + #include ++#include + #include "svdata.hxx" + #include "salinst.hxx" + +@@ -54,8 +55,10 @@ class TimerTest : public test::BootstrapFixture + public: + TimerTest() : BootstrapFixture(true, false) {} + +- void testIdleMainloop(); + void testIdle(); ++#ifndef WIN32 ++ void testIdleMainloop(); ++#endif + #ifdef TEST_WATCHDOG + void testWatchdog(); + #endif +@@ -64,13 +67,19 @@ public: + void testAutoTimer(); + void testMultiAutoTimers(); + #endif +- void testRecursiveTimer(); ++ void testAutoTimerStop(); ++ void testNestedTimer(); + void testSlowTimerCallback(); + void testTriggerIdleFromIdle(); ++ void testInvokedReStart(); ++ void testPriority(); ++ void testRoundRobin(); + + CPPUNIT_TEST_SUITE(TimerTest); + CPPUNIT_TEST(testIdle); ++#ifndef WIN32 + CPPUNIT_TEST(testIdleMainloop); ++#endif + #ifdef TEST_WATCHDOG + CPPUNIT_TEST(testWatchdog); + #endif +@@ -79,9 +88,13 @@ public: + CPPUNIT_TEST(testAutoTimer); + CPPUNIT_TEST(testMultiAutoTimers); + #endif +- CPPUNIT_TEST(testRecursiveTimer); ++ CPPUNIT_TEST(testAutoTimerStop); ++ CPPUNIT_TEST(testNestedTimer); + CPPUNIT_TEST(testSlowTimerCallback); + CPPUNIT_TEST(testTriggerIdleFromIdle); ++ CPPUNIT_TEST(testInvokedReStart); ++ CPPUNIT_TEST(testPriority); ++ CPPUNIT_TEST(testRoundRobin); + + CPPUNIT_TEST_SUITE_END(); + }; +@@ -100,7 +113,7 @@ class IdleBool : public Idle + bool &mrBool; + public: + explicit IdleBool( bool &rBool ) : +- Idle(), mrBool( rBool ) ++ Idle( "IdleBool" ), mrBool( rBool ) + { + SetPriority( TaskPriority::LOWEST ); + Start(); +@@ -117,14 +130,14 @@ void TimerTest::testIdle() + { + bool bTriggered = false; + IdleBool aTest( bTriggered ); +- Scheduler::ProcessTaskScheduling( true ); ++ Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_MESSAGE("idle triggered", bTriggered); + } + ++#ifndef WIN32 + // tdf#91727 + void TimerTest::testIdleMainloop() + { +-#ifndef _WIN32 + bool bTriggered = false; + IdleBool aTest( bTriggered ); + // coverity[loop_top] - Application::Yield allows the timer to fire and toggle bDone +@@ -139,16 +152,15 @@ void TimerTest::testIdleMainloop() + pSVData->maAppData.mnDispatchLevel--; + } + CPPUNIT_ASSERT_MESSAGE("mainloop idle triggered", bTriggered); +-#endif + } +- ++#endif + + class TimerBool : public Timer + { + bool &mrBool; + public: + TimerBool( sal_uLong nMS, bool &rBool ) : +- Timer(), mrBool( rBool ) ++ Timer( "TimerBool" ), mrBool( rBool ) + { + SetTimeout( nMS ); + Start(); +@@ -180,17 +192,26 @@ void TimerTest::testDurations() + class AutoTimerCount : public AutoTimer + { + sal_Int32 &mrCount; ++ const sal_Int32 mnMaxCount; ++ + public: +- AutoTimerCount( sal_uLong nMS, sal_Int32 &rCount ) : +- AutoTimer(), mrCount( rCount ) ++ AutoTimerCount( sal_uLong nMS, sal_Int32 &rCount, ++ const sal_Int32 nMaxCount = -1 ) ++ : AutoTimer( "AutoTimerCount" ) ++ , mrCount( rCount ) ++ , mnMaxCount( nMaxCount ) + { + SetTimeout( nMS ); + Start(); + mrCount = 0; + } ++ + virtual void Invoke() override + { +- mrCount++; ++ ++mrCount; ++ CPPUNIT_ASSERT( mnMaxCount < 0 || mrCount <= mnMaxCount ); ++ if ( mrCount == mnMaxCount ) ++ Stop(); + } + }; + +@@ -296,11 +317,22 @@ void TimerTest::testMultiAutoTimers() + } + #endif // TEST_TIMERPRECISION + ++void TimerTest::testAutoTimerStop() ++{ ++ sal_Int32 nTimerCount = 0; ++ const sal_Int32 nMaxCount = 5; ++ AutoTimerCount aAutoTimer( 0, nTimerCount, nMaxCount ); ++ while ( nMaxCount != nTimerCount ) ++ Application::Yield(); ++ CPPUNIT_ASSERT( !aAutoTimer.IsActive() ); ++ CPPUNIT_ASSERT( !Application::Reschedule() ); ++} ++ + + class YieldTimer : public Timer + { + public: +- explicit YieldTimer( sal_uLong nMS ) : Timer() ++ explicit YieldTimer( sal_uLong nMS ) : Timer( "YieldTimer" ) + { + SetTimeout( nMS ); + Start(); +@@ -312,7 +344,7 @@ public: + } + }; + +-void TimerTest::testRecursiveTimer() ++void TimerTest::testNestedTimer() + { + sal_Int32 nCount = 0; + YieldTimer aCount(5); +@@ -328,7 +360,7 @@ class SlowCallbackTimer : public Timer + bool &mbSlow; + public: + SlowCallbackTimer( sal_uLong nMS, bool &bBeenSlow ) : +- Timer(), mbSlow( bBeenSlow ) ++ Timer( "SlowCallbackTimer" ), mbSlow( bBeenSlow ) + { + SetTimeout( nMS ); + Start(); +@@ -362,7 +394,7 @@ class TriggerIdleFromIdle : public Idle + TriggerIdleFromIdle* mpOther; + public: + explicit TriggerIdleFromIdle( bool* pTriggered, TriggerIdleFromIdle* pOther ) : +- Idle(), mpTriggered(pTriggered), mpOther(pOther) ++ Idle( "TriggerIdleFromIdle" ), mpTriggered(pTriggered), mpOther(pOther) + { + } + virtual void Invoke() override +@@ -384,8 +416,121 @@ void TimerTest::testTriggerIdleFromIdle() + TriggerIdleFromIdle aTest1( &bTriggered1, &aTest2 ); + aTest1.Start(); + Application::Yield(); +- CPPUNIT_ASSERT_MESSAGE("idle triggered", bTriggered1); +- CPPUNIT_ASSERT_MESSAGE("idle triggered", bTriggered2); ++ CPPUNIT_ASSERT_MESSAGE("idle not triggered", bTriggered1); ++ CPPUNIT_ASSERT_MESSAGE("idle not triggered", bTriggered2); ++} ++ ++ ++class IdleInvokedReStart : public Idle ++{ ++ sal_Int32 &mrCount; ++public: ++ IdleInvokedReStart( sal_Int32 &rCount ) ++ : Idle( "IdleInvokedReStart" ), mrCount( rCount ) ++ { ++ Start(); ++ } ++ virtual void Invoke() override ++ { ++ mrCount++; ++ if ( mrCount < 2 ) ++ Start(); ++ } ++}; ++ ++void TimerTest::testInvokedReStart() ++{ ++ sal_Int32 nCount = 0; ++ IdleInvokedReStart aIdle( nCount ); ++ Scheduler::ProcessEventsToIdle(); ++ CPPUNIT_ASSERT_EQUAL( nCount, sal_Int32(2) ); ++} ++ ++ ++class IdleSerializer : public Idle ++{ ++ sal_uInt32 mnPosition; ++ sal_uInt32 &mrProcesed; ++public: ++ IdleSerializer( const sal_Char *pDebugName, ++ sal_uInt32 nPosition, sal_uInt32 &rProcesed ) ++ : Idle( pDebugName ) ++ , mnPosition( nPosition ) ++ , mrProcesed( rProcesed ) ++ { ++ Start(); ++ } ++ virtual void Invoke() override ++ { ++ ++mrProcesed; ++ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Ignored prio", mnPosition, mrProcesed ); ++ } ++}; ++ ++void TimerTest::testPriority() ++{ ++ // scope, so tasks are deleted ++ { ++ // Start: 1st Idle low, 2nd high ++ sal_uInt32 nProcessed = 0; ++ IdleSerializer aLowPrioIdle( "IdleSerializer LowPrio", 2, nProcessed ); ++ aLowPrioIdle.SetPriority( TaskPriority::LOWEST ); ++ IdleSerializer aHighPrioIdle( "IdleSerializer HighPrio", 1, nProcessed ); ++ aHighPrioIdle.SetPriority( TaskPriority::HIGHEST ); ++ Scheduler::ProcessEventsToIdle(); ++ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Not all idles processed", sal_uInt32(2), nProcessed ); ++ } ++ ++ { ++ // Start: 1st Idle high, 2nd low ++ sal_uInt32 nProcessed = 0; ++ IdleSerializer aHighPrioIdle( "IdleSerializer HighPrio", 1, nProcessed ); ++ aHighPrioIdle.SetPriority( TaskPriority::HIGHEST ); ++ IdleSerializer aLowPrioIdle( "IdleSerializer LowPrio", 2, nProcessed ); ++ aLowPrioIdle.SetPriority( TaskPriority::LOWEST ); ++ Scheduler::ProcessEventsToIdle(); ++ CPPUNIT_ASSERT_EQUAL_MESSAGE( "Not all idles processed", sal_uInt32(2), nProcessed ); ++ } ++} ++ ++ ++class TestAutoIdleRR : public AutoIdle ++{ ++ sal_uInt32 &mrCount; ++ ++ DECL_LINK( IdleRRHdl, Timer *, void ); ++ ++public: ++ TestAutoIdleRR( sal_uInt32 &rCount, ++ const sal_Char *pDebugName ) ++ : AutoIdle( pDebugName ) ++ , mrCount( rCount ) ++ { ++ CPPUNIT_ASSERT_EQUAL( mrCount, sal_uInt32(0) ); ++ SetInvokeHandler( LINK( this, TestAutoIdleRR, IdleRRHdl ) ); ++ Start(); ++ } ++}; ++ ++IMPL_LINK_NOARG(TestAutoIdleRR, IdleRRHdl, Timer *, void) ++{ ++ ++mrCount; ++ if ( mrCount == 3 ) ++ Stop(); ++} ++ ++void TimerTest::testRoundRobin() ++{ ++ sal_uInt32 nCount1 = 0, nCount2 = 0; ++ TestAutoIdleRR aIdle1( nCount1, "TestAutoIdleRR aIdle1" ), ++ aIdle2( nCount2, "TestAutoIdleRR aIdle2" ); ++ while ( Application::Reschedule() ) ++ { ++ CPPUNIT_ASSERT( nCount1 == nCount2 || nCount1 - 1 == nCount2 ); ++ CPPUNIT_ASSERT( nCount1 <= 3 ); ++ CPPUNIT_ASSERT( nCount2 <= 3 ); ++ } ++ CPPUNIT_ASSERT( 3 == nCount1 && 3 == nCount2 ); + } + + CPPUNIT_TEST_SUITE_REGISTRATION(TimerTest); +diff --git a/vcl/source/app/idle.cxx b/vcl/source/app/idle.cxx +index a086b5f5c8c9..0eca28c52306 100644 +--- a/vcl/source/app/idle.cxx ++++ b/vcl/source/app/idle.cxx +@@ -18,6 +18,8 @@ + */ + + #include ++#include ++#include + #include "saltimer.hxx" + + Idle::Idle( bool bAuto, const sal_Char *pDebugName ) +@@ -40,8 +42,7 @@ void Idle::Start() + { + switch ( GetPriority() ) + { +- case TaskPriority::LOW: +- case TaskPriority::LOWER: ++ case TaskPriority::DEFAULT_IDLE: + case TaskPriority::LOWEST: + nPeriod = Scheduler::InfiniteTimeoutMs; + break; +@@ -53,21 +54,14 @@ void Idle::Start() + Task::StartTimer(nPeriod); + } + +-bool Idle::ReadyForSchedule( bool bIdle, sal_uInt64 /* nTimeNow */ ) const +-{ +- // always ready if not only looking for timers. +- return bIdle; +-} +- +-bool Idle::IsIdle() const ++sal_uInt64 Idle::UpdateMinPeriod( sal_uInt64 /* nMinPeriod */, sal_uInt64 /* nTimeNow */ ) const + { +- return true; ++ return Scheduler::ImmediateTimeoutMs; + } + +-sal_uInt64 Idle::UpdateMinPeriod( sal_uInt64 /* nMinPeriod */, sal_uInt64 /* nTimeNow */ ) const ++AutoIdle::AutoIdle( const sal_Char *pDebugName ) ++ : Idle( true, pDebugName ) + { +- assert(false); // idles currently don't hit this. +- return Scheduler::ImmediateTimeoutMs; + } + + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/vcl/source/app/scheduler.cxx b/vcl/source/app/scheduler.cxx +index a3de686aa2a7..d1ed16c1890f 100644 +--- a/vcl/source/app/scheduler.cxx ++++ b/vcl/source/app/scheduler.cxx +@@ -17,66 +17,147 @@ + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + ++#include ++ ++#include ++ + #include + #include + #include ++#include + #include + #include ++#include ++#include + + namespace { +-const sal_uInt64 MaximumTimeoutMs = 1000 * 60; // 1 minute +-} + +-void ImplSchedulerData::Invoke() ++template< typename charT, typename traits > ++inline std::basic_ostream & operator <<( ++ std::basic_ostream & stream, const Task& task ) + { +- DBG_TESTSOLARMUTEX(); +- +- assert(!mbInScheduler); +- if (mbDelete || mbInScheduler ) +- return; ++ stream << "a: " << task.IsActive() << " p: " << (int) task.GetPriority(); ++ const sal_Char *name = task.GetDebugName(); ++ if( nullptr == name ) ++ return stream << " (nullptr)"; ++ else ++ return stream << " " << name; ++} + +- // prepare Scheduler Object for deletion after handling +- mpTask->SetDeletionFlags(); ++/** ++ * 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 > ++inline 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: " << (int) timer.GetPriority(); ++ const sal_Char *name = timer.GetDebugName(); ++ if ( nullptr == name ) ++ stream << " (nullptr)"; ++ else ++ stream << " " << name; ++ if ( !bIsIdle ) ++ stream << " " << timer.GetTimeout() << "ms"; ++ stream << " (" << &timer << ")"; ++ return stream; ++} + +- // tdf#92036 Reset the period to avoid re-firing immediately. +- mpTask->mpSchedulerData->mnUpdateTime = tools::Time::GetSystemTicks(); ++template< typename charT, typename traits > ++inline std::basic_ostream & operator <<( ++ std::basic_ostream & stream, const Idle& idle ) ++{ ++ return stream << static_cast( &idle ); ++} + +- // invoke it +- mbInScheduler = true; +- mpTask->Invoke(); +- mbInScheduler = false; ++template< typename charT, typename traits > ++inline std::basic_ostream & operator <<( ++ std::basic_ostream & stream, const ImplSchedulerData& data ) ++{ ++ stream << " i: " << data.mbInScheduler; ++ return stream; + } + ++} // end anonymous namespace ++ + void Scheduler::ImplDeInitScheduler() + { +- ImplSVData* pSVData = ImplGetSVData(); +- ImplSchedulerData* pSchedulerData = pSVData->mpFirstSchedulerData; +- if (pSVData->mpSalTimer) +- { +- pSVData->mpSalTimer->Stop(); +- } ++ ImplSVData* pSVData = ImplGetSVData(); ++ assert( pSVData != nullptr ); ++ ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx; ++ ++ DBG_TESTSOLARMUTEX(); + +- if ( pSchedulerData ) ++ SchedulerGuard aSchedulerGuard; ++ rSchedCtx.mbActive = false; ++ ++ assert( nullptr == rSchedCtx.mpSchedulerStack ); ++ assert( 1 == rSchedCtx.maMutex.lockDepth() ); ++ ++ if (rSchedCtx.mpSalTimer) rSchedCtx.mpSalTimer->Stop(); ++ DELETEZ( rSchedCtx.mpSalTimer ); ++ ++ ImplSchedulerData* pSchedulerData = rSchedCtx.mpFirstSchedulerData; ++ while ( pSchedulerData ) + { +- do ++ Task *pTask = pSchedulerData->mpTask; ++ if ( pTask ) + { +- ImplSchedulerData* pTempSchedulerData = pSchedulerData; +- if ( pSchedulerData->mpTask ) +- { +- pSchedulerData->mpTask->mbActive = false; +- pSchedulerData->mpTask->mpSchedulerData = nullptr; +- } +- pSchedulerData = pSchedulerData->mpNext; +- delete pTempSchedulerData; ++ pTask->mbActive = false; ++ pTask->mpSchedulerData = nullptr; ++ pTask->SetStatic(); + } +- while ( pSchedulerData ); ++ ImplSchedulerData* pDeleteSchedulerData = pSchedulerData; ++ pSchedulerData = pSchedulerData->mpNext; ++ delete pDeleteSchedulerData; ++ } + +- pSVData->mpFirstSchedulerData = nullptr; +- pSVData->mnTimerPeriod = 0; ++ rSchedCtx.mpFirstSchedulerData = nullptr; ++ rSchedCtx.mpLastSchedulerData = nullptr; ++ rSchedCtx.mnTimerPeriod = InfiniteTimeoutMs; ++} ++ ++void SchedulerMutex::acquire( sal_uInt32 nLockCount ) ++{ ++ assert(nLockCount > 0); ++ for (sal_uInt32 i = 0; i != nLockCount; ++i) { ++ if (!maMutex.acquire()) ++ abort(); + } ++ mnLockDepth += nLockCount; ++} + +- delete pSVData->mpSalTimer; +- pSVData->mpSalTimer = nullptr; ++sal_uInt32 SchedulerMutex::release( bool bUnlockAll ) ++{ ++ assert(mnLockDepth > 0); ++ const sal_uInt32 nLockCount = ++ (bUnlockAll || 0 == mnLockDepth) ? mnLockDepth : 1; ++ mnLockDepth -= nLockCount; ++ for (sal_uInt32 i = 0; i != nLockCount; ++i) { ++ if (!maMutex.release()) ++ abort(); ++ } ++ return nLockCount; ++} ++ ++void Scheduler::Lock( sal_uInt32 nLockCount ) ++{ ++ ImplSVData* pSVData = ImplGetSVData(); ++ assert( pSVData != nullptr ); ++ pSVData->maSchedCtx.maMutex.acquire( nLockCount ); ++} ++ ++sal_uInt32 Scheduler::Unlock( bool bUnlockAll ) ++{ ++ ImplSVData* pSVData = ImplGetSVData(); ++ assert( pSVData != nullptr ); ++ return pSVData->maSchedCtx.maMutex.release( bUnlockAll ); + } + + /** +@@ -86,202 +167,282 @@ void Scheduler::ImplDeInitScheduler() + * 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) ++void Scheduler::ImplStartTimer(sal_uInt64 nMS, bool bForce, sal_uInt64 nTime) + { + ImplSVData* pSVData = ImplGetSVData(); +- if (pSVData->mbDeInit) +- { +- // do not start new timers during shutdown - if that happens after +- // ImplSalStopTimer() on WNT the timer queue is restarted and never ends ++ ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx; ++ if ( !rSchedCtx.mbActive ) + return; +- } + +- DBG_TESTSOLARMUTEX(); +- +- if (!pSVData->mpSalTimer) ++ if (!rSchedCtx.mpSalTimer) + { +- pSVData->mnTimerPeriod = MaximumTimeoutMs; +- pSVData->mpSalTimer = pSVData->mpDefInst->CreateSalTimer(); +- pSVData->mpSalTimer->SetCallback(Scheduler::CallbackTaskScheduling); ++ rSchedCtx.mnTimerStart = 0; ++ rSchedCtx.mnTimerPeriod = InfiniteTimeoutMs; ++ rSchedCtx.mpSalTimer = pSVData->mpDefInst->CreateSalTimer(); ++ rSchedCtx.mpSalTimer->SetCallback(Scheduler::CallbackTaskScheduling); + } + +- if ( !nMS ) +- nMS = 1; ++ 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. +- if (bForce || nMS < pSVData->mnTimerPeriod) ++ // Force instant wakeup on 0ms, if the previous period was not 0ms ++ if (bForce || nProposedTimeout < nCurTimeout || (!nMS && rSchedCtx.mnTimerPeriod)) + { +- pSVData->mnTimerPeriod = nMS; +- pSVData->mpSalTimer->Start(nMS); ++ SAL_INFO( "vcl.schedule", " Starting scheduler system timer (" << nMS << "ms)" ); ++ rSchedCtx.mnTimerStart = nTime; ++ rSchedCtx.mnTimerPeriod = nMS; ++ rSchedCtx.mpSalTimer->Start( nMS ); + } + } + +-void Scheduler::CallbackTaskScheduling( bool bIdle ) ++void Scheduler::CallbackTaskScheduling() + { + // this function is for the saltimer callback +- Scheduler::ProcessTaskScheduling( bIdle ); ++ Scheduler::ProcessTaskScheduling(); + } + +-bool Scheduler::ProcessTaskScheduling( bool bIdle ) ++static bool g_bDeterministicMode = false; ++ ++void Scheduler::SetDeterministicMode(bool bDeterministic) + { +- ImplSVData *pSVData = ImplGetSVData(); +- if ( pSVData->mbDeInit ) +- return false; +- ImplSchedulerData *pMostUrgent = nullptr; +- sal_uInt64 nTime = tools::Time::GetSystemTicks(); ++ g_bDeterministicMode = bDeterministic; ++} + +- DBG_TESTSOLARMUTEX(); ++bool Scheduler::GetDeterministicMode() ++{ ++ return g_bDeterministicMode; ++} + +- for ( ImplSchedulerData *pSchedulerData = pSVData->mpFirstSchedulerData; +- pSchedulerData; pSchedulerData = pSchedulerData->mpNext ) ++inline void Scheduler::UpdateSystemTimer( ImplSchedulerContext &rSchedCtx, ++ const sal_uInt64 nMinPeriod, ++ const bool bForce, const sal_uInt64 nTime ) ++{ ++ if ( InfiniteTimeoutMs == nMinPeriod ) + { +- if ( !pSchedulerData->mpTask || pSchedulerData->mbDelete || pSchedulerData->mbInScheduler || +- !pSchedulerData->mpTask->ReadyForSchedule( bIdle, nTime ) || +- !pSchedulerData->mpTask->IsActive()) +- continue; +- if (!pMostUrgent) +- pMostUrgent = pSchedulerData; +- else +- { +- // Find the highest priority. +- // If the priority of the current task is higher (numerical value is lower) than +- // the priority of the most urgent, the current task gets the new most urgent. +- if ( pSchedulerData->mpTask->GetPriority() < pMostUrgent->mpTask->GetPriority() ) +- pMostUrgent = pSchedulerData; +- } ++ SAL_INFO("vcl.schedule", " Stopping system timer"); ++ if ( rSchedCtx.mpSalTimer ) ++ rSchedCtx.mpSalTimer->Stop(); ++ rSchedCtx.mnTimerPeriod = nMinPeriod; + } ++ else ++ Scheduler::ImplStartTimer( nMinPeriod, bForce, nTime ); ++} + +- if ( pMostUrgent ) ++static inline void AppendSchedulerData( ImplSchedulerContext &rSchedCtx, ++ ImplSchedulerData * const pSchedulerData ) ++{ ++ if ( !rSchedCtx.mpLastSchedulerData ) + { +- SAL_INFO("vcl.schedule", "Invoke task " << pMostUrgent->GetDebugName()); +- +- pMostUrgent->mnUpdateTime = nTime; +- pMostUrgent->Invoke(); +- return true; ++ rSchedCtx.mpFirstSchedulerData = pSchedulerData; ++ rSchedCtx.mpLastSchedulerData = pSchedulerData; + } + else +- return false; ++ { ++ rSchedCtx.mpLastSchedulerData->mpNext = pSchedulerData; ++ rSchedCtx.mpLastSchedulerData = pSchedulerData; ++ } ++ pSchedulerData->mpNext = nullptr; + } + +-static bool g_bDeterministicMode = false; +- +-void Scheduler::SetDeterministicMode(bool bDeterministic) ++static inline ImplSchedulerData* DropSchedulerData( ++ ImplSchedulerContext &rSchedCtx, ImplSchedulerData * const pPrevSchedulerData, ++ const ImplSchedulerData * const pSchedulerData ) + { +- g_bDeterministicMode = bDeterministic; +-} ++ assert( pSchedulerData ); ++ if ( pPrevSchedulerData ) ++ assert( pPrevSchedulerData->mpNext == pSchedulerData ); ++ else ++ assert( rSchedCtx.mpFirstSchedulerData == pSchedulerData ); + +-bool Scheduler::GetDeterministicMode() +-{ +- return g_bDeterministicMode; ++ ImplSchedulerData * const pSchedulerDataNext = pSchedulerData->mpNext; ++ if ( pPrevSchedulerData ) ++ pPrevSchedulerData->mpNext = pSchedulerDataNext; ++ else ++ rSchedCtx.mpFirstSchedulerData = pSchedulerDataNext; ++ if ( !pSchedulerDataNext ) ++ rSchedCtx.mpLastSchedulerData = pPrevSchedulerData; ++ return pSchedulerDataNext; + } + +-sal_uInt64 Scheduler::CalculateMinimumTimeout( bool &bHasActiveIdles ) ++bool Scheduler::ProcessTaskScheduling() + { +- ImplSchedulerData* pSchedulerData = nullptr; +- ImplSchedulerData* pPrevSchedulerData = nullptr; +- ImplSVData* pSVData = ImplGetSVData(); +- sal_uInt64 nTime = tools::Time::GetSystemTicks(); +- sal_uInt64 nMinPeriod = MaximumTimeoutMs; ++ ImplSVData *pSVData = ImplGetSVData(); ++ ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx; + + DBG_TESTSOLARMUTEX(); + +- SAL_INFO("vcl.schedule", "Calculating minimum timeout:"); +- pSchedulerData = pSVData->mpFirstSchedulerData; ++ SchedulerGuard aSchedulerGuard; ++ if ( !rSchedCtx.mbActive || InfiniteTimeoutMs == rSchedCtx.mnTimerPeriod ) ++ return false; ++ ++ sal_uInt64 nTime = tools::Time::GetSystemTicks(); ++ if ( nTime < rSchedCtx.mnTimerStart + rSchedCtx.mnTimerPeriod ) ++ { ++ SAL_WARN( "vcl.schedule", "we're too early - restart the timer!" ); ++ UpdateSystemTimer( rSchedCtx, ++ rSchedCtx.mnTimerStart + rSchedCtx.mnTimerPeriod - nTime, ++ true, nTime ); ++ return false; ++ } ++ ++ ImplSchedulerData* pSchedulerData = nullptr; ++ ImplSchedulerData* pPrevSchedulerData = nullptr; ++ ImplSchedulerData *pMostUrgent = nullptr; ++ ImplSchedulerData *pPrevMostUrgent = nullptr; ++ sal_uInt64 nMinPeriod = InfiniteTimeoutMs; ++ sal_uInt64 nMostUrgentPeriod = InfiniteTimeoutMs; ++ sal_uInt64 nReadyPeriod = InfiniteTimeoutMs; ++ ++ pSchedulerData = rSchedCtx.mpFirstSchedulerData; + while ( pSchedulerData ) + { +- ImplSchedulerData *pNext = pSchedulerData->mpNext; ++ 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 Task be released from scheduling? +- if ( !pSchedulerData->mbInScheduler && +- pSchedulerData->mbDelete ) ++ // Should the Task be released from scheduling or stacked? ++ if ( !pSchedulerData->mpTask || !pSchedulerData->mpTask->IsActive() ++ || pSchedulerData->mbInScheduler ) + { +- if ( pPrevSchedulerData ) +- pPrevSchedulerData->mpNext = pSchedulerData->mpNext; ++ ImplSchedulerData * const pSchedulerDataNext = ++ DropSchedulerData( rSchedCtx, pPrevSchedulerData, pSchedulerData ); ++ if ( pSchedulerData->mbInScheduler ) ++ { ++ pSchedulerData->mpNext = rSchedCtx.mpSchedulerStack; ++ rSchedCtx.mpSchedulerStack = pSchedulerData; ++ } + else +- pSVData->mpFirstSchedulerData = pSchedulerData->mpNext; +- if ( pSchedulerData->mpTask ) +- pSchedulerData->mpTask->mpSchedulerData = nullptr; +- pNext = pSchedulerData->mpNext; +- delete pSchedulerData; +- } +- else +- { +- if (!pSchedulerData->mbInScheduler) + { +- if ( !pSchedulerData->mpTask->IsIdle() ) +- { +- sal_uInt64 nOldMinPeriod = nMinPeriod; +- nMinPeriod = pSchedulerData->mpTask->UpdateMinPeriod( +- nOldMinPeriod, nTime ); +- SAL_INFO("vcl.schedule", "Have active timer '" << +- pSchedulerData->GetDebugName() << +- "' update min period from " << nOldMinPeriod << +- " to " << nMinPeriod); +- assert( nMinPeriod <= nOldMinPeriod ); +- if ( nMinPeriod > nOldMinPeriod ) +- { +- nMinPeriod = nOldMinPeriod; +- SAL_WARN("vcl.schedule", +- "New update min period > old period - using old"); +- } +- } +- else +- { +- SAL_INFO("vcl.schedule", "Have active idle '" << +- pSchedulerData->GetDebugName() << "'"); +- bHasActiveIdles = true; +- } ++ if ( pSchedulerData->mpTask ) ++ pSchedulerData->mpTask->mpSchedulerData = nullptr; ++ delete pSchedulerData; + } +- pPrevSchedulerData = pSchedulerData; ++ pSchedulerData = pSchedulerDataNext; ++ continue; + } +- pSchedulerData = pNext; +- } + +- // delete clock if no more timers available, +- if ( !pSVData->mpFirstSchedulerData ) +- { +- if ( pSVData->mpSalTimer ) +- pSVData->mpSalTimer->Stop(); +- nMinPeriod = MaximumTimeoutMs; +- pSVData->mnTimerPeriod = nMinPeriod; +- SAL_INFO("vcl.schedule", "Unusual - no more timers available - stop timer"); ++ assert( pSchedulerData->mpTask ); ++ if ( !pSchedulerData->mpTask->IsActive() ) ++ goto next_entry; ++ ++ // skip ready tasks with lower priority than the most urgent (numerical lower is higher) ++ nReadyPeriod = pSchedulerData->mpTask->UpdateMinPeriod( nMinPeriod, nTime ); ++ if ( ImmediateTimeoutMs == nReadyPeriod && ++ (!pMostUrgent || (pSchedulerData->mpTask->GetPriority() < pMostUrgent->mpTask->GetPriority())) ) ++ { ++ if ( pMostUrgent && nMinPeriod > nMostUrgentPeriod ) ++ nMinPeriod = nMostUrgentPeriod; ++ pPrevMostUrgent = pPrevSchedulerData; ++ pMostUrgent = pSchedulerData; ++ nMostUrgentPeriod = nReadyPeriod; ++ } ++ else if ( nMinPeriod > nReadyPeriod ) ++ nMinPeriod = nReadyPeriod; ++ ++next_entry: ++ pPrevSchedulerData = pSchedulerData; ++ pSchedulerData = pSchedulerData->mpNext; + } +- else ++ ++ if ( InfiniteTimeoutMs != nMinPeriod ) ++ SAL_INFO("vcl.schedule", "Calculated minimum timeout as " << nMinPeriod ); ++ UpdateSystemTimer( rSchedCtx, nMinPeriod, true, nTime ); ++ ++ if ( pMostUrgent ) + { +- Scheduler::ImplStartTimer(nMinPeriod, true); +- SAL_INFO("vcl.schedule", "Calculated minimum timeout as " << nMinPeriod << " and " << +- (bHasActiveIdles ? "has active idles" : "no idles")); +- } ++ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " " ++ << pMostUrgent << " invoke-in " << *pMostUrgent->mpTask ); + +- return nMinPeriod; +-} ++ Task *pTask = pMostUrgent->mpTask; + +-const char *ImplSchedulerData::GetDebugName() const +-{ +- return mpTask && mpTask->GetDebugName() ? +- mpTask->GetDebugName() : "unknown"; ++ // prepare Scheduler object for deletion after handling ++ pTask->SetDeletionFlags(); ++ ++ // invoke the task ++ // defer pushing the scheduler stack to next run, as most tasks will ++ // not run a nested Scheduler loop and don't need a stack push! ++ pMostUrgent->mbInScheduler = true; ++ sal_uInt32 nLockCount = Unlock( true ); ++ try ++ { ++ pTask->Invoke(); ++ } ++ catch (...) ++ { ++ SAL_WARN( "vcl.schedule", ++ "Uncaught exception during Task::Invoke()!" ); ++ abort(); ++ } ++ Lock( nLockCount ); ++ pMostUrgent->mbInScheduler = false; ++ ++ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() << " " ++ << pMostUrgent << " invoke-out" ); ++ ++ // eventually pop the scheduler stack ++ // this just happens for nested calls, which renders all accounting ++ // invalid, so we just enforce a rescheduling! ++ if ( pMostUrgent == pSVData->maSchedCtx.mpSchedulerStack ) ++ { ++ pSchedulerData = pSVData->maSchedCtx.mpSchedulerStack; ++ pSVData->maSchedCtx.mpSchedulerStack = pSchedulerData->mpNext; ++ AppendSchedulerData( rSchedCtx, pSchedulerData ); ++ UpdateSystemTimer( rSchedCtx, ImmediateTimeoutMs, true, ++ tools::Time::GetSystemTicks() ); ++ } ++ else ++ { ++ // Since we can restart tasks, round-robin all non-last tasks ++ if ( pMostUrgent->mpNext ) ++ { ++ DropSchedulerData( rSchedCtx, pPrevMostUrgent, pMostUrgent ); ++ AppendSchedulerData( rSchedCtx, pMostUrgent ); ++ } ++ ++ if ( pMostUrgent->mpTask && pMostUrgent->mpTask->IsActive() ) ++ { ++ pMostUrgent->mnUpdateTime = nTime; ++ nReadyPeriod = pMostUrgent->mpTask->UpdateMinPeriod( nMinPeriod, nTime ); ++ if ( nMinPeriod > nReadyPeriod ) ++ nMinPeriod = nReadyPeriod; ++ UpdateSystemTimer( rSchedCtx, nMinPeriod, false, nTime ); ++ } ++ } ++ } ++ ++ return !!pMostUrgent; + } + + void Task::StartTimer( sal_uInt64 nMS ) + { +- Scheduler::ImplStartTimer( nMS, false ); ++ Scheduler::ImplStartTimer( nMS, false, tools::Time::GetSystemTicks() ); + } + + void Task::SetDeletionFlags() + { +- mpSchedulerData->mbDelete = true; + mbActive = false; + } + + void Task::Start() + { + ImplSVData *const pSVData = ImplGetSVData(); +- if (pSVData->mbDeInit) +- { +- return; +- } ++ ImplSchedulerContext &rSchedCtx = pSVData->maSchedCtx; + +- DBG_TESTSOLARMUTEX(); ++ SchedulerGuard aSchedulerGuard; ++ if ( !rSchedCtx.mbActive ) ++ return; + + // Mark timer active + mbActive = true; +@@ -289,34 +450,27 @@ void Task::Start() + if ( !mpSchedulerData ) + { + // insert Task +- mpSchedulerData = new ImplSchedulerData; +- mpSchedulerData->mpTask = this; +- mpSchedulerData->mbInScheduler = false; +- +- // insert last due to SFX! +- ImplSchedulerData* pPrev = nullptr; +- ImplSchedulerData* pData = pSVData->mpFirstSchedulerData; +- while ( pData ) +- { +- pPrev = pData; +- pData = pData->mpNext; +- } +- mpSchedulerData->mpNext = nullptr; +- if ( pPrev ) +- pPrev->mpNext = mpSchedulerData; +- else +- pSVData->mpFirstSchedulerData = mpSchedulerData; ++ ImplSchedulerData* pSchedulerData = new ImplSchedulerData; ++ pSchedulerData->mpTask = this; ++ pSchedulerData->mbInScheduler = false; ++ mpSchedulerData = pSchedulerData; ++ ++ AppendSchedulerData( rSchedCtx, pSchedulerData ); ++ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() ++ << " " << mpSchedulerData << " added " << *this ); + } +- mpSchedulerData->mbDelete = false; ++ else ++ SAL_INFO( "vcl.schedule", tools::Time::GetSystemTicks() ++ << " " << mpSchedulerData << " restarted " << *this ); ++ + mpSchedulerData->mnUpdateTime = tools::Time::GetSystemTicks(); + } + + void Task::Stop() + { ++ SAL_INFO_IF( mbActive, "vcl.schedule", tools::Time::GetSystemTicks() ++ << " " << mpSchedulerData << " stopped " << *this ); + mbActive = false; +- +- if ( mpSchedulerData ) +- mpSchedulerData->mbDelete = true; + } + + Task& Task::operator=( const Task& rTask ) +@@ -336,8 +490,9 @@ Task& Task::operator=( const Task& rTask ) + Task::Task( const sal_Char *pDebugName ) + : mpSchedulerData( nullptr ) + , mpDebugName( pDebugName ) +- , mePriority( TaskPriority::HIGH ) ++ , mePriority( TaskPriority::DEFAULT ) + , mbActive( false ) ++ , mbStatic( false ) + { + } + +@@ -346,6 +501,7 @@ Task::Task( const Task& rTask ) + , mpDebugName( rTask.mpDebugName ) + , mePriority( rTask.mePriority ) + , mbActive( false ) ++ , mbStatic( false ) + { + if ( rTask.IsActive() ) + Start(); +@@ -353,11 +509,14 @@ Task::Task( const Task& rTask ) + + Task::~Task() + { +- if ( mpSchedulerData ) ++ if ( !IsStatic() ) + { +- mpSchedulerData->mbDelete = true; +- mpSchedulerData->mpTask = nullptr; ++ SchedulerGuard aSchedulerGuard; ++ if ( mpSchedulerData ) ++ mpSchedulerData->mpTask = nullptr; + } ++ else ++ assert( nullptr == mpSchedulerData ); + } + + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/vcl/source/app/svapp.cxx b/vcl/source/app/svapp.cxx +index 3fcf82b4e59c..d382cdb87379 100644 +--- a/vcl/source/app/svapp.cxx ++++ b/vcl/source/app/svapp.cxx +@@ -54,6 +54,7 @@ + #if HAVE_FEATURE_OPENGL + #include + #endif ++#include + + #include "salinst.hxx" + #include "salframe.hxx" +@@ -64,6 +65,9 @@ + #include "window.h" + #include "accmgr.hxx" + #include "svids.hrc" ++#if OSL_DEBUG_LEVEL > 0 ++#include "schedulerimpl.hxx" ++#endif + + #include + #include +@@ -449,7 +453,7 @@ void Application::Execute() + pSVData->maAppData.mnEventTestLimit = 50; + pSVData->maAppData.mpEventTestingIdle = new Idle("eventtesting"); + pSVData->maAppData.mpEventTestingIdle->SetInvokeHandler(LINK(&(pSVData->maAppData), ImplSVAppData, VclEventTestingHdl)); +- pSVData->maAppData.mpEventTestingIdle->SetPriority(TaskPriority::MEDIUM); ++ pSVData->maAppData.mpEventTestingIdle->SetPriority(TaskPriority::HIGH_IDLE); + pSVData->maAppData.mpEventTestInput = new SvFileStream("eventtesting", StreamMode::READ); + pSVData->maAppData.mpEventTestingIdle->Start(); + } +@@ -467,19 +471,6 @@ inline bool ImplYield(bool i_bWait, bool i_bAllEvents, sal_uLong const nReleased + SAL_INFO("vcl.schedule", "Enter ImplYield: " << (i_bWait ? "wait" : "no wait") << + ": " << (i_bAllEvents ? "all events" : "one event") << ": " << nReleased); + +- bool bHasActiveIdles = false; +- sal_uInt64 nMinTimeout = 0; +- if (nReleased == 0) // else thread doesn't have SolarMutex so avoid race +- nMinTimeout = Scheduler::CalculateMinimumTimeout(bHasActiveIdles); +- +- // FIXME: should use returned value as param to DoYield +- (void)nMinTimeout; +- +- // If we have idles, don't wait for the timeout; check for events +- // and come back as quick as possible. +- if (bHasActiveIdles) +- i_bWait = false; +- + // TODO: there's a data race here on WNT only because ImplYield may be + // called without SolarMutex; if we can get rid of LazyDelete (with VclPtr) + // then the only remaining use of mnDispatchLevel is in OSX specific code +@@ -488,60 +479,73 @@ inline bool ImplYield(bool i_bWait, bool i_bAllEvents, sal_uLong const nReleased + + // do not wait for events if application was already quit; in that + // case only dispatch events already available +- // do not wait for events either if the app decided that it is too busy for timers +- // (feature added for the slideshow) +- SalYieldResult eResult = ++ bool bProcessedEvent = + pSVData->mpDefInst->DoYield( + i_bWait && !pSVData->maAppData.mbAppQuit, + i_bAllEvents, nReleased); + +- SAL_INFO("vcl.schedule", "DoYield with " << (bHasActiveIdles ? "active idles" : "no idles") << +- " returns: " << (eResult == SalYieldResult::EVENT ? "processed event" : "timeout")); +- + pSVData->maAppData.mnDispatchLevel--; + + DBG_TESTSOLARMUTEX(); // must be locked on return from Yield + +- if (nReleased == 0) // tdf#99383 don't run stuff from ReAcquireSolarMutex +- { +- // Process all Tasks +- Scheduler::ProcessTaskScheduling(eResult != SalYieldResult::EVENT); +- } +- + // flush lazy deleted objects + if( pSVData->maAppData.mnDispatchLevel == 0 ) + vcl::LazyDelete::flush(); + +- SAL_INFO("vcl.schedule", "Leave ImplYield"); +- +- return bHasActiveIdles || eResult == SalYieldResult::EVENT; ++ SAL_INFO("vcl.schedule", "Leave ImplYield with return " << bProcessedEvent ); ++ return bProcessedEvent; + } + +-void Application::Reschedule( bool i_bAllEvents ) ++bool Application::Reschedule( bool i_bAllEvents ) + { +- ImplYield(false, i_bAllEvents, 0); ++ return ImplYield(false, i_bAllEvents, 0); + } + + void Scheduler::ProcessEventsToSignal(bool& bSignal) + { +- while(!bSignal && (Scheduler::ProcessTaskScheduling( true ) || +- ImplYield(false, false, 0))) +- { +- } ++ while (!bSignal && Application::Reschedule() ); + } + + void Scheduler::ProcessEventsToIdle() + { +- int nSanity = 1000; +- while(Scheduler::ProcessTaskScheduling( true ) || +- ImplYield(false, false, 0)) ++ int nSanity = 1; ++#if OSL_DEBUG_LEVEL > 0 ++ const ImplSVData* pSVData = ImplGetSVData(); ++ bool bIsMainThread = pSVData->mpDefInst->IsMainThread(); ++ if ( bIsMainThread ) ++ Scheduler::Lock(); ++#endif ++ while( Application::Reschedule( true ) ) + { +- if (nSanity-- < 0) ++ if (0 == ++nSanity % 1000) + { +- SAL_WARN("vcl.schedule", "Unexpected volume of events to process"); +- break; ++ SAL_WARN("vcl.schedule", "ProcessEventsToIdle: " << nSanity); ++ } ++ } ++#if OSL_DEBUG_LEVEL > 0 ++ // If we yield from a non-main thread we just can guarantee that all idle ++ // events were processed at some point, but our check can't prevent further ++ // processing in the main thread, which may add new events, so skip it. ++ if ( !bIsMainThread ) ++ return; ++ const ImplSchedulerData* pSchedulerData = pSVData->maSchedCtx.mpFirstSchedulerData; ++ bool bAnyIdle = false; ++ while ( pSchedulerData ) ++ { ++ if ( pSchedulerData->mpTask && !pSchedulerData->mbInScheduler ) ++ { ++ Idle *pIdle = dynamic_cast( pSchedulerData->mpTask ); ++ if ( pIdle && pIdle->IsActive() ) ++ { ++ SAL_WARN( "vcl.schedule", "Unprocessed Idle: " ++ << pIdle << " " << pIdle->GetDebugName() ); ++ } + } ++ pSchedulerData = pSchedulerData->mpNext; + } ++ assert( !bAnyIdle ); ++ Scheduler::Unlock(); ++#endif + } + + extern "C" { +diff --git a/vcl/source/app/svmain.cxx b/vcl/source/app/svmain.cxx +index 2d7950a8aa11..9e1d8e5515c4 100644 +--- a/vcl/source/app/svmain.cxx ++++ b/vcl/source/app/svmain.cxx +@@ -576,8 +576,9 @@ void DeInitVCL() + // and thereby unloading the plugin + delete pSVData->mpSalSystem; + pSVData->mpSalSystem = nullptr; +- delete pSVData->mpSalTimer; +- pSVData->mpSalTimer = nullptr; ++ assert( !pSVData->maSchedCtx.mpSalTimer ); ++ delete pSVData->maSchedCtx.mpSalTimer; ++ pSVData->maSchedCtx.mpSalTimer = nullptr; + + pSVData->mpDefaultWin = nullptr; + pSVData->mpIntroWindow = nullptr; +diff --git a/vcl/source/app/timer.cxx b/vcl/source/app/timer.cxx +index 56f0922963b8..f615c9f732e6 100644 +--- a/vcl/source/app/timer.cxx ++++ b/vcl/source/app/timer.cxx +@@ -19,7 +19,9 @@ + + #include + #include ++#include + #include "saltimer.hxx" ++#include "schedulerimpl.hxx" + + void Timer::SetDeletionFlags() + { +@@ -28,26 +30,11 @@ void Timer::SetDeletionFlags() + Task::SetDeletionFlags(); + } + +-bool Timer::ReadyForSchedule( bool /* bIdle */, sal_uInt64 nTimeNow ) const +-{ +- return (GetSchedulerData()->mnUpdateTime + mnTimeout) <= nTimeNow; +-} +- +-bool Timer::IsIdle() const +-{ +- return false; +-} +- +-sal_uInt64 Timer::UpdateMinPeriod( sal_uInt64 nMinPeriod, sal_uInt64 nTimeNow ) const ++sal_uInt64 Timer::UpdateMinPeriod( sal_uInt64, sal_uInt64 nTimeNow ) const + { + sal_uInt64 nWakeupTime = GetSchedulerData()->mnUpdateTime + mnTimeout; +- if( nWakeupTime <= nTimeNow ) +- return Scheduler::ImmediateTimeoutMs; +- else +- { +- sal_uInt64 nSleepTime = nWakeupTime - nTimeNow; +- return ( nSleepTime < nMinPeriod ) ? nSleepTime : nMinPeriod; +- } ++ return ( nWakeupTime <= nTimeNow ) ++ ? Scheduler::ImmediateTimeoutMs : nWakeupTime - nTimeNow; + } + + Timer::Timer( bool bAuto, const sal_Char *pDebugName ) +@@ -55,7 +42,7 @@ Timer::Timer( bool bAuto, const sal_Char *pDebugName ) + , mnTimeout( Scheduler::ImmediateTimeoutMs ) + , mbAuto( bAuto ) + { +- SetPriority( TaskPriority::HIGHEST ); ++ SetPriority( TaskPriority::DEFAULT ); + } + + Timer::Timer( const sal_Char *pDebugName ) +diff --git a/vcl/source/edit/textdata.cxx b/vcl/source/edit/textdata.cxx +index ea6bd1d1ef0e..6b9b42c36bd0 100644 +--- a/vcl/source/edit/textdata.cxx ++++ b/vcl/source/edit/textdata.cxx +@@ -276,7 +276,7 @@ IdleFormatter::IdleFormatter() + { + mpView = nullptr; + mnRestarts = 0; +- SetPriority(TaskPriority::HIGH); ++ SetPriority(TaskPriority::HIGH_IDLE); + } + + IdleFormatter::~IdleFormatter() +diff --git a/vcl/source/gdi/print2.cxx b/vcl/source/gdi/print2.cxx +index 74ab167ecbdd..4ca2d0eacf69 100644 +--- a/vcl/source/gdi/print2.cxx ++++ b/vcl/source/gdi/print2.cxx +@@ -1231,8 +1231,7 @@ bool OutputDevice::RemoveTransparenciesFromMetaFile( const GDIMetaFile& rInMtf, + pCurrAct->Execute( aPaintVDev.get() ); + } + +- if( !( nActionNum % 8 ) ) +- Application::Reschedule(); ++ Application::Reschedule( true ); + } + + const bool bOldMap = mbMap; +diff --git a/vcl/source/uitest/uno/uiobject_uno.cxx b/vcl/source/uitest/uno/uiobject_uno.cxx +index 65956ac93a62..2d65e75f7c52 100644 +--- a/vcl/source/uitest/uno/uiobject_uno.cxx ++++ b/vcl/source/uitest/uno/uiobject_uno.cxx +@@ -11,6 +11,7 @@ + #include "uiobject_uno.hxx" + #include + #include ++#include + + #include + #include +@@ -114,7 +115,7 @@ void SAL_CALL UIObjectUnoObj::executeAction(const OUString& rAction, const css:: + mReady = false; + Idle aIdle; + aIdle.SetDebugName("UI Test Idle Handler"); +- aIdle.SetPriority(TaskPriority::HIGH); ++ aIdle.SetPriority(TaskPriority::DEFAULT); + + std::function func = [this](){ + +diff --git a/vcl/source/window/dockmgr.cxx b/vcl/source/window/dockmgr.cxx +index 0df373e02a10..27b94169f3d1 100644 +--- a/vcl/source/window/dockmgr.cxx ++++ b/vcl/source/window/dockmgr.cxx +@@ -87,11 +87,11 @@ ImplDockFloatWin2::ImplDockFloatWin2( vcl::Window* pParent, WinBits nWinBits, + SetBackground( GetSettings().GetStyleSettings().GetFaceColor() ); + + maDockIdle.SetInvokeHandler( LINK( this, ImplDockFloatWin2, DockTimerHdl ) ); +- maDockIdle.SetPriority( TaskPriority::MEDIUM ); ++ maDockIdle.SetPriority( TaskPriority::HIGH_IDLE ); + maDockIdle.SetDebugName( "vcl::ImplDockFloatWin2 maDockIdle" ); + + maEndDockIdle.SetInvokeHandler( LINK( this, ImplDockFloatWin2, EndDockTimerHdl ) ); +- maEndDockIdle.SetPriority( TaskPriority::MEDIUM ); ++ maDockIdle.SetPriority( TaskPriority::HIGH_IDLE ); + maEndDockIdle.SetDebugName( "vcl::ImplDockFloatWin2 maEndDockIdle" ); + } + +diff --git a/vcl/source/window/dockwin.cxx b/vcl/source/window/dockwin.cxx +index a6e828fe2d3c..99eece5f85e4 100644 +--- a/vcl/source/window/dockwin.cxx ++++ b/vcl/source/window/dockwin.cxx +@@ -97,7 +97,7 @@ ImplDockFloatWin::ImplDockFloatWin( vcl::Window* pParent, WinBits nWinBits, + SetBackground(); + + maDockIdle.SetInvokeHandler( LINK( this, ImplDockFloatWin, DockTimerHdl ) ); +- maDockIdle.SetPriority( TaskPriority::MEDIUM ); ++ maDockIdle.SetPriority( TaskPriority::HIGH_IDLE ); + maDockIdle.SetDebugName( "vcl::ImplDockFloatWin maDockIdle" ); + } + +diff --git a/vcl/unx/generic/app/saldata.cxx b/vcl/unx/generic/app/saldata.cxx +index 740ab2636852..f22af3fb4b66 100644 +--- a/vcl/unx/generic/app/saldata.cxx ++++ b/vcl/unx/generic/app/saldata.cxx +@@ -324,7 +324,6 @@ void X11SalData::PopXErrorLevel() + } + + SalXLib::SalXLib() +- : blockIdleTimeout( false ) + { + m_aTimeout.tv_sec = 0; + m_aTimeout.tv_usec = 0; +@@ -563,11 +562,11 @@ void X11SalData::XError( Display *pDisplay, XErrorEvent *pEvent ) + m_aXErrorHandlerStack.back().m_bWas = true; + } + +-void X11SalData::Timeout( bool idle ) ++void X11SalData::Timeout() + { + ImplSVData* pSVData = ImplGetSVData(); +- if( pSVData->mpSalTimer ) +- pSVData->mpSalTimer->CallCallback( idle ); ++ if( pSVData->maSchedCtx.mpSalTimer ) ++ pSVData->maSchedCtx.mpSalTimer->CallCallback(); + } + + struct YieldEntry +@@ -646,34 +645,22 @@ bool SalXLib::CheckTimeout( bool bExecuteTimers ) + * timers are being dispatched. + */ + m_aTimeout += m_nTimeoutMS; +- // Determine if the app is idle (for idle timers). If there's user input pending, +- // if there's IO pending or if we're called inside a temporary yield (=blockIdleTimeout), +- // then the app is not idle. +- bool idle = true; +- if( blockIdleTimeout || XPending( vcl_sal::getSalDisplay(GetGenericData())->GetDisplay())) +- idle = false; +- for ( int nFD = 0; idle && nFD < nFDs_; nFD++ ) +- { +- YieldEntry* pEntry = &(yieldTable[nFD]); +- if ( pEntry->fd && pEntry->HasPendingEvent()) +- idle = false; +- } + // notify +- X11SalData::Timeout( idle ); ++ X11SalData::Timeout(); + } + } + } + return bRet; + } + +-SalYieldResult ++bool + SalXLib::Yield( bool bWait, bool bHandleAllCurrentEvents ) + { +- blockIdleTimeout = !bWait; + // check for timeouts here if you want to make screenshots + static char* p_prioritize_timer = getenv ("SAL_HIGHPRIORITY_REPAINT"); ++ bool bHandledEvent = false; + if (p_prioritize_timer != nullptr) +- CheckTimeout(); ++ bHandledEvent = CheckTimeout(); + + const int nMaxEvents = bHandleAllCurrentEvents ? 100 : 1; + +@@ -689,8 +676,7 @@ SalXLib::Yield( bool bWait, bool bHandleAllCurrentEvents ) + pEntry->HandleNextEvent(); + if( ! bHandleAllCurrentEvents ) + { +- blockIdleTimeout = false; +- return SalYieldResult::EVENT; ++ return true; + } + } + } +@@ -705,7 +691,6 @@ SalXLib::Yield( bool bWait, bool bHandleAllCurrentEvents ) + timeval Timeout = noyield_; + timeval *pTimeout = &Timeout; + +- bool bHandledEvent = false; + + if (bWait) + { +@@ -742,7 +727,7 @@ SalXLib::Yield( bool bWait, bool bHandleAllCurrentEvents ) + + // usually handle timeouts here (as in 5.2) + if (p_prioritize_timer == nullptr) +- CheckTimeout(); ++ bHandledEvent = CheckTimeout() || bHandledEvent; + + // handle wakeup events. + if ((nFound > 0) && (FD_ISSET(m_pTimeoutFDS[0], &ReadFDS))) +@@ -766,8 +751,7 @@ SalXLib::Yield( bool bWait, bool bHandleAllCurrentEvents ) + // someone-else has done the job for us + if (nFound == 0) + { +- blockIdleTimeout = false; +- return SalYieldResult::TIMEOUT; ++ return false; + } + + for ( int nFD = 0; nFD < nFDs_; nFD++ ) +@@ -795,10 +779,8 @@ SalXLib::Yield( bool bWait, bool bHandleAllCurrentEvents ) + } + } + } +- blockIdleTimeout = false; + +- return bHandledEvent ? SalYieldResult::EVENT +- : SalYieldResult::TIMEOUT; ++ return bHandledEvent; + } + + void SalXLib::Wakeup() +diff --git a/vcl/unx/generic/app/salinst.cxx b/vcl/unx/generic/app/salinst.cxx +index c63274db4e4d..456c238a1d08 100644 +--- a/vcl/unx/generic/app/salinst.cxx ++++ b/vcl/unx/generic/app/salinst.cxx +@@ -166,7 +166,7 @@ bool X11SalInstance::AnyInput(VclInputFlags nType) + return bRet; + } + +-SalYieldResult X11SalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong const nReleased) ++bool X11SalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong const nReleased) + { + (void) nReleased; + assert(nReleased == 0); // not implemented +diff --git a/vcl/unx/gtk/gtkdata.cxx b/vcl/unx/gtk/gtkdata.cxx +index cf466457e744..1d07ed381e32 100644 +--- a/vcl/unx/gtk/gtkdata.cxx ++++ b/vcl/unx/gtk/gtkdata.cxx +@@ -417,7 +417,6 @@ GtkData::GtkData( SalInstance *pInstance ) + : SalGenericData( SAL_DATA_GTK, pInstance ) + , m_aDispatchMutex() + , m_aDispatchCondition() +- , blockIdleTimeout( false ) + { + m_pUserEvent = nullptr; + } +@@ -461,10 +460,8 @@ void GtkData::Dispose() + } + + /// Allows events to be processed, returns true if we processed an event. +-SalYieldResult GtkData::Yield( bool bWait, bool bHandleAllCurrentEvents ) ++bool GtkData::Yield( bool bWait, bool bHandleAllCurrentEvents ) + { +- blockIdleTimeout = !bWait; +- + /* #i33212# only enter g_main_context_iteration in one thread at any one + * time, else one of them potentially will never end as long as there is + * another thread in there. Having only one yielding thread actually dispatch +@@ -479,8 +476,7 @@ SalYieldResult GtkData::Yield( bool bWait, bool bHandleAllCurrentEvents ) + bDispatchThread = true; + else if( ! bWait ) + { +- blockIdleTimeout = false; +- return SalYieldResult::TIMEOUT; // someone else is waiting already, return ++ return false; // someone else is waiting already, return + } + + if( bDispatchThread ) +@@ -512,10 +508,8 @@ SalYieldResult GtkData::Yield( bool bWait, bool bHandleAllCurrentEvents ) + if( bWasEvent ) + m_aDispatchCondition.set(); // trigger non dispatch thread yields + } +- blockIdleTimeout = false; + +- return bWasEvent ? SalYieldResult::EVENT +- : SalYieldResult::TIMEOUT; ++ return bWasEvent; + } + + void GtkData::Init() +@@ -658,6 +652,10 @@ bool GtkData::ErrorTrapPop( bool bIgnoreError ) + return gdk_error_trap_pop () != 0; + } + ++#if !GLIB_CHECK_VERSION(2,32,0) ++#define G_SOURCE_REMOVE FALSE ++#endif ++ + extern "C" { + + struct SalGtkTimeoutSource { +@@ -735,14 +733,10 @@ extern "C" { + sal_gtk_timeout_defer( pTSource ); + + ImplSVData* pSVData = ImplGetSVData(); +- if( pSVData->mpSalTimer ) +- { +- // TODO: context_pending should be probably checked too, but it causes locking assertion failures +- bool idle = !pSalData->BlockIdleTimeout() && /*!g_main_context_pending( NULL ) &&*/ !gdk_events_pending(); +- pSVData->mpSalTimer->CallCallback( idle ); +- } ++ if( pSVData->maSchedCtx.mpSalTimer ) ++ pSVData->maSchedCtx.mpSalTimer->CallCallback(); + +- return TRUE; ++ return G_SOURCE_REMOVE; + } + + static GSourceFuncs sal_gtk_timeout_funcs = +@@ -787,13 +781,13 @@ GtkSalTimer::GtkSalTimer() + GtkSalTimer::~GtkSalTimer() + { + GtkInstance *pInstance = static_cast(GetSalData()->m_pInstance); +- pInstance->RemoveTimer( this ); ++ pInstance->RemoveTimer(); + Stop(); + } + + bool GtkSalTimer::Expired() + { +- if( !m_pTimeout ) ++ if( !m_pTimeout || g_source_is_destroyed( &m_pTimeout->aParent ) ) + return false; + + gint nDummy = 0; +@@ -806,6 +800,8 @@ void GtkSalTimer::Start( sal_uLong nMS ) + { + // glib is not 64bit safe in this regard. + assert( nMS <= G_MAXINT ); ++ if ( nMS > G_MAXINT ) ++ nMS = G_MAXINT; + m_nTimeoutMS = nMS; // for restarting + Stop(); // FIXME: ideally re-use an existing m_pTimeout + m_pTimeout = create_sal_gtk_timeout( this ); +@@ -868,7 +864,9 @@ void GtkData::PostUserEvent() + else // nothing pending anyway + { + m_pUserEvent = g_idle_source_new(); +- g_source_set_priority (m_pUserEvent, G_PRIORITY_HIGH); ++ // tdf#112890 some dialog don't appear under gtk2, use the same ++ // priority for user-events as gtk3 does since tdf#110737 ++ g_source_set_priority (m_pUserEvent, G_PRIORITY_HIGH_IDLE + 30); + g_source_set_can_recurse (m_pUserEvent, TRUE); + g_source_set_callback (m_pUserEvent, call_userEventFn, + static_cast(this), nullptr); +diff --git a/vcl/unx/gtk/gtkinst.cxx b/vcl/unx/gtk/gtkinst.cxx +index e01a267b6131..cdcfb4d322a5 100644 +--- a/vcl/unx/gtk/gtkinst.cxx ++++ b/vcl/unx/gtk/gtkinst.cxx +@@ -156,6 +156,7 @@ GtkInstance::GtkInstance( SalYieldMutex* pMutex ) + #else + : X11SalInstance( pMutex ) + #endif ++ , m_pTimer(nullptr) + , bNeedsInit(true) + , m_pLastCairoFontOptions(nullptr) + { +@@ -200,8 +201,7 @@ void GtkInstance::EnsureInit() + + GtkInstance::~GtkInstance() + { +- while( !m_aTimers.empty() ) +- delete *m_aTimers.begin(); ++ assert( nullptr == m_pTimer ); + DeInitAtkBridge(); + ResetLastSeenCairoFontOptions(); + } +@@ -405,21 +405,19 @@ void GtkInstance::DestroyMenuItem( SalMenuItem* ) {} + SalTimer* GtkInstance::CreateSalTimer() + { + EnsureInit(); +- GtkSalTimer *pTimer = new GtkSalTimer(); +- m_aTimers.push_back( pTimer ); +- return pTimer; ++ assert( nullptr == m_pTimer ); ++ if ( nullptr == m_pTimer ) ++ m_pTimer = new GtkSalTimer(); ++ return m_pTimer; + } + +-void GtkInstance::RemoveTimer (SalTimer *pTimer) ++void GtkInstance::RemoveTimer () + { + EnsureInit(); +- std::vector::iterator it; +- it = std::find( m_aTimers.begin(), m_aTimers.end(), pTimer ); +- if( it != m_aTimers.end() ) +- m_aTimers.erase( it ); ++ m_pTimer = nullptr; + } + +-SalYieldResult GtkInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong const nReleased) ++bool GtkInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents, sal_uLong const nReleased) + { + (void) nReleased; + assert(nReleased == 0); // not implemented +@@ -430,12 +428,7 @@ SalYieldResult GtkInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents, sa + bool GtkInstance::IsTimerExpired() + { + EnsureInit(); +- for( std::vector::iterator it = m_aTimers.begin(); +- it != m_aTimers.end(); ++it ) +- if( (*it)->Expired() ) +- return true; +- +- return false; ++ return (m_pTimer && m_pTimer->Expired()); + } + + bool GtkInstance::AnyInput( VclInputFlags nType ) +diff --git a/vcl/unx/gtk3/gtk3gtkdata.cxx b/vcl/unx/gtk3/gtk3gtkdata.cxx +index 559819aab026..58fb6d3d2844 100644 +--- a/vcl/unx/gtk3/gtk3gtkdata.cxx ++++ b/vcl/unx/gtk3/gtk3gtkdata.cxx +@@ -389,7 +389,6 @@ GtkData::GtkData( SalInstance *pInstance ) + : SalGenericData( SAL_DATA_GTK3, pInstance ) + , m_aDispatchMutex() + , m_aDispatchCondition() +- , blockIdleTimeout( false ) + { + m_pUserEvent = nullptr; + } +@@ -438,10 +437,8 @@ void GtkData::Dispose() + } + + /// Allows events to be processed, returns true if we processed an event. +-SalYieldResult GtkData::Yield( bool bWait, bool bHandleAllCurrentEvents ) ++bool GtkData::Yield( bool bWait, bool bHandleAllCurrentEvents ) + { +- blockIdleTimeout = !bWait; +- + /* #i33212# only enter g_main_context_iteration in one thread at any one + * time, else one of them potentially will never end as long as there is + * another thread in there. Having only one yielding thread actually dispatch +@@ -456,8 +453,7 @@ SalYieldResult GtkData::Yield( bool bWait, bool bHandleAllCurrentEvents ) + bDispatchThread = true; + else if( ! bWait ) + { +- blockIdleTimeout = false; +- return SalYieldResult::TIMEOUT; // someone else is waiting already, return ++ return false; // someone else is waiting already, return + } + + if( bDispatchThread ) +@@ -491,10 +487,8 @@ SalYieldResult GtkData::Yield( bool bWait, bool bHandleAllCurrentEvents ) + if( bWasEvent ) + m_aDispatchCondition.set(); // trigger non dispatch thread yields + } +- blockIdleTimeout = false; + +- return bWasEvent ? SalYieldResult::EVENT +- : SalYieldResult::TIMEOUT; ++ return bWasEvent; + } + + void GtkData::Init() +@@ -621,6 +615,10 @@ bool GtkData::ErrorTrapPop( bool bIgnoreError ) + return gdk_error_trap_pop () != 0; + } + ++#if !GLIB_CHECK_VERSION(2,32,0) ++#define G_SOURCE_REMOVE FALSE ++#endif ++ + extern "C" { + + struct SalGtkTimeoutSource { +@@ -698,14 +696,10 @@ extern "C" { + sal_gtk_timeout_defer( pTSource ); + + ImplSVData* pSVData = ImplGetSVData(); +- if( pSVData->mpSalTimer ) +- { +- // TODO: context_pending should be probably checked too, but it causes locking assertion failures +- bool idle = !pSalData->BlockIdleTimeout() && /*!g_main_context_pending( NULL ) &&*/ !gdk_events_pending(); +- pSVData->mpSalTimer->CallCallback( idle ); +- } ++ if( pSVData->maSchedCtx.mpSalTimer ) ++ pSVData->maSchedCtx.mpSalTimer->CallCallback(); + +- return TRUE; ++ return G_SOURCE_REMOVE; + } + + static GSourceFuncs sal_gtk_timeout_funcs = +@@ -750,13 +744,13 @@ GtkSalTimer::GtkSalTimer() + GtkSalTimer::~GtkSalTimer() + { + GtkInstance *pInstance = static_cast(GetSalData()->m_pInstance); +- pInstance->RemoveTimer( this ); ++ pInstance->RemoveTimer(); + Stop(); + } + + bool GtkSalTimer::Expired() + { +- if( !m_pTimeout ) ++ if( !m_pTimeout || g_source_is_destroyed( &m_pTimeout->aParent ) ) + return false; + + gint nDummy = 0; +@@ -769,6 +763,8 @@ void GtkSalTimer::Start( sal_uLong nMS ) + { + // glib is not 64bit safe in this regard. + assert( nMS <= G_MAXINT ); ++ if ( nMS > G_MAXINT ) ++ nMS = G_MAXINT; + m_nTimeoutMS = nMS; // for restarting + Stop(); // FIXME: ideally re-use an existing m_pTimeout + m_pTimeout = create_sal_gtk_timeout( this ); +diff --git a/vcl/unx/kde4/KDESalInstance.cxx b/vcl/unx/kde4/KDESalInstance.cxx +index dfe02cd36e2e..56f3df9465cb 100644 +--- a/vcl/unx/kde4/KDESalInstance.cxx ++++ b/vcl/unx/kde4/KDESalInstance.cxx +@@ -25,6 +25,8 @@ + #include "KDEXLib.hxx" + #include "KDESalDisplay.hxx" + ++#include ++#include + #include + + using namespace com::sun::star; +@@ -58,4 +60,9 @@ SalX11Display* KDESalInstance::CreateDisplay() const + return new SalKDEDisplay( QX11Info::display() ); + } + ++bool KDESalInstance::IsMainThread() const ++{ ++ return qApp->thread() == QThread::currentThread(); ++} ++ + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/vcl/unx/kde4/KDESalInstance.hxx b/vcl/unx/kde4/KDESalInstance.hxx +index 54a0b405d96f..7b7417caaaf1 100644 +--- a/vcl/unx/kde4/KDESalInstance.hxx ++++ b/vcl/unx/kde4/KDESalInstance.hxx +@@ -26,18 +26,20 @@ class SalFrame; + + class KDESalInstance : public X11SalInstance + { +- protected: +- virtual SalX11Display* CreateDisplay() const override; ++protected: ++ virtual SalX11Display* CreateDisplay() const override; + +- public: +- explicit KDESalInstance(SalYieldMutex* pMutex); +- virtual SalFrame* CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) override; ++public: ++ explicit KDESalInstance(SalYieldMutex* pMutex); ++ virtual SalFrame* CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) override; + +- virtual bool hasNativeFileSelection() const override { return true; } ++ virtual bool hasNativeFileSelection() const override { return true; } + +- virtual css::uno::Reference< css::ui::dialogs::XFilePicker2 > +- createFilePicker( const css::uno::Reference< +- css::uno::XComponentContext >& ) override; ++ virtual css::uno::Reference< css::ui::dialogs::XFilePicker2 > ++ createFilePicker( const css::uno::Reference< ++ css::uno::XComponentContext >& ) override; ++ ++ virtual bool IsMainThread() const override; + }; + + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ +diff --git a/vcl/unx/kde4/KDEXLib.cxx b/vcl/unx/kde4/KDEXLib.cxx +index a1dea735f348..39de53527cc4 100644 +--- a/vcl/unx/kde4/KDEXLib.cxx ++++ b/vcl/unx/kde4/KDEXLib.cxx +@@ -50,21 +50,23 @@ + KDEXLib::KDEXLib() : + SalXLib(), m_bStartupDone(false), + m_pFreeCmdLineArgs(nullptr), m_pAppCmdLineArgs(nullptr), m_nFakeCmdLineArgs( 0 ), +- m_isGlibEventLoopType(false), +- m_allowKdeDialogs(false), m_blockIdleTimeout(false) ++ m_isGlibEventLoopType(false), m_allowKdeDialogs(false), ++ m_timerEventId( -1 ), m_postUserEventId( -1 ) + { ++ m_timerEventId = QEvent::registerEventType(); ++ m_postUserEventId = QEvent::registerEventType(); ++ + // the timers created here means they belong to the main thread. + // As the timeoutTimer runs the LO event queue, which may block on a dialog, + // the timer has to use a Qt::QueuedConnection, otherwise the nested event + // loop will detect the blocking timer and drop it from the polling + // freezing LO X11 processing. ++ timeoutTimer.setSingleShot( true ); + connect( &timeoutTimer, SIGNAL( timeout()), this, SLOT( timeoutActivated()), Qt::QueuedConnection ); +- connect( &userEventTimer, SIGNAL( timeout()), this, SLOT( userEventActivated()), Qt::QueuedConnection ); + + // QTimer::start() can be called only in its (here main) thread, so this will + // forward between threads if needed + connect( this, SIGNAL( startTimeoutTimerSignal()), this, SLOT( startTimeoutTimer()), Qt::QueuedConnection ); +- connect( this, SIGNAL( startUserEventTimerSignal()), this, SLOT( startUserEventTimer()), Qt::QueuedConnection ); + + // this one needs to be blocking, so that the handling in main thread is processed before + // the thread emitting the signal continues +@@ -267,7 +269,7 @@ void KDEXLib::socketNotifierActivated( int fd ) + sdata.handle( fd, sdata.data ); + } + +-SalYieldResult KDEXLib::Yield( bool bWait, bool bHandleAllCurrentEvents ) ++bool KDEXLib::Yield( bool bWait, bool bHandleAllCurrentEvents ) + { + if( !m_isGlibEventLoopType ) + { +@@ -278,9 +280,7 @@ SalYieldResult KDEXLib::Yield( bool bWait, bool bHandleAllCurrentEvents ) + // otherwise they can remain unhandled for quite a long while + wasEvent = processYield( false, bHandleAllCurrentEvents ); + } +- SalYieldResult aResult = SalXLib::Yield(bWait, bHandleAllCurrentEvents); +- return (aResult == SalYieldResult::EVENT || wasEvent) ? +- SalYieldResult::EVENT : SalYieldResult::TIMEOUT; ++ return SalXLib::Yield(bWait, bHandleAllCurrentEvents) || wasEvent; + } + // if we are the main thread (which is where the event processing is done), + // good, just do it +@@ -294,9 +294,8 @@ SalYieldResult KDEXLib::Yield( bool bWait, bool bHandleAllCurrentEvents ) + // release the yield lock to prevent deadlock with the main thread + // (it's ok to release it here, since even normal processYield() would + // temporarily do it while checking for new events) +- SalYieldMutexReleaser aReleaser; +- Q_EMIT processYieldSignal( bWait, bHandleAllCurrentEvents ); +- return SalYieldResult::TIMEOUT; ++ SolarMutexReleaser aReleaser; ++ return Q_EMIT processYieldSignal( bWait, bHandleAllCurrentEvents ); + } + } + +@@ -304,18 +303,15 @@ SalYieldResult KDEXLib::Yield( bool bWait, bool bHandleAllCurrentEvents ) + * Quoting the Qt docs: [QAbstractEventDispatcher::processEvents] processes + * pending events that match flags until there are no more events to process. + */ +-SalYieldResult KDEXLib::processYield( bool bWait, bool ) ++bool KDEXLib::processYield( bool bWait, bool ) + { +- m_blockIdleTimeout = !bWait; + QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance( qApp->thread()); + bool wasEvent = false; + if ( bWait ) + wasEvent = dispatcher->processEvents( QEventLoop::WaitForMoreEvents ); + else + wasEvent = dispatcher->processEvents( QEventLoop::AllEvents ); +- m_blockIdleTimeout = false; +- return wasEvent ? SalYieldResult::EVENT +- : SalYieldResult::TIMEOUT; ++ return wasEvent; + } + + void KDEXLib::StartTimer( sal_uLong nMS ) +@@ -344,21 +340,16 @@ void KDEXLib::StopTimer() + + void KDEXLib::timeoutActivated() + { +- // HACK? Always process posted events before timer timeouts. +- // There are places that may watch both (for example, there's a posted +- // event about change of the current active window and there's a timeout +- // event informing that a document has finished loading). This is of course +- // racy, but both generic and gtk event loops manage to deliver posted events +- // first, so it's at least consistent, and it probably kind of makes at least +- // some sense (timeouts should be more ok to wait and be triggered somewhen). +- while( SalKDEDisplay::self()->HasUserEvents() ) +- SalKDEDisplay::self()->DispatchInternalEvent(); ++ // don't potentially wait in timeout, as QTimer is non-recursive ++ QApplication::postEvent(this, new QEvent(QEvent::Type( m_timerEventId ))); ++} + +- // QGuiEventDispatcherGlib makes glib watch also X11 fd, but its hasPendingEvents() +- // doesn't check X11, so explicitly check XPending() here. +- bool idle = QApplication::hasPendingEvents() && !m_blockIdleTimeout && !XPending( QX11Info::display()); +- X11SalData::Timeout( idle ); +- // QTimer is not single shot, so will be restarted immediately ++void KDEXLib::customEvent(QEvent* e) ++{ ++ if( e->type() == m_timerEventId ) ++ X11SalData::Timeout(); ++ else if( e->type() == m_postUserEventId ) ++ SalKDEDisplay::self()->DispatchInternalEvent(); + } + + void KDEXLib::Wakeup() +@@ -372,23 +363,7 @@ void KDEXLib::PostUserEvent() + { + if( !m_isGlibEventLoopType ) + return SalXLib::PostUserEvent(); +- if( qApp->thread() == QThread::currentThread()) +- startUserEventTimer(); +- else +- Q_EMIT startUserEventTimerSignal(); +-} +- +-void KDEXLib::startUserEventTimer() +-{ +- userEventTimer.start( 0 ); +-} +- +-void KDEXLib::userEventActivated() +-{ +- if( ! SalKDEDisplay::self()->HasUserEvents() ) +- userEventTimer.stop(); +- SalKDEDisplay::self()->DispatchInternalEvent(); +- // QTimer is not single shot, so will be restarted immediately ++ QApplication::postEvent(this, new QEvent(QEvent::Type( m_postUserEventId ))); + } + + void KDEXLib::doStartup() +diff --git a/vcl/unx/kde4/KDEXLib.hxx b/vcl/unx/kde4/KDEXLib.hxx +index 4b482cc8c0d9..204cb6cf80ea 100644 +--- a/vcl/unx/kde4/KDEXLib.hxx ++++ b/vcl/unx/kde4/KDEXLib.hxx +@@ -52,10 +52,10 @@ class KDEXLib : public QObject, public SalXLib + }; + QHash< int, SocketData > socketData; // key is fd + QTimer timeoutTimer; +- QTimer userEventTimer; + bool m_isGlibEventLoopType; + bool m_allowKdeDialogs; +- bool m_blockIdleTimeout; ++ int m_timerEventId; ++ int m_postUserEventId; + + private: + void setupEventLoop(); +@@ -63,14 +63,12 @@ class KDEXLib : public QObject, public SalXLib + private Q_SLOTS: + void socketNotifierActivated( int fd ); + void timeoutActivated(); +- void userEventActivated(); + void startTimeoutTimer(); +- void startUserEventTimer(); +- SalYieldResult processYield( bool bWait, bool bHandleAllCurrentEvents ); ++ static bool processYield( bool bWait, bool bHandleAllCurrentEvents ); ++ + Q_SIGNALS: + void startTimeoutTimerSignal(); +- void startUserEventTimerSignal(); +- void processYieldSignal( bool bWait, bool bHandleAllCurrentEvents ); ++ bool processYieldSignal( bool bWait, bool bHandleAllCurrentEvents ); + css::uno::Reference< css::ui::dialogs::XFilePicker2 > + createFilePickerSignal( const css::uno::Reference< css::uno::XComponentContext >& ); + +@@ -79,7 +77,7 @@ class KDEXLib : public QObject, public SalXLib + virtual ~KDEXLib() override; + + virtual void Init() override; +- virtual SalYieldResult Yield( bool bWait, bool bHandleAllCurrentEvents ) override; ++ virtual bool Yield( bool bWait, bool bHandleAllCurrentEvents ) override; + virtual void Insert( int fd, void* data, YieldFunc pending, YieldFunc queued, YieldFunc handle ) override; + virtual void Remove( int fd ) override; + virtual void StartTimer( sal_uLong nMS ) override; +@@ -90,6 +88,8 @@ class KDEXLib : public QObject, public SalXLib + void doStartup(); + bool allowKdeDialogs() { return m_allowKdeDialogs; } + ++ virtual void customEvent(QEvent* e) override; ++ + public Q_SLOTS: + css::uno::Reference< css::ui::dialogs::XFilePicker2 > + createFilePicker( const css::uno::Reference< css::uno::XComponentContext >& ); +-- +2.14.3 + diff --git a/libreoffice.spec b/libreoffice.spec index 5fc3337..1dc44ad 100644 --- a/libreoffice.spec +++ b/libreoffice.spec @@ -57,7 +57,7 @@ Summary: Free Software Productivity Suite Name: libreoffice Epoch: 1 Version: %{libo_version}.2 -Release: 3%{?libo_prerelease}%{?dist} +Release: 4%{?libo_prerelease}%{?dist} License: (MPLv1.1 or LGPLv3+) and LGPLv3 and LGPLv2+ and BSD and (MPLv1.1 or GPLv2 or LGPLv2 or Netscape) and Public Domain and ASL 2.0 and MPLv2.0 and CC0 URL: http://www.libreoffice.org/ @@ -253,6 +253,7 @@ Patch8: 0001-impress-constantly-trying-to-create-an-internal-Side.patch Patch9: 0001-Related-tdf-105998-except-cut-and-paste-as-bitmap-in.patch Patch10: 0001-always-install-fc_local.conf-on-linux.patch Patch11: 0001-editviewoverlay-Allow-EditView-to-run-in-Overlay.patch +Patch12: 0001-backport-single-shot-timer-scheduler-changes.patch %if 0%{?rhel} # not upstreamed @@ -2223,6 +2224,9 @@ done %{_includedir}/LibreOfficeKit %changelog +* Thu Jan 18 2018 Caolán McNamara - 1:5.4.4.2-4 +- Resolves: rhbz#1534149 glib2-2.54.3 >= triggers 100% cpu + * Fri Jan 12 2018 Caolán McNamara - 1:5.4.4.2-3 - backport editviewoverlay to workaround unfortunate Overpass metrics