AutoSuspend: Avoid unbalanced prevent/allow Suspend calls (#8970)

This prevents crashing interactions with other prevent/allow callers when the feature is disabled.
pull/8983/head
NiLuJe 2 years ago committed by GitHub
parent b845674bed
commit 61cafab0e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -805,7 +805,6 @@ function Kobo:suspend()
-- So, unless that changes, unconditionally disable it.
--[[
local has_wakeup_count = false
f = io.open("/sys/power/wakeup_count", "re")
if f ~= nil then
@ -822,12 +821,11 @@ function Kobo:suspend()
curr_wakeup_count = "$(cat /sys/power/wakeup_count)"
logger.info("Kobo suspend: Current WakeUp count:", curr_wakeup_count)
end
-]]
-- NOTE: Sets gSleep_Mode_Suspend to 1. Used as a flag throughout the
-- kernel to suspend/resume various subsystems
-- cf. kernel/power/main.c @ L#207
-- c.f., state_extended_store @ kernel/power/main.c
local ret = writeToSys("1", "/sys/power/state-extended")
logger.info("Kobo suspend: asked the kernel to put subsystems to sleep, ret:", ret)
@ -838,7 +836,6 @@ function Kobo:suspend()
logger.info("Kobo suspend: synced FS")
--[[
if has_wakeup_count then
f = io.open("/sys/power/wakeup_count", "we")
if not f then
@ -854,13 +851,12 @@ function Kobo:suspend()
end
f:close()
end
--]]
logger.info("Kobo suspend: asking for a suspend to RAM . . .")
f = io.open("/sys/power/state", "we")
if not f then
-- reset state-extend back to 0 since we are giving up
-- Reset state-extended back to 0 since we are giving up.
local ext_fd = io.open("/sys/power/state-extended", "we")
if not ext_fd then
logger.err("cannot open /sys/power/state-extended for writing!")

@ -1226,11 +1226,14 @@ This is essentially a cached TimeVal:now(), computed at the top of every iterati
(right before checking/running scheduled tasks).
This is mainly useful to compute/schedule stuff in the same time scale as the UI loop (i.e., MONOTONIC),
without having to resort to a syscall.
It should never be significantly stale (i.e., it should be precise enough),
unless you're blocking the UI for a significant amount of time in the same UI tick.
It should never be significantly stale, assuming the UI is in use (e.g., there are input events),
unless you're blocking the UI for a significant amount of time in a single UI frame.
Prefer the appropriate TimeVal method for your needs if you require perfect accuracy
(e.g., when you're actually working on the event loop *itself* (UIManager, Input, GestureDetector)).
That is to say, its granularity is an UI frame.
Prefer the appropriate TimeVal method for your needs if you require perfect accuracy or better granularity
(e.g., when you're actually working on the event loop *itself* (UIManager, Input, GestureDetector),
or if you're dealing with intra-frame timers).
This is *NOT* wall clock time (REALTIME).
]]

@ -33,8 +33,9 @@ local AutoSuspend = WidgetContainer:new{
auto_suspend_timeout_seconds = default_auto_suspend_timeout_seconds,
auto_standby_timeout_seconds = default_auto_standby_timeout_seconds,
last_action_tv = TimeVal.zero,
is_standby_scheduled = nil,
is_standby_scheduled = false,
task = nil,
standby_task = nil,
}
function AutoSuspend:_enabledStandby()
@ -61,7 +62,7 @@ function AutoSuspend:_schedule(shutdown_only)
delay_suspend = self.auto_suspend_timeout_seconds
delay_shutdown = self.autoshutdown_timeout_seconds
else
local now_tv = UIManager:getTime() + Device.total_standby_tv
local now_tv = UIManager:getElapsedTimeSinceBoot()
delay_suspend = (self.last_action_tv - now_tv):tonumber() + self.auto_suspend_timeout_seconds
delay_shutdown = (self.last_action_tv - now_tv):tonumber() + self.autoshutdown_timeout_seconds
end
@ -94,7 +95,7 @@ end
function AutoSuspend:_start()
if self:_enabled() or self:_enabledShutdown() then
self.last_action_tv = UIManager:getTime() + Device.total_standby_tv
self.last_action_tv = UIManager:getElapsedTimeSinceBoot()
logger.dbg("AutoSuspend: start at", self.last_action_tv:tonumber())
self:_schedule()
end
@ -103,7 +104,7 @@ end
-- Variant that only re-engages the shutdown timer for onUnexpectedWakeupLimit
function AutoSuspend:_restart()
if self:_enabledShutdown() then
self.last_action_tv = UIManager:getTime() + Device.total_standby_tv
self.last_action_tv = UIManager:getElapsedTimeSinceBoot()
logger.dbg("AutoSuspend: restart at", self.last_action_tv:tonumber())
self:_schedule(true)
end
@ -123,9 +124,18 @@ function AutoSuspend:init()
UIManager.event_hook:registerWidget("InputEvent", self)
-- We need an instance-specific function reference to schedule, because in some rare cases,
-- we may instantiate a new plugin instance *before* tearing down the old one.
-- If we only cared about accessing the right instance members,
-- we could use scheduleIn(t, self.function, self),
-- but we also care about unscheduling the task from *this* instance only:
-- unschedule(self.function) would unschedule that function for *every* instance,
-- as self.function == AutoSuspend.function ;).
self.task = function(shutdown_only)
self:_schedule(shutdown_only)
end
self.standby_task = function()
self:allowStandby()
end
self:_start()
self:_reschedule_standby()
@ -141,54 +151,68 @@ function AutoSuspend:onCloseWidget()
self:_unschedule()
self.task = nil
if not Device:canStandby() then return end
self:_unschedule_standby()
-- allowStandby is necessary, as we do a preventStandby on plugin start
UIManager:allowStandby()
self.standby_task = nil
end
function AutoSuspend:onInputEvent()
logger.dbg("AutoSuspend: onInputEvent")
self.last_action_tv = UIManager:getTime() + Device.total_standby_tv
self.last_action_tv = UIManager:getElapsedTimeSinceBoot()
-- NOTE: The fact that we run this on *this* event ensures we don't have to handle the standby scheduling
-- at all in setSuspendShutdownTimes ;).
self:_reschedule_standby()
end
function AutoSuspend:_unschedule_standby()
UIManager:unschedule(AutoSuspend.allowStandby)
if self.is_standby_scheduled and self.standby_task then
logger.dbg("AutoSuspend: unschedule standby")
UIManager:unschedule(self.standby_task)
-- Restore the UIManager balance, as we run preventStandby right after scheduling this task.
UIManager:allowStandby()
self.is_standby_scheduled = false
end
end
function AutoSuspend:_reschedule_standby(standby_timeout)
function AutoSuspend:_reschedule_standby()
if not Device:canStandby() then return end
standby_timeout = standby_timeout or self.auto_standby_timeout_seconds
-- We may have just disabled the feature, so unschedule before checking it.
self:_unschedule_standby()
if standby_timeout < 1 then
return
end
if not self:_enabledStandby() then return end
logger.dbg("AutoSuspend: schedule autoStandby in", self.auto_standby_timeout_seconds)
UIManager:scheduleIn(self.auto_standby_timeout_seconds, self.standby_task)
self.is_standby_scheduled = true
-- Prevent standby until our scheduled allowStandby
self:preventStandby()
logger.dbg("AutoSuspend: schedule autoStandby in", standby_timeout) -- xxx may be deleted later
UIManager:scheduleIn(standby_timeout, self.allowStandby, self)
end
function AutoSuspend:preventStandby()
if self.is_standby_scheduled ~= false then
self.is_standby_scheduled = false
UIManager:preventStandby()
end
-- Tell UIManager that we want to prevent standby until our allowStandby scheduled task runs.
UIManager:preventStandby()
end
-- NOTE: This is the scheduled task that should trip the UIManager state to standby
function AutoSuspend:allowStandby()
if not self.is_standby_scheduled then
self.is_standby_scheduled = true
UIManager:allowStandby()
logger.dbg("AutoSuspend:allowStandby")
-- Tell UIManager that we now allow standby.
UIManager:allowStandby()
-- This is necessary for wakeup from standby, as the deadline for receiving input events
-- is calculated from the time to the next scheduled function.
-- Make sure this function comes soon, as the time for going to standby after a scheduled wakeup
-- is prolonged by the given time. Any time between 0.500 and 0.001 seconds would go.
-- Let's call it deadline_guard.
UIManager:scheduleIn(0.100, function() end)
end
-- This is necessary for wakeup from standby, as the deadline for receiving input events
-- is calculated from the time to the next scheduled function.
-- Make sure this function comes soon, as the time for going to standby after a scheduled wakeup
-- is prolonged by the given time. Any time between 0.500 and 0.001 seconds would go.
-- Let's call it deadline_guard.
UIManager:scheduleIn(0.100, function() end)
-- We've just run our course.
self.is_standby_scheduled = false
end
function AutoSuspend:onSuspend()
@ -385,7 +409,7 @@ function AutoSuspend:addToMainMenu(menu_items)
Standby can not be entered if Wi-Fi is on.
Upon user input, the device needs a certain amount of time to wake up. With some devices this period of time is not noticeable, with other devices it can be annoying.]])
Upon user input, the device needs a certain amount of time to wake up. Generally, the newer the device, the less noticeable this delay will be, but it can be fairly aggravating on slower devices.]])
menu_items.autostandby = {
sorting_hint = "device",
@ -404,14 +428,14 @@ Upon user input, the device needs a certain amount of time to wake up. With some
help_text = standby_help,
keep_menu_open = true,
callback = function(touchmenu_instance)
-- 5 sec is the minimum and 60*60 sec (15min) is the maximum standby time.
-- 4 sec is the minimum and 15*60 sec (15min) is the maximum standby time.
-- We need a minimum time, so that scheduled function have a chance to execute.
-- A standby time of 15 min seem excessive.
-- But or battery testing it might give some sense.
self:setSuspendShutdownTimes(touchmenu_instance,
_("Timeout for autostandby"), _("Enter time in minutes and seconds."),
"auto_standby_timeout_seconds", default_auto_standby_timeout_seconds,
{3, 15*60}, 0)
{default_auto_standby_timeout_seconds, 15*60}, 0)
end,
}
end
@ -421,50 +445,58 @@ end
-- UI signals us that standby is allowed at this very moment because nothing else goes on in the background.
function AutoSuspend:onAllowStandby()
logger.dbg("AutoSuspend: onAllowStandby")
-- In case the OS frontend itself doesn't manage power state, we can do it on our own here.
-- One should also configure wake-up pins and perhaps wake alarm,
-- if we want to enter deeper sleep states later on from within standby.
-- This piggy-backs minimally on the UI framework implemented for the PocketBook autostandby plugin,
-- see its own AllowStandby handler for more details.
-- Start the long list of conditions in which we do *NOT* want to go into standby ;).
if not Device:canStandby() then
return
end
-- Don't enter standby if wifi is on, as this my break reconnecting (at least on Kobo-Sage)
-- Don't enter standby if we haven't set a proper timeout yet.
if not self:_enabledStandby() then
logger.dbg("AutoSuspend: No timeout set, no standby")
return
end
-- Don't enter standby if wifi is on, as this will break in fun and interesting ways (from Wi-Fi issues to kernel deadlocks).
if NetworkMgr:isWifiOn() then
logger.dbg("AutoSuspend: WiFi is on, no standby")
return
end
-- Don't enter standby if device is charging and it is a non sunxi kobo
-- Don't enter standby when charging on devices where charging prevents entering low power states.
if Device.powerd:isCharging() and not Device:canPowerSaveWhileCharging() then
logger.dbg("AutoSuspend: charging, no standby")
return
end
if Device:canStandby() then
local wake_in = math.huge
-- The next scheduled function should be the deadline_guard
-- Wake before the second next scheduled function executes (e.g. footer update, suspend ...)
local scheduler_times = UIManager:getNextTaskTimes(2)
if #scheduler_times == 2 then
-- Wake up slightly after the formerly scheduled event, to avoid resheduling the same function
-- after a fraction of a second again (e.g. don't draw footer twice)
wake_in = math.floor(scheduler_times[2]:tonumber()) + 1
end
-- Do the thing!
local wake_in = math.huge
-- The next scheduled function should be our deadline_guard (c.f., `AutoSuspend:allowStandby`).
-- Wake up before the second next scheduled function executes (e.g. footer update, suspend ...)
local scheduler_times = UIManager:getNextTaskTimes(2)
if #scheduler_times == 2 then
-- Wake up slightly after the formerly scheduled event,
-- to avoid resheduling the same function after a fraction of a second again (e.g. don't draw footer twice).
wake_in = math.floor(scheduler_times[2]:tonumber()) + 1
end
if wake_in > 3 then -- don't go into standby, if scheduled wake is in less than 3 secs
UIManager:broadcastEvent(Event:new("EnterStandby"))
logger.dbg("AutoSuspend: going to standby and wake in " .. wake_in .. "s zZzzZzZzzzzZZZzZZZz")
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: going to standby and wake in " .. wake_in .. "s zZzzZzZzzzzZZZzZZZz")
-- This is for the Kobo Sage/Elipsa for now, as these are the only with useStandby.
-- Other devices may be added
Device:standby(wake_in)
-- This obviously needs a matching implementation in Device, the canonical one being Kobo.
Device:standby(wake_in)
logger.dbg("AutoSuspend: leaving standby after " .. Device.last_standby_tv:tonumber() .. " s")
logger.dbg("AutoSuspend: leaving standby after " .. Device.last_standby_tv:tonumber() .. " s")
UIManager:broadcastEvent(Event:new("LeaveStandby"))
self:_unschedule() -- unschedule suspend and shutdown as the realtime clock has ticked
self:_schedule() -- reschedule suspend and shutdown with the new time
end
-- Don't do a `self:_reschedule_standby()` here, as this will interfere with suspend.
-- Better to to it in onLeaveStandby.
UIManager:broadcastEvent(Event:new("LeaveStandby"))
self:_unschedule() -- unschedule suspend and shutdown, as the realtime clock has ticked
self:_schedule() -- reschedule suspend and shutdown with the new time
end
-- Don't do a `self:_reschedule_standby()` here, as this will interfere with suspend.
-- Leave that to `onLeaveStandby`.
end
return AutoSuspend

Loading…
Cancel
Save