*ttl drivers: TTL branch dereferences delete() result without a nil check, killing the fiber
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 76/100
Research direction
Start with the TTL branches in queue/abstract/driver/fifottl.lua and utubettl.lua at the referenced lines, then read the delete implementation around fifottl.lua#L334-L342 and compare the handling in subqueuettl from #259. Verify the chosen fix for a task deleted between selection and deletion, including vinyl and MVCC cases, and ensure TTL processing continues instead of the fiber dying.
Written by the indexing model from the issue text.
Description
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 #L521
task = self.space.index.watch:min{ task_state }
if task ~= nil and task[i_status] == task_state then
if now >= task[i_next_event] then
task = 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()andget(); I could not reproduce it there. - vinyl (
fifottlsupportsengine = 'vinyl'): reads may yield on disk I/O, so a concurrentack()/delete()of the same expired task betweenmin()anddelete()hits it. - memtx with MVCC:
delete()may also raiseTransaction has been aborted by conflictwith 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])
if task ~= nil then
self: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.
- Dominant language
- Lua
- Stars
- 244
- Forks
- 56
- Avg merge
- 6d 8h
- Merged PRs (30d)
- 1
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from tarantool/queue
-
documentation good first issue
Difficulty 1/5 Under an hour Newbie friendliness 85/100
-
Difficulty 4/5 3-5 days Newbie friendliness 48/100
-
Difficulty 4/5 3-5 days Newbie friendliness 55/100
-
1sp bug teamE
Difficulty 4/5 3-5 days Newbie friendliness 25/100
-
bug
Difficulty 4/5 3-5 days Newbie friendliness 35/100
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
mailcow/mailcow-dockerized#7480 ·
-
documentation feature-request mini.completion
Difficulty 1/5 Under an hour Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
fluent/fluent-bit-docs#2727 ·
-
OS:Windows
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
enhancement needs-triage
Difficulty 2/5 1-3 hours Newbie friendliness 88/100