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).
This commit is contained in:
NiLuJe
2022-05-22 23:51:17 +02:00
parent 6694b8f3b7
commit 31a2c3ef2b
2 changed files with 22 additions and 2 deletions

View File

@@ -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?)

View File

@@ -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