async
async copied to clipboard
Rspec spec does not terminate
I cannot figure out why the spec in the 2nd context does not terminate:
RSpec.describe Spec do
context 'terminates' do
let(:s) { Sync { 1 } }
it 'eq' do
expect(s).to eq 1
end
end
context 'does not terminate' do
let(:s) { Sync { param } }
let(:param) { 1 }
it 'eq' do
expect(s).to eq 1
end
end
end
Using ruby 3.1.2 and async 2.0.2.
IIRC, this is because let
blocks are "thread safe" and have an implicit shared mutex around every request. It's supposed to be reentrant but because Sync { param }
happens on a different fiber, it's effectively a deadlock, i.e. it's not that different from:
m = Thread::Monitor.new # recursive mutex
param = lambda{m.synchronize{1}}
s = lambda{m.synchronise{Thread.new{param.call}.value}}
s.call
To resolve s
it locks the mutex for the current (main) thread. Then it creates a new thread (Sync
block creates a new fiber) and then tries to lock the mutex again and this deadlocks.
The real issue here is whether RSpec should be "thread safe" in this way. I personally think it's wrong.
I got pretty frustrated with RSpec for this reason as well as others, and implemented https://github.com/ioquatix/sus which explicitly avoids this problem. I don't have a good answer and can't fix RSpec, maybe they will avoid having a mutex around their let variables.