web,data: timeout specs hold their jobs on gates, not fixed sleeps (#1050)

This commit is contained in:
Afri authored and GitHub committed 2026-10-16 09:07:27 +02:00
1 parent a8ddaf1d3c
commit b7ff2c8466
3 files changed
+42 -18

No files matched your search

+9 -3
View File
@@ -126,10 +126,16 @@ def test_close_interface_respects_timeout(monkeypatch):
class SlowInterface: class SlowInterface:
def close(self): def close(self):
blocker.wait(timeout=0.1) # Held until the test releases it, so the close still runs however
# late the 0.01 s join returns. After 5 s it ends anyway, so a
# _close_interface that waits for the close fails instead of hanging.
blocker.wait(timeout=5.0)
daemon._close_interface(SlowInterface()) try:
assert any("timeout_seconds" in entry for entry in log_calls) daemon._close_interface(SlowInterface())
assert any("timeout_seconds" in entry for entry in log_calls)
finally:
blocker.set()
def test_close_interface_immediate_path(monkeypatch): def test_close_interface_immediate_path(monkeypatch):
+14 -10
View File
@@ -403,26 +403,30 @@ RSpec.describe "Federation crawl limits" do
it "lets the worker pool's task timeout end a crawl" do it "lets the worker pool's task timeout end a crawl" do
fed.lists[hostile] = %w[p1 p2 p3 p4].map { |name| peers.record("#{name}.fed.invalid") } fed.lists[hostile] = %w[p1 p2 p3 p4].map { |name| peers.record("#{name}.fed.invalid") }
# The first request holds until the pool's task timeout interrupts it,
# however late that fires, so the crawl cannot go on by itself. After 5 s
# it ends anyway, so a pool and a wait that both ignore their timeouts
# fail, not hang.
gate = Queue.new
first = true first = true
fed.hooks << lambda do |_entry| fed.hooks << lambda do |_entry|
if first if first
first = false first = false
sleep 0.6 gate.pop(timeout: 5)
end end
end end
worker_pool = PotatoMesh::App::WorkerPool.new(size: 1, max_queue: 2, task_timeout: 0.3, name: "spec-fl6") worker_pool = PotatoMesh::App::WorkerPool.new(size: 1, max_queue: 2, task_timeout: 0.3, name: "spec-fl6")
task = worker_pool.schedule { crawl(hostile) } task = worker_pool.schedule { crawl(hostile) }
outcome = begin begin
task.wait(timeout: 5) # The pool's message: the wait's own deadline raises "task deadline
:completed # exceeded", which is no task timeout ending the crawl (SPEC FL6).
rescue PotatoMesh::App::WorkerPool::TaskTimeoutError expect { task.wait(timeout: 3) }.to raise_error(PotatoMesh::App::WorkerPool::TaskTimeoutError, "task exceeded timeout")
:timed_out ensure
ensure gate.close
worker_pool.shutdown(timeout: 1, force_kill_after: 1) worker_pool.shutdown(timeout: 1, force_kill_after: 1)
end end
expect(outcome).to eq(:timed_out)
expect(fed.total).to eq(1) expect(fed.total).to eq(1)
end end
+19 -5
View File
@@ -39,10 +39,17 @@ RSpec.describe PotatoMesh::App::WorkerPool do
end end
it "fails tasks that exceed the configured timeout" do it "fails tasks that exceed the configured timeout" do
gate = Queue.new
with_pool(task_timeout: 0.01) do |pool| with_pool(task_timeout: 0.01) do |pool|
task = pool.schedule { sleep 0.05; :late } # The job holds until the pool's task timeout interrupts it, however
expect { task.wait(timeout: 1) }.to raise_error(described_class::TaskTimeoutError) # late that fires. The message is the pool's: the wait's own deadline
# raises "task deadline exceeded". After 5 s the job ends anyway, so a
# pool and a wait that both ignore their timeouts fail, not hang.
task = pool.schedule { gate.pop(timeout: 5); :late }
expect { task.wait(timeout: 3) }.to raise_error(described_class::TaskTimeoutError, "task exceeded timeout")
end end
ensure
gate.close
end end
it "ignores invalid timeout values" do it "ignores invalid timeout values" do
@@ -130,13 +137,20 @@ RSpec.describe PotatoMesh::App::WorkerPool do
describe PotatoMesh::App::WorkerPool::Task do describe PotatoMesh::App::WorkerPool::Task do
it "raises a timeout when the job exceeds the provided deadline" do it "raises a timeout when the job exceeds the provided deadline" do
gate = Queue.new
with_pool do |pool| with_pool do |pool|
task = pool.schedule { sleep 0.1; :done } # The job holds until the gate opens, so the first wait can only end
# at its own deadline, however late it wakes. After 5 s the job ends
# anyway, so a wait that ignores its deadline fails instead of hanging.
task = pool.schedule { gate.pop(timeout: 5); :done }
expect do expect do
task.wait(timeout: 0.01) task.wait(timeout: 0.01)
end.to raise_error(PotatoMesh::App::WorkerPool::TaskTimeoutError) end.to raise_error(PotatoMesh::App::WorkerPool::TaskTimeoutError, "task deadline exceeded")
expect(task.wait(timeout: 1)).to eq(:done) gate << :open
expect(task.wait(timeout: 5)).to eq(:done)
end end
ensure
gate.close
end end
it "reports completion status" do it "reports completion status" do