*ttl drivers: a dead TTL fiber cannot be restarted and wedges the queue in ENDING on the next RO switch
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
Research direction
Start with queue/abstract/driver/fifottl.lua lines 164-167 and 415-427, then compare the buffered-channel behavior in utubettl. Run the reproduction in the issue and inspect queue_state handling during RO/RW switches. Done means a failed TTL iteration does not permanently disable processing or leave the queue stuck in ENDING, and start/stop remain safe.
Written by the indexing model from the issue text.
Description
Environment: queue master (07fd732), Tarantool 3.8.0 (also the same code in 1.5.0). memtx.
Related: #238 asks for an API to restart the TTL fiber. This issue is about what happens today when the fiber dies: it cannot be restarted, and for fifottl/limfifottl the queue state machine gets stuck in ENDING on the next RO switch, so the queue never comes back without an instance restart.
Summary
Any error inside a TTL iteration (a user on_task_change callback raising, an MVCC conflict, a vinyl read error, the nil dereference from the TTL branch, ...) makes the fiber return 1 (fifottl.lua#L164-L167) while self.fiber keeps pointing at the dead fiber. Consequences:
method.start()is a no-op becauseself.fiberis not nil (fifottl.lua#L415-L420). TTL/TTR/delay processing for the tube is dead.- On the next RO switch the state machine calls
stop(), which doesself.sync_chan:put(true)(fifottl.lua#L427). The channel is unbuffered and the only reader is dead (same root cause as #262), so thequeue_statefiber blocks forever. The queue stays inENDING, andput/takekeep failing withqueue is in ENDING stateeven after the instance is RW again.
For utubettl (buffered fiber.channel(1)) point 2 does not hang, but point 1 still holds until an RO->RW cycle.
Repro
local fiber = require('fiber')
box.cfg{}
local queue = require('queue')
local fired = false
local tube = queue.create_tube('t', 'fifottl', {
on_task_change = function(task, stat)
if stat == 'ttl' and not fired then
fired = true
error('user callback failed once')
end
end,
})
tube:put('a', {ttl = 0.1})
fiber.sleep(0.3)
print('1. fiber after one callback error:', tube.raw.fiber:status())
local b = tube:put('b', {ttl = 0.1})
fiber.sleep(0.3)
print('2. expired task still in space:', tube.raw.space:get(b[1]) ~= nil)
tube.raw:start()
print('3. fiber after start():', tube.raw.fiber:status())
box.cfg{read_only = true}
fiber.sleep(1)
print('4. queue.state() after RO switch:', queue.state())
box.cfg{read_only = false}
fiber.sleep(1)
print('5. queue.state() after RW switch:', queue.state())
print('6. put() after RW switch:', pcall(tube.put, tube, 'c', {ttl = 0.1}))
Output on master:
1. fiber after one callback error: dead
2. expired task still in space: true
3. fiber after start(): dead
4. queue.state() after RO switch: ENDING
5. queue.state() after RW switch: ENDING
6. put() after RW switch: true nil -- put() returns nil, log: "put: queue is in ENDING state"
Expected
A failed iteration should not permanently kill TTL processing: either the fiber survives the error (log it and continue / restart with backoff), or at least self.fiber is cleared on exit so start() can create a new one, and stop() must never block on a channel nobody reads.
Suggested fix
- Clear
self.fiberin a guaranteed cleanup path when the fiber exits (checking it still points to the current fiber). - Make
stop()tolerant of a dead fiber (checkself.fiber:status()or use a buffered channel /fiber:cancel()). - Optionally restart the fiber after an error with a backoff instead of
return 1; user callback errors could be pcall-ed inabstract.luaso a user bug cannot take the driver down.
- 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 3/5 1-2 days Newbie friendliness 76/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
-
5.1
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
fluent/fluent-bit-docs#2727 · 4 comments · 1 assignee ·
-
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