PM: Optimize task queue handling around standby (#10203)

Instead of firing on(Enter|Leave)Standby Events, and having every other piece of code that might care about that handle re-scheduling their stuff themselves; simply make the standby implementation (i.e., AutoSuspend's) shift the whole task queue by the amount of time spent in standby to re-sync everything automatically.

(This is necessary in the first place because Linux, as the task queue ticks in CLOCK_MONOTONIC, which does *not* tick during suspend/standby; while we expect most of the tasks scheduled to actually reflect real world clock delays).
reviewable/pr10223/r5^2
zwim 1 year ago committed by GitHub
parent 86ddfc856d
commit 4dbaca180a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -165,10 +165,6 @@ function ReaderCoptListener:onResume()
self:headerRefresh() self:headerRefresh()
end end
function ReaderCoptListener:onLeaveStandby()
self:headerRefresh()
end
function ReaderCoptListener:onOutOfScreenSaver() function ReaderCoptListener:onOutOfScreenSaver()
if not self._delayed_screensaver then if not self._delayed_screensaver then
return return
@ -181,7 +177,6 @@ end
-- Unschedule on these events -- Unschedule on these events
ReaderCoptListener.onCloseDocument = ReaderCoptListener.unscheduleHeaderRefresh ReaderCoptListener.onCloseDocument = ReaderCoptListener.unscheduleHeaderRefresh
ReaderCoptListener.onSuspend = ReaderCoptListener.unscheduleHeaderRefresh ReaderCoptListener.onSuspend = ReaderCoptListener.unscheduleHeaderRefresh
ReaderCoptListener.onEnterStandby = ReaderCoptListener.unscheduleHeaderRefresh
function ReaderCoptListener:setAndSave(setting, property, value) function ReaderCoptListener:setAndSave(setting, property, value)
self.ui.document._document:setIntProperty(property, value) self.ui.document._document:setIntProperty(property, value)

@ -2471,21 +2471,12 @@ function ReaderFooter:onOutOfScreenSaver()
self:rescheduleFooterAutoRefreshIfNeeded() self:rescheduleFooterAutoRefreshIfNeeded()
end end
function ReaderFooter:onLeaveStandby()
self:maybeUpdateFooter()
self:rescheduleFooterAutoRefreshIfNeeded()
end
function ReaderFooter:onSuspend() function ReaderFooter:onSuspend()
self:unscheduleFooterAutoRefresh() self:unscheduleFooterAutoRefresh()
-- Reset the initial marker -- Reset the initial marker
self.progress_bar.inital_percentage = nil self.progress_bar.inital_percentage = nil
end end
function ReaderFooter:onEnterStandby()
self:unscheduleFooterAutoRefresh()
end
function ReaderFooter:onCloseDocument() function ReaderFooter:onCloseDocument()
self:unscheduleFooterAutoRefresh() self:unscheduleFooterAutoRefresh()
end end

@ -243,6 +243,16 @@ function UIManager:close(widget, refreshtype, refreshregion, refreshdither)
end end
end end
--- Shift the execution times of all scheduled tasks.
-- UIManager uses CLOCK_MONOTONIC (which doesn't tick during standby), so shifting the execution
-- time by a negative value will lead to an execution at the expected time.
-- @param time if positive execute the tasks later, if negative they should be executed earlier
function UIManager:shiftScheduledTasksBy(shift_time)
for i, v in ipairs(self._task_queue) do
v.time = v.time + shift_time
end
end
-- Schedule an execution task; task queue is in descending order -- Schedule an execution task; task queue is in descending order
function UIManager:schedule(sched_time, action, ...) function UIManager:schedule(sched_time, action, ...)
local lo, hi = 1, #self._task_queue local lo, hi = 1, #self._task_queue

@ -212,44 +212,14 @@ function AutoDim:_onResume()
self:_schedule_autodim_task() self:_schedule_autodim_task()
end end
function AutoDim:_onEnterStandby()
self:_unschedule_autodim_task()
-- don't unschedule ramp task, as this is done in onLeaveStandby if necessary
end
function AutoDim:_onLeaveStandby()
if self.isCurrentlyDimming then
if self.last_ramp_scheduling_time then
-- we are during the ramp down
local now = UIManager:getElapsedTimeSinceBoot()
local next_ramp_time_s = self.last_ramp_scheduling_time + time.s(self.autodim_step_time_s) - now
self:_unschedule_ramp_task() -- self.last_ramp_scheduling_time gets deleted with this call
self.isCurrentlyDimming = true -- as this gets deleted by `_unschedule_ramp_task()`
if next_ramp_time_s <= 0 then -- only happens, when standby is ended by a scheduled ramp_task()
self:ramp_task()
else
self:_schedule_ramp_task(time.to_s(next_ramp_time_s))
end
else
self:_unschedule_ramp_task()
end
else
self:autodim_task() -- check times and reschedule autodim_task if necessary
end
end
function AutoDim:setEventHandlers() function AutoDim:setEventHandlers()
self.onResume = self._onResume self.onResume = self._onResume
self.onSuspend = self._onSuspend self.onSuspend = self._onSuspend
self.onEnterStandby = self._onEnterStandby
self.onLeaveStandby = self._onLeaveStandby
end end
function AutoDim:clearEventHandlers() function AutoDim:clearEventHandlers()
self.onResume = nil self.onResume = nil
self.onSuspend = nil self.onSuspend = nil
self.onEnterStandby = nil
self.onLeaveStandby = nil
end end
function AutoDim:onFrontlightTurnedOff() function AutoDim:onFrontlightTurnedOff()

@ -5,7 +5,6 @@ if not Device:canSuspend() then
return { disabled = true, } return { disabled = true, }
end end
local Event = require("ui/event")
local Math = require("optmath") local Math = require("optmath")
local NetworkMgr = require("ui/network/manager") local NetworkMgr = require("ui/network/manager")
local PluginShare = require("pluginshare") local PluginShare = require("pluginshare")
@ -34,8 +33,6 @@ local AutoSuspend = WidgetContainer:extend{
task = nil, task = nil,
kindle_task = nil, kindle_task = nil,
standby_task = nil, standby_task = nil,
leave_standby_task = nil,
wrapped_leave_standby_task = nil,
going_to_suspend = nil, going_to_suspend = nil,
} }
@ -109,10 +106,10 @@ function AutoSuspend:_start()
end end
end end
function AutoSuspend:_start_standby() function AutoSuspend:_start_standby(sleep_in)
if self:_enabledStandby() then if self:_enabledStandby() then
logger.dbg("AutoSuspend: start standby timer at", time.format_time(self.last_action_time)) logger.dbg("AutoSuspend: start standby timer at", time.format_time(self.last_action_time))
self:_schedule_standby() self:_schedule_standby(sleep_in)
end end
end end
@ -201,14 +198,6 @@ function AutoSuspend:init()
self.standby_task = function() self.standby_task = function()
self:_schedule_standby() self:_schedule_standby()
end end
self.leave_standby_task = function()
-- Only if we're not already entering suspend...
if self.going_to_suspend then
return
end
UIManager:broadcastEvent(Event:new("LeaveStandby"))
end
-- Make sure we only have an AllowStandby handler when we actually want one... -- Make sure we only have an AllowStandby handler when we actually want one...
self:toggleStandbyHandler(self:_enabledStandby()) self:toggleStandbyHandler(self:_enabledStandby())
@ -235,7 +224,6 @@ function AutoSuspend:onCloseWidget()
self:_unschedule_standby() self:_unschedule_standby()
self.standby_task = nil self.standby_task = nil
self.leave_standby_task = nil
end end
function AutoSuspend:onInputEvent() function AutoSuspend:onInputEvent()
@ -252,17 +240,13 @@ function AutoSuspend:_unschedule_standby()
self.is_standby_scheduled = false self.is_standby_scheduled = false
end end
-- Make sure we don't trigger a ghost LeaveStandby event...
if self.leave_standby_task then
logger.dbg("AutoSuspend: unschedule leave standby task")
UIManager:unschedule(self.leave_standby_task)
end
end end
function AutoSuspend:_schedule_standby() function AutoSuspend:_schedule_standby(sleep_in)
sleep_in = sleep_in or self.auto_standby_timeout_seconds
-- Start the long list of conditions in which we do *NOT* want to go into standby ;). -- Start the long list of conditions in which we do *NOT* want to go into standby ;).
if not Device:canStandby() then if not Device:canStandby() or self.going_to_suspend then
return return
end end
@ -292,7 +276,7 @@ function AutoSuspend:_schedule_standby()
standby_delay_seconds = self.auto_standby_timeout_seconds standby_delay_seconds = self.auto_standby_timeout_seconds
else else
local now = UIManager:getElapsedTimeSinceBoot() local now = UIManager:getElapsedTimeSinceBoot()
standby_delay_seconds = self.auto_standby_timeout_seconds - time.to_number(now - self.last_action_time) standby_delay_seconds = sleep_in - time.to_number(now - self.last_action_time)
-- If we blow past the deadline on the first call of a scheduling cycle, -- If we blow past the deadline on the first call of a scheduling cycle,
-- make sure we don't go straight to allowStandby, as we haven't called preventStandby yet... -- make sure we don't go straight to allowStandby, as we haven't called preventStandby yet...
@ -302,7 +286,7 @@ function AutoSuspend:_schedule_standby()
-- or if the only input events we consumed did not trigger an InputEvent event (woken up by gyro events), -- or if the only input events we consumed did not trigger an InputEvent event (woken up by gyro events),
-- meaning self.last_action_time is further in the past than it ought to. -- meaning self.last_action_time is further in the past than it ought to.
-- Delay by the full amount to avoid further bad scheduling interactions. -- Delay by the full amount to avoid further bad scheduling interactions.
standby_delay_seconds = self.auto_standby_timeout_seconds standby_delay_seconds = sleep_in
end end
end end
@ -360,8 +344,7 @@ function AutoSuspend:onSuspend()
UIManager:preventStandby() UIManager:preventStandby()
end end
-- And make sure onLeaveStandby, which will come *after* us if we suspended *during* standby, -- Make sure that we don't re-schedule standby *after* us if we suspended *during* standby,
-- won't re-schedule stuff right before entering suspend...
self.going_to_suspend = true self.going_to_suspend = true
end end
@ -386,20 +369,6 @@ function AutoSuspend:onResume()
self:_start_standby() self:_start_standby()
end end
function AutoSuspend:onLeaveStandby()
logger.dbg("AutoSuspend: onLeaveStandby")
-- Unschedule suspend and shutdown, as the realtime clock has ticked
self:_unschedule()
-- Reschedule suspend and shutdown (we'll recompute the delay based on the last user input, *not* the current time).
-- i.e., the goal is to behave as if we'd never unscheduled it, making sure we do *NOT* reset the delay to the full timeout.
self:_start()
-- Assuming _start didn't send us straight to onSuspend (i.e., we were woken from standby by the scheduled suspend task!)...
if not self.going_to_suspend then
-- Reschedule standby, too (we're guaranteed that no standby task is currently scheduled, hence the lack of unscheduling).
self:_start_standby()
end
end
function AutoSuspend:onUnexpectedWakeupLimit() function AutoSuspend:onUnexpectedWakeupLimit()
logger.dbg("AutoSuspend: onUnexpectedWakeupLimit") logger.dbg("AutoSuspend: onUnexpectedWakeupLimit")
-- Should be unnecessary, because we should *always* follow onSuspend, which already does this... -- Should be unnecessary, because we should *always* follow onSuspend, which already does this...
@ -649,7 +618,6 @@ function AutoSuspend:AllowStandbyHandler()
end end
if wake_in >= 3 then -- don't go into standby, if scheduled wakeup is in less than 3 secs if wake_in >= 3 then -- don't go into standby, if scheduled wakeup is in less than 3 secs
UIManager:broadcastEvent(Event:new("EnterStandby"))
logger.dbg("AutoSuspend: entering standby with a wakeup alarm in", wake_in, "s") logger.dbg("AutoSuspend: entering standby with a wakeup alarm in", wake_in, "s")
-- This obviously needs a matching implementation in Device, the canonical one being Kobo. -- This obviously needs a matching implementation in Device, the canonical one being Kobo.
@ -657,29 +625,28 @@ function AutoSuspend:AllowStandbyHandler()
logger.dbg("AutoSuspend: left standby after", time.format_time(Device.last_standby_time), "s") logger.dbg("AutoSuspend: left standby after", time.format_time(Device.last_standby_time), "s")
-- We delay the LeaveStandby event (our onLeaveStandby handler is responsible for rescheduling everything properly), -- NOTE: UIManager consumes scheduled tasks before input events,
-- to make sure UIManager will consume the input events that woke us up first
-- (in case we were woken up by user input, as opposed to an rtc wake alarm)!
-- (This ensures we'll use an up to date last_action_time, and that it only ever gets updated from *user* input).
-- NOTE: While UIManager consumes scheduled tasks before input events, we do *NOT* have to rely on tickAfterNext,
-- solely because of where we run inside an UI frame (via UIManager:_standbyTransition): -- solely because of where we run inside an UI frame (via UIManager:_standbyTransition):
-- we're neither a scheduled task nor an input event, we run *between* scheduled tasks and input polling. -- we're neither a scheduled task nor an input event, we run *between* scheduled tasks and input polling.
-- That means we go straight to input polling when returning, *without* a trip through the task queue -- That means we go straight to input polling when returning, *without* a trip through the task queue
-- (c.f., UIManager:_checkTasks in UIManager:handleInput). -- (c.f., UIManager:_checkTasks in UIManager:handleInput).
UIManager:nextTick(self.leave_standby_task)
UIManager:shiftScheduledTasksBy( - Device.last_standby_time) -- correct scheduled times by last_standby_time
-- Since we go straight to input polling, and that our time spent in standby won't have affected the already computed -- Since we go straight to input polling, and that our time spent in standby won't have affected the already computed
-- input polling deadline (because MONOTONIC doesn't tick during standby/suspend), -- input polling deadline (because MONOTONIC doesn't tick during standby/suspend),
-- tweak said deadline to make sure poll will return immediately, so we get a chance to run through the task queue ASAP. -- tweak said deadline to make sure poll will return immediately, so we get a chance to run through the task queue ASAP.
-- This ensures we get a LeaveStandby event in a timely fashion,
-- even when there isn't actually any user input happening (e.g., woken up by the rtc alarm).
-- This shouldn't prevent us from actually consuming any pending input events first, -- This shouldn't prevent us from actually consuming any pending input events first,
-- because if we were woken up by user input, those events should already be in the evdev queue... -- because if we were woken up by user input, those events should already be in the evdev queue...
UIManager:consumeInputEarlyAfterPM(true) UIManager:consumeInputEarlyAfterPM(true)
-- When we exit this method, we are sure that the input polling deadline is zero (consumeInputEarly).
-- UIManager will check newly scheduled tasks before going to input polling again (with a new deadline).
self:_start_standby() -- Schedule the next standby check in the future.
else else
if not self.going_to_suspend then -- When we exit this method, we are sure that the input polling deadline is approximately `wake_in`.
self:_start_standby() -- So it is safe to schedule another task a bit later.
end self:_start_standby(wake_in + 0.1) -- Schedule the next standby check 0.1 seconds after the next calculated wakeup time.
end end
end end

@ -1,4 +1,3 @@
local Device = require("device")
local Event = require("ui/event") local Event = require("ui/event")
local PluginShare = require("pluginshare") local PluginShare = require("pluginshare")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
@ -29,14 +28,14 @@ function AutoTurn:_schedule()
return return
end end
local delay = self.last_action_time + time.s(self.autoturn_sec) - UIManager:getTime() local delay = self.last_action_time + time.s(self.autoturn_sec) - UIManager:getElapsedTimeSinceBoot()
if delay <= 0 then if delay <= 0 then
local top_wg = UIManager:getTopmostVisibleWidget() or {} local top_wg = UIManager:getTopmostVisibleWidget() or {}
if top_wg.name == "ReaderUI" then if top_wg.name == "ReaderUI" then
logger.dbg("AutoTurn: go to next page") logger.dbg("AutoTurn: go to next page")
self.ui:handleEvent(Event:new("GotoViewRel", self.autoturn_distance)) self.ui:handleEvent(Event:new("GotoViewRel", self.autoturn_distance))
self.last_action_time = UIManager:getTime() self.last_action_time = UIManager:getElapsedTimeSinceBoot()
end end
logger.dbg("AutoTurn: schedule in", self.autoturn_sec) logger.dbg("AutoTurn: schedule in", self.autoturn_sec)
UIManager:scheduleIn(self.autoturn_sec, self.task) UIManager:scheduleIn(self.autoturn_sec, self.task)
@ -60,10 +59,10 @@ end
function AutoTurn:_start() function AutoTurn:_start()
if self:_enabled() then if self:_enabled() then
local now = UIManager:getTime() local time_since_boot = UIManager:getElapsedTimeSinceBoot()
logger.dbg("AutoTurn: start at", time.format_time(now)) logger.dbg("AutoTurn: start at", time.format_time(time_since_boot))
PluginShare.pause_auto_suspend = true PluginShare.pause_auto_suspend = true
self.last_action_time = now self.last_action_time = time_since_boot
self:_schedule() self:_schedule()
local text local text
@ -109,11 +108,7 @@ end
function AutoTurn:onInputEvent() function AutoTurn:onInputEvent()
logger.dbg("AutoTurn: onInputEvent") logger.dbg("AutoTurn: onInputEvent")
self.last_action_time = UIManager:getTime() self.last_action_time = UIManager:getElapsedTimeSinceBoot()
end
function AutoTurn:onEnterStandby()
self:_unschedule()
end end
-- We do not want autoturn to turn pages during the suspend process. -- We do not want autoturn to turn pages during the suspend process.
@ -123,16 +118,6 @@ function AutoTurn:onSuspend()
self:_unschedule() self:_unschedule()
end end
function AutoTurn:_onLeaveStandby()
self.last_action_time = self.last_action_time - Device.last_standby_time
-- We messed with last_action_time, so a complete reschedule has to be done.
if self:_enabled() then
self:_unschedule()
self:_schedule()
end
end
function AutoTurn:_onResume() function AutoTurn:_onResume()
logger.dbg("AutoTurn: onResume") logger.dbg("AutoTurn: onResume")
self:_start() self:_start()
@ -168,7 +153,6 @@ function AutoTurn:addToMainMenu(menu_items)
self:_unschedule() self:_unschedule()
menu:updateItems() menu:updateItems()
self.onResume = nil self.onResume = nil
self.onLeaveStandby = nil
end, end,
ok_always_enabled = true, ok_always_enabled = true,
callback = function(t) callback = function(t)
@ -180,7 +164,6 @@ function AutoTurn:addToMainMenu(menu_items)
self:_start() self:_start()
menu:updateItems() menu:updateItems()
self.onResume = self._onResume self.onResume = self._onResume
self.onLeaveStandby = self._onLeaveStandby
end, end,
} }
UIManager:show(autoturn_spin) UIManager:show(autoturn_spin)

@ -137,7 +137,9 @@ function AutoWarmth:onAutoWarmthMode()
self:scheduleMidnightUpdate() self:scheduleMidnightUpdate()
end end
function AutoWarmth:leavePowerSavingState(from_resume) function AutoWarmth:_onResume()
logger.dbg("AutoWarmth: onResume")
local resume_date = os.date("*t") local resume_date = os.date("*t")
-- check if resume and suspend are done on the same day -- check if resume and suspend are done on the same day
@ -146,26 +148,16 @@ function AutoWarmth:leavePowerSavingState(from_resume)
local now_s = SunTime:getTimeInSec(resume_date) local now_s = SunTime:getTimeInSec(resume_date)
self.sched_warmth_index = self.sched_warmth_index - 1 -- scheduleNextWarmth will check this self.sched_warmth_index = self.sched_warmth_index - 1 -- scheduleNextWarmth will check this
self:scheduleNextWarmthChange(from_resume) self:scheduleNextWarmthChange(true)
self:scheduleToggleFrontlight(now_s) -- reset user toggles at sun set or sun rise self:scheduleToggleFrontlight(now_s) -- reset user toggles at sun set or sun rise
self:toggleFrontlight(now_s) self:toggleFrontlight(now_s)
-- Reschedule 1sec after midnight -- Reschedule 1sec after midnight
UIManager:scheduleIn(24*3600 + 1 - now_s, self.scheduleMidnightUpdate, self) UIManager:scheduleIn(24*3600 + 1 - now_s, self.scheduleMidnightUpdate, self)
else else
self:scheduleMidnightUpdate(from_resume) -- resume is on the other day, do all calcs again self:scheduleMidnightUpdate(true) -- resume is on the other day, do all calcs again
end end
end end
function AutoWarmth:_onResume()
logger.dbg("AutoWarmth: onResume")
self:leavePowerSavingState(true)
end
function AutoWarmth:_onLeaveStandby()
logger.dbg("AutoWarmth: onLeaveStandby")
self:leavePowerSavingState(false)
end
function AutoWarmth:_onSuspend() function AutoWarmth:_onSuspend()
logger.dbg("AutoWarmth: onSuspend") logger.dbg("AutoWarmth: onSuspend")
UIManager:unschedule(self.scheduleMidnightUpdate) UIManager:unschedule(self.scheduleMidnightUpdate)
@ -173,8 +165,6 @@ function AutoWarmth:_onSuspend()
UIManager:unschedule(self.setFrontlight) UIManager:unschedule(self.setFrontlight)
end end
AutoWarmth._onEnterStandby = AutoWarmth._onSuspend
function AutoWarmth:_onToggleNightMode() function AutoWarmth:_onToggleNightMode()
logger.dbg("AutoWarmth: onToggleNightMode") logger.dbg("AutoWarmth: onToggleNightMode")
if not self.hide_nightmode_warning then if not self.hide_nightmode_warning then
@ -220,8 +210,6 @@ end
function AutoWarmth:setEventHandlers() function AutoWarmth:setEventHandlers()
self.onResume = self._onResume self.onResume = self._onResume
self.onSuspend = self._onSuspend self.onSuspend = self._onSuspend
self.onEnterStandby = self._onEnterStandby
self.onLeaveStandby = self._onLeaveStandby
if self.control_nightmode then if self.control_nightmode then
self.onToggleNightMode = self._onToggleNightMode self.onToggleNightMode = self._onToggleNightMode
self.onSetNightMode = self._onToggleNightMode self.onSetNightMode = self._onToggleNightMode
@ -234,8 +222,6 @@ end
function AutoWarmth:clearEventHandlers() function AutoWarmth:clearEventHandlers()
self.onResume = nil self.onResume = nil
self.onSuspend = nil self.onSuspend = nil
self.onEnterStandby = nil
self.onLeaveStandby = nil
self.onToggleNightMode = nil self.onToggleNightMode = nil
self.onSetNightMode = nil self.onSetNightMode = nil
self.onToggleFrontlight = nil self.onToggleFrontlight = nil

Loading…
Cancel
Save