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

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

@ -243,6 +243,16 @@ function UIManager:close(widget, refreshtype, refreshregion, refreshdither)
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
function UIManager:schedule(sched_time, action, ...)
local lo, hi = 1, #self._task_queue

@ -212,44 +212,14 @@ function AutoDim:_onResume()
self:_schedule_autodim_task()
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()
self.onResume = self._onResume
self.onSuspend = self._onSuspend
self.onEnterStandby = self._onEnterStandby
self.onLeaveStandby = self._onLeaveStandby
end
function AutoDim:clearEventHandlers()
self.onResume = nil
self.onSuspend = nil
self.onEnterStandby = nil
self.onLeaveStandby = nil
end
function AutoDim:onFrontlightTurnedOff()

@ -5,7 +5,6 @@ if not Device:canSuspend() then
return { disabled = true, }
end
local Event = require("ui/event")
local Math = require("optmath")
local NetworkMgr = require("ui/network/manager")
local PluginShare = require("pluginshare")
@ -34,8 +33,6 @@ local AutoSuspend = WidgetContainer:extend{
task = nil,
kindle_task = nil,
standby_task = nil,
leave_standby_task = nil,
wrapped_leave_standby_task = nil,
going_to_suspend = nil,
}
@ -109,10 +106,10 @@ function AutoSuspend:_start()
end
end
function AutoSuspend:_start_standby()
function AutoSuspend:_start_standby(sleep_in)
if self:_enabledStandby() then
logger.dbg("AutoSuspend: start standby timer at", time.format_time(self.last_action_time))
self:_schedule_standby()
self:_schedule_standby(sleep_in)
end
end
@ -201,14 +198,6 @@ function AutoSuspend:init()
self.standby_task = function()
self:_schedule_standby()
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...
self:toggleStandbyHandler(self:_enabledStandby())
@ -235,7 +224,6 @@ function AutoSuspend:onCloseWidget()
self:_unschedule_standby()
self.standby_task = nil
self.leave_standby_task = nil
end
function AutoSuspend:onInputEvent()
@ -252,17 +240,13 @@ function AutoSuspend:_unschedule_standby()
self.is_standby_scheduled = false
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
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 ;).
if not Device:canStandby() then
if not Device:canStandby() or self.going_to_suspend then
return
end
@ -292,7 +276,7 @@ function AutoSuspend:_schedule_standby()
standby_delay_seconds = self.auto_standby_timeout_seconds
else
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,
-- 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),
-- 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.
standby_delay_seconds = self.auto_standby_timeout_seconds
standby_delay_seconds = sleep_in
end
end
@ -360,8 +344,7 @@ function AutoSuspend:onSuspend()
UIManager:preventStandby()
end
-- And make sure onLeaveStandby, which will come *after* us if we suspended *during* standby,
-- won't re-schedule stuff right before entering suspend...
-- Make sure that we don't re-schedule standby *after* us if we suspended *during* standby,
self.going_to_suspend = true
end
@ -386,20 +369,6 @@ function AutoSuspend:onResume()
self:_start_standby()
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()
logger.dbg("AutoSuspend: onUnexpectedWakeupLimit")
-- Should be unnecessary, because we should *always* follow onSuspend, which already does this...
@ -649,7 +618,6 @@ function AutoSuspend:AllowStandbyHandler()
end
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")
-- 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")
-- We delay the LeaveStandby event (our onLeaveStandby handler is responsible for rescheduling everything properly),
-- 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,
-- NOTE: UIManager consumes scheduled tasks before input events,
-- 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.
-- That means we go straight to input polling when returning, *without* a trip through the task queue
-- (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
-- 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.
-- 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,
-- because if we were woken up by user input, those events should already be in the evdev queue...
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
if not self.going_to_suspend then
self:_start_standby()
end
-- When we exit this method, we are sure that the input polling deadline is approximately `wake_in`.
-- So it is safe to schedule another task a bit later.
self:_start_standby(wake_in + 0.1) -- Schedule the next standby check 0.1 seconds after the next calculated wakeup time.
end
end

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

@ -137,7 +137,9 @@ function AutoWarmth:onAutoWarmthMode()
self:scheduleMidnightUpdate()
end
function AutoWarmth:leavePowerSavingState(from_resume)
function AutoWarmth:_onResume()
logger.dbg("AutoWarmth: onResume")
local resume_date = os.date("*t")
-- 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)
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:toggleFrontlight(now_s)
-- Reschedule 1sec after midnight
UIManager:scheduleIn(24*3600 + 1 - now_s, self.scheduleMidnightUpdate, self)
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
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()
logger.dbg("AutoWarmth: onSuspend")
UIManager:unschedule(self.scheduleMidnightUpdate)
@ -173,8 +165,6 @@ function AutoWarmth:_onSuspend()
UIManager:unschedule(self.setFrontlight)
end
AutoWarmth._onEnterStandby = AutoWarmth._onSuspend
function AutoWarmth:_onToggleNightMode()
logger.dbg("AutoWarmth: onToggleNightMode")
if not self.hide_nightmode_warning then
@ -220,8 +210,6 @@ end
function AutoWarmth:setEventHandlers()
self.onResume = self._onResume
self.onSuspend = self._onSuspend
self.onEnterStandby = self._onEnterStandby
self.onLeaveStandby = self._onLeaveStandby
if self.control_nightmode then
self.onToggleNightMode = self._onToggleNightMode
self.onSetNightMode = self._onToggleNightMode
@ -234,8 +222,6 @@ end
function AutoWarmth:clearEventHandlers()
self.onResume = nil
self.onSuspend = nil
self.onEnterStandby = nil
self.onLeaveStandby = nil
self.onToggleNightMode = nil
self.onSetNightMode = nil
self.onToggleFrontlight = nil

Loading…
Cancel
Save