From 31a2c3ef2bef93dae71e97a6271e945184eb6147 Mon Sep 17 00:00:00 2001 From: NiLuJe Date: Sun, 22 May 2022 23:51:17 +0200 Subject: [PATCH] Fix a nasty race condition when trying to unschedule a tickAfterNext task *before* the task actually makes it to the task queue (Because that only happens on the next UI frame, in the current frame, what's in the task queue is the anonymous wrapper function that passes action to nextTick). --- frontend/ui/uimanager.lua | 12 +++++++++++- plugins/autosuspend.koplugin/main.lua | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index 93e727d22..507fe187a 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -630,6 +630,9 @@ Useful to run UI callbacks ASAP without skipping repaints. @func action reference to the task to be scheduled (may be anonymous) @param ... optional arguments passed to action + +@return A reference to the initial nextTick wrapper function, +necessary if the caller wants to unschedule action *before* it actually gets inserted in the task queue by nextTick. @see nextTick ]] function UIManager:tickAfterNext(action, ...) @@ -637,7 +640,14 @@ function UIManager:tickAfterNext(action, ...) -- c.f., http://lua-users.org/wiki/VarargTheSecondClassCitizen local n = select('#', ...) local va = {...} - return self:nextTick(function() self:nextTick(action, unpack(va, 1, n)) end) + -- We need to keep a reference to this anonymous function, as it is *NOT* quite `action`, + -- and the caller might want to unschedule it early... + local delayed_action = function() + self:nextTick(action, unpack(va, 1, n)) + end + self:nextTick(delayed_action) + + return delayed_action end --[[ -- NOTE: This appears to work *nearly* just as well, but does sometimes go too fast (might depend on kernel HZ & NO_HZ settings?) diff --git a/plugins/autosuspend.koplugin/main.lua b/plugins/autosuspend.koplugin/main.lua index b4dcdd0c4..b73bf594a 100644 --- a/plugins/autosuspend.koplugin/main.lua +++ b/plugins/autosuspend.koplugin/main.lua @@ -37,6 +37,7 @@ local AutoSuspend = WidgetContainer:new{ task = nil, standby_task = nil, leave_standby_task = nil, + wrapped_leave_standby_task = nil, going_to_suspend = nil, } @@ -184,6 +185,7 @@ function AutoSuspend:onCloseWidget() self:_unschedule_standby() self.standby_task = nil + self.wrapped_leave_standby_task = nil self.leave_standby_task = nil end @@ -203,7 +205,13 @@ function AutoSuspend:_unschedule_standby() end -- Make sure we don't trigger a ghost LeaveStandby event... + if self.wrapped_leave_standby_task then + logger.dbg("AutoSuspend: unschedule leave standby task wrapper") + UIManager:unschedule(self.wrapped_leave_standby_task) + end + if self.leave_standby_task then + logger.dbg("AutoSuspend: unschedule leave standby task") UIManager:unschedule(self.leave_standby_task) end end @@ -330,6 +338,8 @@ end function AutoSuspend:onLeaveStandby() logger.dbg("AutoSuspend: onLeaveStandby") + -- If the Event got through, tickAfterNext did its thing, clear the reference to the initial nextTick wrapper... + self.wrapped_leave_standby_task = nil -- 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). @@ -594,7 +604,7 @@ function AutoSuspend:AllowStandbyHandler() -- (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: UIManager consumes scheduled tasks before input events, which is why we can't use nextTick. - UIManager:tickAfterNext(self.leave_standby_task) + self.wrapped_leave_standby_task = UIManager:tickAfterNext(self.leave_standby_task) end end