async icon indicating copy to clipboard operation
async copied to clipboard

Rspec spec does not terminate

Open klobuczek opened this issue 2 years ago • 1 comments

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.

klobuczek avatar Jun 08 '22 13:06 klobuczek

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.

ioquatix avatar Jun 09 '22 03:06 ioquatix

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.

ioquatix avatar Oct 31 '22 04:10 ioquatix