You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
*ttl drivers: TTL branch dereferences delete() result without a nil check, killing the fiber #264
Environment: queue master (07fd732), also 1.5.0. Found by code review while porting the subqueuettl driver (#259), which copies the same loop.
Summary
In the TTL branch of the fiber iteration the result of delete() is dereferenced unconditionally:
-- fifottl.lua#L113-L117, same in utubettl.lua#L243 and #L521task=self.space.index.watch:min{ task_state }
iftask~=nilandtask[i_status] ==task_statethenifnow>=task[i_next_event] thentask=self:delete(task[i_id]):transform(2, 1, state.DONE)
self:on_task_change(task, 'ttl')
but method.delete() returns nil when the task is already gone (fifottl.lua#L334-L342), so a task that disappears between min() and delete() produces attempt to index a nil value, the fiber dies, and TTL processing stops for good (see #263). The delayed and TTR branches tolerate a nil from update() because abstract.lua ignores on_task_change(nil, ...); only the TTL branch does not.
When it is reachable
memtx without MVCC: not reachable, there is no yield between min() and get(); I could not reproduce it there.
vinyl (fifottl supports engine = 'vinyl'): reads may yield on disk I/O, so a concurrent ack()/delete() of the same expired task between min() and delete() hits it.
memtx with MVCC:delete() may also raise Transaction has been aborted by conflict with a concurrent write on the same task, which kills the fiber the same way.
Suggested fix
Either guard the result:
task=self:delete(task[i_id])
iftask~=nilthenself:on_task_change(task, 'ttl')
end
or do min() + delete() inside one box.atomic() so the selection and the delete see the same state. The subqueuettl driver in #259 does the former for the fork we run.
Activity