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
+37 -13

No files matched your search

+7 -1
View File
@@ -126,10 +126,16 @@ def test_close_interface_respects_timeout(monkeypatch):
class SlowInterface:
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)
try:
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):
+11 -7
View File
@@ -403,26 +403,30 @@ RSpec.describe "Federation crawl limits" 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") }
# 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
fed.hooks << lambda do |_entry|
if first
first = false
sleep 0.6
gate.pop(timeout: 5)
end
end
worker_pool = PotatoMesh::App::WorkerPool.new(size: 1, max_queue: 2, task_timeout: 0.3, name: "spec-fl6")
task = worker_pool.schedule { crawl(hostile) }
outcome = begin
task.wait(timeout: 5)
:completed
rescue PotatoMesh::App::WorkerPool::TaskTimeoutError
:timed_out
begin
# The pool's message: the wait's own deadline raises "task deadline
# exceeded", which is no task timeout ending the crawl (SPEC FL6).
expect { task.wait(timeout: 3) }.to raise_error(PotatoMesh::App::WorkerPool::TaskTimeoutError, "task exceeded timeout")
ensure
gate.close
worker_pool.shutdown(timeout: 1, force_kill_after: 1)
end
expect(outcome).to eq(:timed_out)
expect(fed.total).to eq(1)
end
+19 -5
View File
@@ -39,10 +39,17 @@ RSpec.describe PotatoMesh::App::WorkerPool do
end
it "fails tasks that exceed the configured timeout" do
gate = Queue.new
with_pool(task_timeout: 0.01) do |pool|
task = pool.schedule { sleep 0.05; :late }
expect { task.wait(timeout: 1) }.to raise_error(described_class::TaskTimeoutError)
# The job holds until the pool's task timeout interrupts it, however
# 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
ensure
gate.close
end
it "ignores invalid timeout values" do
@@ -130,13 +137,20 @@ RSpec.describe PotatoMesh::App::WorkerPool do
describe PotatoMesh::App::WorkerPool::Task do
it "raises a timeout when the job exceeds the provided deadline" do
gate = Queue.new
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
task.wait(timeout: 0.01)
end.to raise_error(PotatoMesh::App::WorkerPool::TaskTimeoutError)
expect(task.wait(timeout: 1)).to eq(:done)
end.to raise_error(PotatoMesh::App::WorkerPool::TaskTimeoutError, "task deadline exceeded")
gate << :open
expect(task.wait(timeout: 5)).to eq(:done)
end
ensure
gate.close
end
it "reports completion status" do