rspec / rspec-mocks

RSpec's 'test double' framework, with support for stubbing and mocking
https://rspec.info
MIT License
1.16k stars 359 forks source link

stub_chain thread safety #1023

Open mrbrdo opened 8 years ago

mrbrdo commented 8 years ago

I think there might be a thread-safety issue with stub_chain.

I am occasionally getting this error:

gems/rspec-mocks-2.99.4/lib/rspec/mocks/any_instance/stub_chain.rb:17:in `<<': Detected invalid array contents due to unsynchronized modifications with concurrent users (ConcurrencyError)

I am testing code that is threaded (JRuby). The tests themselves are not threaded (i.e. not using something like parallel_tests).

The method being called which produced this error was stubbed in this way:

    c = SomeClass.any_instance
    c.stub(:m1).and_return(r1)
    c.stub(:m2).and_return(r2)

The issue doesn't occur very often and so is hard to reproduce for me.

Thanks

JonRowe commented 8 years ago

RSpec isn't entirely thread safe but it is something we're working towards, 3.x contains improvements towards thread safe mocks (in particular fixing a deadlock issue) but those fixes won't be back ported to 2.99 (as we're not maintaining 2.x). Can you try this on 3.x and reopen if it reoccurs?

mrbrdo commented 8 years ago

I'm still getting the error on 3.x, this is how I stubbed: allow_any_instance_of(SomeClass).to receive(:mobile?) { false }

Backtrace:

gems/rspec-mocks-3.4.1/lib/rspec/mocks/order_group.rb:17:in `<<': Detected invalid array contents due to unsynchronized modifications with concurrent users (ConcurrencyError)
  from gems/rspec-mocks-3.4.1/lib/rspec/mocks/order_group.rb:17:in `invoked'
  from gems/rspec-mocks-3.4.1/lib/rspec/mocks/proxy.rb:163:in `record_message_received'
  from gems/rspec-mocks-3.4.1/lib/rspec/mocks/proxy.rb:169:in `message_received'
  from gems/rspec-mocks-3.4.1/lib/rspec/mocks/proxy.rb:321:in `message_received'
  from gems/rspec-mocks-3.4.1/lib/rspec/mocks/method_double.rb:77:in `proxy_method_invoked'
  from gems/rspec-mocks-3.4.1/lib/rspec/mocks/method_double.rb:64:in `mobile?'
  from gems/rspec-mocks-3.4.1/lib/rspec/mocks/any_instance/recorder.rb:236:in `mobile?'

Another similar backtrace from another case:

gems/rspec-mocks-3.4.1/lib/rspec/mocks/order_group.rb:17:in `<<': Detected invalid array contents due to unsynchronized modifications with concurrent users (ConcurrencyError)
  from gems/rspec-mocks-3.4.1/lib/rspec/mocks/order_group.rb:17:in `invoked'
  from gems/rspec-mocks-3.4.1/lib/rspec/mocks/proxy.rb:163:in `record_message_received'
  from gems/rspec-mocks-3.4.1/lib/rspec/mocks/proxy.rb:169:in `message_received'
  from gems/rspec-mocks-3.4.1/lib/rspec/mocks/proxy.rb:321:in `message_received'
  from gems/rspec-mocks-3.4.1/lib/rspec/mocks/method_double.rb:77:in `proxy_method_invoked'
  from gems/rspec-mocks-3.4.1/lib/rspec/mocks/method_double.rb:64:in `new'

I can't reopen this issue myself it seems.

myronmarston commented 8 years ago

Reopened.

fables-tales commented 7 years ago

@mrbrdo could you provide us a full spec file with a sample class and sample spec that produces this problem? I tried to reproduce this locally and couldn't.

yaauie commented 6 years ago

Here is a self-contained example that consistently triggers on my machine:

# encoding: utf-8

require 'rspec'
require 'rspec/mocks'
require 'thread'

describe 'rspec-mocks validation thread-safety' do
  it 'validates receive counts' do
    parallelism = 10
    repetition = 10

    foo = double('Foo').as_null_object

    # set an expectation
    expect(foo).to receive(:bar).exactly(parallelism * repetition).times

    # fulfil that expectation with some parallelism
    parallelism.times.map do |outer_idx|
      Thread.new do
        repetition.times do |inner_idx|
          foo.bar
        end
      end
    end.map(&:join)
  end
end

Result:

╭─{ yaauie@castrovel:~/src/scratch/rspec-thread-safety }
╰─● rspec spec/threadsafety_spec.rb

rspec-mocks validation thread-safety
  validates receive counts (FAILED - 1)

Failures:

  1) rspec-mocks validation thread-safety validates receive counts
     Failure/Error: foo.bar

     ConcurrencyError:
       Detected invalid array contents due to unsynchronized modifications with concurrent users
     # ./spec/threadsafety_spec.rb:21:in `block in (root)'
     # ./spec/threadsafety_spec.rb:20:in `block in (root)'

Finished in 0.08449 seconds (files took 0.69396 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/threadsafety_spec.rb:8 # rspec-mocks validation thread-safety validates receive counts

rspec spec/threadsafety_spec.rb  12.25s user 0.43s system 381% cpu 3.320 total
[error: 1]

RSpec and Plugin Versions:

╭─{ yaauie@castrovel:~/src/scratch/rspec-thread-safety }
╰─● rspec --version
RSpec 3.8
  - rspec-core 3.8.0
  - rspec-expectations 3.8.1
  - rspec-mocks 3.8.0
  - rspec-support 3.8.0
rspec --version  9.73s user 0.40s system 341% cpu 2.969 total
[success]

╭─{ yaauie@castrovel:~/src/scratch/rspec-thread-safety }
╰─● ruby --version
jruby 9.1.12.0 (2.3.3) 2017-06-15 33c6439 Java HotSpot(TM) 64-Bit Server VM 25.152-b16 on 1.8.0_152-b16 +jit [darwin-x86_64]
[success]
senid231 commented 5 years ago

What's the status of this issue?

JonRowe commented 5 years ago

No change