Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Getting deadlock issues when mocking in specs (3.3.0) #1994

Closed
ahorner opened this issue Jun 12, 2015 · 18 comments
Closed

Getting deadlock issues when mocking in specs (3.3.0) #1994

ahorner opened this issue Jun 12, 2015 · 18 comments

Comments

@ahorner
Copy link

ahorner commented Jun 12, 2015

After upgrading to 3.3.0, I started seeing a handful of consistent spec failures.

  • Installed through rspec-rails version 3.3.0
  • Rails version 4.2.1
  • Using infer_spec_type_from_file_location!

The failing specs have the following in common: They are in the spec/policies directory, and use the expect_any_instance_of or allow_any_instance_of methods (both seem to run into the same issue).

The failures manifest as such:

8) EventPolicy#overbooked? when any Assignment is overbooked should be overbooked
     Failure/Error: it { is_expected.to be_overbooked }
     ThreadError:
       deadlock; recursive locking
     # ./app/policies/event_policy.rb:29:in `block in overbooked?'
     # ./app/policies/event_policy.rb:28:in `any?'
     # ./app/policies/event_policy.rb:28:in `overbooked?'
     # ./spec/policies/event_policy_spec.rb:35:in `block (4 levels) in <top (required)>'

  9) BookingPolicy#ready_for_approval? when there are events and they are ready for approval should be ready for approval
     Failure/Error: it { is_expected.to be_ready_for_approval }
     ThreadError:
       deadlock; recursive locking
     # ./app/policies/booking_policy.rb:8:in `block in ready_for_approval?'
     # ./app/policies/booking_policy.rb:8:in `each'
     # ./app/policies/booking_policy.rb:8:in `all?'
     # ./app/policies/booking_policy.rb:8:in `ready_for_approval?'
     # ./spec/policies/booking_policy_spec.rb:23:in `block (5 levels) in <top (required)>'
@myronmarston
Copy link
Member

RSpec doesn't spin up any threads so I'd be surprised if it's causing this...although an internal change may have allowed a deadlock problem to surface. Is there any way you can provide a reproducible example of this?

If you can't provide that, can you run your specs with -b to get the full backtrace and past that here?

@ahorner
Copy link
Author

ahorner commented Jun 13, 2015

Backtraces here; looks like it's an issue in rspec-mocks.

Failures:

  1) BookingPolicy#ready_for_approval? when there are events but they are not ready for approval should not be ready for approval
     Failure/Error: it { is_expected.to_not be_ready_for_approval }
     ThreadError:
       deadlock; recursive locking
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:106:in `synchronize'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:106:in `proxy_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/test_double.rb:111:in `__mock_proxy'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/test_double.rb:29:in `null_object?'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_double.rb:8:in `respond_to?'
     # /Users/ahorner/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/delegate.rb:105:in `respond_to_missing?'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-rails-3.3.0/lib/rspec/rails/active_record.rb:12:in `respond_to?'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-rails-3.3.0/lib/rspec/rails/active_record.rb:12:in `block (2 levels) in initialize_activerecord_configuration'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:103:in `call'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:103:in `block in initialize'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:102:in `each'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:102:in `initialize'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:160:in `new'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:160:in `proxy_not_found_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:108:in `block (2 levels) in proxy_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:108:in `fetch'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:108:in `block in proxy_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:106:in `synchronize'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:106:in `proxy_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/any_instance/recorder.rb:104:in `playback!'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/any_instance/recorder.rb:231:in `block in observe!'
     # ./app/policies/booking_policy.rb:8:in `block in ready_for_approval?'
     # ./app/policies/booking_policy.rb:8:in `each'
     # ./app/policies/booking_policy.rb:8:in `all?'
     # ./app/policies/booking_policy.rb:8:in `ready_for_approval?'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-expectations-3.3.0/lib/rspec/matchers/built_in/be.rb:236:in `predicate_matches?'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-expectations-3.3.0/lib/rspec/matchers/built_in/be.rb:194:in `does_not_match?'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-expectations-3.3.0/lib/rspec/expectations/handler.rb:78:in `does_not_match?'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-expectations-3.3.0/lib/rspec/expectations/handler.rb:72:in `block in handle_matcher'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-expectations-3.3.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-expectations-3.3.0/lib/rspec/expectations/handler.rb:70:in `handle_matcher'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-expectations-3.3.0/lib/rspec/expectations/expectation_target.rb:67:in `not_to'
     # ./spec/policies/booking_policy_spec.rb:30:in `block (5 levels) in <top (required)>'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:206:in `instance_exec'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:206:in `block in run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:430:in `block in with_around_and_singleton_context_hooks'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:388:in `block in with_around_example_hooks'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:478:in `block in run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:618:in `block in run_around_example_hooks_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-rails-3.3.0/lib/rspec/rails/adapters.rb:127:in `block (2 levels) in <module:MinitestLifecycleAdapter>'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:378:in `instance_exec'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:378:in `instance_exec'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:389:in `execute_with'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:620:in `block (2 levels) in run_around_example_hooks_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:621:in `run_around_example_hooks_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:478:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:388:in `with_around_example_hooks'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:430:in `with_around_and_singleton_context_hooks'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:203:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:559:in `block in run_examples'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:555:in `map'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:555:in `run_examples'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:521:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `block in run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `map'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `block in run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `map'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `block in run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `map'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:115:in `block (3 levels) in run_specs'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:115:in `map'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:115:in `block (2 levels) in run_specs'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/configuration.rb:1627:in `with_suite_hooks'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:114:in `block in run_specs'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/reporter.rb:77:in `report'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:113:in `run_specs'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:89:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:73:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:41:in `invoke'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/exe/rspec:4:in `<top (required)>'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/bin/rspec:23:in `load'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/bin/rspec:23:in `<main>'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/bin/ruby_executable_hooks:15:in `eval'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/bin/ruby_executable_hooks:15:in `<main>'

  2) BookingPolicy#ready_for_approval? when there are events and they are ready for approval should be ready for approval
     Failure/Error: it { is_expected.to be_ready_for_approval }
     ThreadError:
       deadlock; recursive locking
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:106:in `synchronize'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:106:in `proxy_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/test_double.rb:111:in `__mock_proxy'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/test_double.rb:29:in `null_object?'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_double.rb:8:in `respond_to?'
     # /Users/ahorner/.rvm/rubies/ruby-2.2.2/lib/ruby/2.2.0/delegate.rb:105:in `respond_to_missing?'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-rails-3.3.0/lib/rspec/rails/active_record.rb:12:in `respond_to?'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-rails-3.3.0/lib/rspec/rails/active_record.rb:12:in `block (2 levels) in initialize_activerecord_configuration'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:103:in `call'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:103:in `block in initialize'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:102:in `each'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/verifying_proxy.rb:102:in `initialize'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:160:in `new'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:160:in `proxy_not_found_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:108:in `block (2 levels) in proxy_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:108:in `fetch'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:108:in `block in proxy_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:106:in `synchronize'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/space.rb:106:in `proxy_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/any_instance/recorder.rb:104:in `playback!'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-mocks-3.3.0/lib/rspec/mocks/any_instance/recorder.rb:231:in `block in observe!'
     # ./app/policies/booking_policy.rb:8:in `block in ready_for_approval?'
     # ./app/policies/booking_policy.rb:8:in `each'
     # ./app/policies/booking_policy.rb:8:in `all?'
     # ./app/policies/booking_policy.rb:8:in `ready_for_approval?'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-expectations-3.3.0/lib/rspec/matchers/built_in/be.rb:236:in `predicate_matches?'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-expectations-3.3.0/lib/rspec/matchers/built_in/be.rb:188:in `matches?'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-expectations-3.3.0/lib/rspec/expectations/handler.rb:50:in `block in handle_matcher'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-expectations-3.3.0/lib/rspec/expectations/handler.rb:27:in `with_matcher'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-expectations-3.3.0/lib/rspec/expectations/handler.rb:48:in `handle_matcher'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-expectations-3.3.0/lib/rspec/expectations/expectation_target.rb:54:in `to'
     # ./spec/policies/booking_policy_spec.rb:23:in `block (5 levels) in <top (required)>'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:206:in `instance_exec'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:206:in `block in run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:430:in `block in with_around_and_singleton_context_hooks'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:388:in `block in with_around_example_hooks'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:478:in `block in run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:618:in `block in run_around_example_hooks_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-rails-3.3.0/lib/rspec/rails/adapters.rb:127:in `block (2 levels) in <module:MinitestLifecycleAdapter>'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:378:in `instance_exec'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:378:in `instance_exec'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:389:in `execute_with'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:620:in `block (2 levels) in run_around_example_hooks_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:273:in `call'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:621:in `run_around_example_hooks_for'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/hooks.rb:478:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:388:in `with_around_example_hooks'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:430:in `with_around_and_singleton_context_hooks'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example.rb:203:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:559:in `block in run_examples'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:555:in `map'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:555:in `run_examples'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:521:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `block in run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `map'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `block in run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `map'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `block in run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `map'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/example_group.rb:522:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:115:in `block (3 levels) in run_specs'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:115:in `map'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:115:in `block (2 levels) in run_specs'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/configuration.rb:1627:in `with_suite_hooks'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:114:in `block in run_specs'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/reporter.rb:77:in `report'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:113:in `run_specs'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:89:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:73:in `run'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/lib/rspec/core/runner.rb:41:in `invoke'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/gems/rspec-core-3.3.0/exe/rspec:4:in `<top (required)>'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/bin/rspec:23:in `load'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/bin/rspec:23:in `<main>'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/bin/ruby_executable_hooks:15:in `eval'
     # /Users/ahorner/.rvm/gems/ruby-2.2.2@test-app/bin/ruby_executable_hooks:15:in `<main>'

Finished in 0.07235 seconds (files took 6.5 seconds to load)
3 examples, 2 failures

@ahorner
Copy link
Author

ahorner commented Jun 13, 2015

The below should be sufficient to reproduce the error:

class EventPolicy < SimpleDelegator; end

# Public: A Policy object for interacting with Bookings.
class BookingPolicy < SimpleDelegator

  # Public: Is this Booking ready for approval?
  #
  # Returns true or false.
  def ready_for_approval?
    events.any? && events.all? { |e| EventPolicy.new(e).ready_for_approval? }
  end

end
RSpec.describe BookingPolicy do

  let(:booking) { instance_double(Booking) }
  let(:policy) { BookingPolicy.new(booking) }
  subject { policy }

  describe "#ready_for_approval?" do
    context "when there are no events" do
      before { allow(booking).to receive(:events).and_return([]) }
      it { is_expected.to_not be_ready_for_approval }
    end

    context "when there are events" do
      let(:event) { instance_double(Event) }
      before { allow(booking).to receive(:events).and_return([event]) }

      context "and they are ready for approval" do
        before do
          allow_any_instance_of(EventPolicy).to receive(:ready_for_approval?).and_return(true)
        end
        it { is_expected.to be_ready_for_approval }
      end

      context "but they are not ready for approval" do
        before do
          allow_any_instance_of(EventPolicy).to receive(:ready_for_approval?).and_return(false)
        end
        it { is_expected.to_not be_ready_for_approval }
      end
    end
  end

end

@myronmarston
Copy link
Member

Thanks. I've tried your snippet and I get NameError: uninitialized constant Booking from the instance_double(Booking) line (as you'd expect). I tried changing that to instance_double("Booking") and then I got NameError: uninitialized constant Event from instance_double(Event), so I changed that to instance_double("Event"), but then the spec passes for me.

So no, that snippet isn't sufficient to reproduce the error :(. Can you work on making it runnable in isolation? A common technique used to report bugs like this is to create a tiny little gist that I can clone and run, but pasting code in-line like you did is totally fine as well, as long as it does actually work to repro when run in isolation.

@ahorner
Copy link
Author

ahorner commented Jun 15, 2015

After a ton of fidgeting, I was able to figure out how to recreate the issue. See: https://github.com/ahorner/deadlock-sample

I documented my findings in the README for that repo, but in summary: it seems like something in rspec-rails is triggering an underlying bug in rspec-mocks. I don't have enough bandwidth or context to dig into it any further; hopefully this gives you enough information to solve the issue.

@cupakromer
Copy link
Member

Thanks for the repo @ahorner ❤️ I haven't dug too deeply, but I have a hunch this is related to rspec-rails using RSpec::Mocks.configuration.when_declaring_verifying_double. In the provided block, rspec-rails calls the model class's define_attribute_methods; which is currently defined as:

# Generates all the attribute related methods for columns in the database
# accessors, mutators and query methods.
def define_attribute_methods # :nodoc:
  return false if @attribute_methods_generated
  # Use a mutex; we don't want two threads simultaneously trying to define
  # attribute methods.
  generated_attribute_methods.synchronize do
    return false if @attribute_methods_generated
    superclass.define_attribute_methods unless self == base_class
    super(attribute_names)
    @attribute_methods_generated = true
  end
  true
end

I'll have to dig more to understand why the locks are deadlocking in this manner.

@cupakromer
Copy link
Member

I did confirm that commenting out the line in rspec-rails which calls define_attribute_methods in the when_declaring_verifying_double block resolves the deadlock. Interestingly, my attempt to preload the attribute methods by calling Child.define_attribute_methods and Parent.define_attribute_methods outside of the RSpec.describe block did not resolve the deadlock.

@cupakromer
Copy link
Member

I've ruled out rspec-rails being at fault here. This seems to be an issue with verify_partial_doubles being enable, and any instance being set on a delegator who's source is a mock.

Here's a new simplified reproduction scenario:

RSpec.configure do |config|
  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end
end

class ChildPoll < SimpleDelegator
  def asleep?
    false
  end
end

RSpec.describe "Deadlocks with SimpleDelegator and mock instances" do
  RSpec::Mocks.configuration.when_declaring_verifying_double do |ref|
    ref.target.respond_to?(:any_method)
  end

  around do |ex|
    orig_callbacks = RSpec::Mocks.configuration.verifying_double_callbacks.dup
    ex.call
    RSpec::Mocks.configuration.verifying_double_callbacks.replace orig_callbacks
  end

  it "when the verify block causes delegation to a mock target a deadlock occurs" do
    child = double
    allow_any_instance_of(ChildPoll).to receive(:asleep?).and_return(true)
    expect(ChildPoll.new(child)).to be_asleep
  end

  it "when the verify block causes delegation to a verifying mock target a deadlock occurs" do
    child_klass = Class.new
    child = instance_double(child_klass)
    allow_any_instance_of(ChildPoll).to receive(:asleep?).and_return(true)
    expect(ChildPoll.new(child)).to be_asleep
  end

  it "when the verify block does not forward to the delegate source no deadlock occurs" do
    RSpec::Mocks.configuration.verifying_double_callbacks.clear
    RSpec::Mocks.configuration.when_declaring_verifying_double do |possible_model|
      # This does not access the simple delegator source object
      possible_model.target.class
    end

    child = double
    allow_any_instance_of(ChildPoll).to receive(:asleep?).and_return(true)
    expect(ChildPoll.new(child)).to be_asleep
  end

  it "passing a non-mock to simple delegator does not cause a deadlock" do
    child = double
    allow_any_instance_of(ChildPoll).to receive(:asleep?).and_return(true)
    expect(ChildPoll.new(child)).to be_asleep
  end
end

@cupakromer
Copy link
Member

Though I have no idea what the actual root cause is at this time.

@cupakromer
Copy link
Member

This appears to not be specific to any instance as the following spec also causes the deadlock in the above context:

  it "when the verify block causes delegation to a mock target a deadlock occurs" do
    child = double
    poll = ChildPoll.new(child)
    allow(poll).to receive(:asleep?).and_return(true)
    # Note we do not need to do anything else the `allow` call trips the deadlock
  end

It seems consistent so far that we need:

  • a mock as the target of a delegator
  • we then stub the delegator
  • we force the delegator to work with it's mock source in a verify double callback block

For example, the following spec passes without deadlock:

  it "delegator to a mock by itself does not cause a deadlock" do
    RSpec::Mocks.configuration.verifying_double_callbacks.clear
    child = double

    allow_any_instance_of(ChildPoll).to receive(:asleep?).and_return(true)
    expect(ChildPoll.new(child)).to be_asleep

    poll = ChildPoll.new(child)
    allow(poll).to receive(:asleep?).and_return(true)
    expect(poll).to be_asleep
  end

@myronmarston
Copy link
Member

This is all really useful info. I plan to look into this later tonight if there's time or in the next couple days if there's not.

@myronmarston
Copy link
Member

I see what's going on.

We protect proxy creation with a mutex. In #940 we started invoking the verifying double callback that is used by rspec-rails from the initialize method of VerifyingPartialDoubleProxy. When that happens we are still in the synchronize block of the mutex. Any code can be registered to be invoked by the callback, including code that triggers the access.creation of a proxy again. When you use allow_any_instance_of with a delegator wrapping another double, this happens. Ruby's Mutex is not re-entrant and so when you try to re-enter a mutex.synchronize block when the same thread is already in that block higher up in the callstack, ruby raises the deadlock error.

I see a few ways we can go about fixing this:

  • Switch away form a Mutex to something re-entrant. Monitor from stdlib is re-entrant, although it's slower, includes a bunch of functionality we don't need and would bring in another piece of stdlib (which we don't usually want to do). A better solution would be to move https://github.com/rspec/rspec-core/blob/v3.3.0/lib/rspec/core/reentrant_mutex.rb to rspec-support (for 3.4) and port it to rspec-mocks (for 3.3) and use that.
  • Move where we invoke the verifying double callbacks, out of VerifyingPartialDoubleProxy#initialize and to another place that is outside of the mutex.synchronize block.
  • Fix the block rspec-rails registers as the callback so that it can no longer trigger further proxy access. The problem is that target.respond_to? can do just about anything since target can implement respond_to? however it wants. A better solution might be to change it to an ActiveRecord::Base === target check so that we first confirm that it's an AR subclass. There's no way that can cause another proxy access unless people are stubbing ActiveRecord::Base.=== which would be pretty bad to do (and could cause all sorts of other bugs in rails, so it's probably something the rails team would say is unsupported).

There's nothing mutually exclusive about these ideas. We may want to do multiple (or all) of them.

@myronmarston
Copy link
Member

rspec-rails 3.3.2 has been released with a fix.

@JonRowe
Copy link
Member

JonRowe commented Jul 2, 2015

For future reference I've done two of @myronmarston's suggestions here, but I'm not sure how feasible the last one is, if anyone else fancies a try have at it!

@myronmarston
Copy link
Member

Closing.

@myronmarston
Copy link
Member

Thanks, @JonRowe!

@ml
Copy link

ml commented Sep 8, 2015

I just encountered this error, updated rspec, but no luck. Somehow (I didn't have time for more digging) rspec and Draper don't like each other. I kept getting deadlock errors from


     # /Users/maciej/.rvm/gems/ruby-2.1.7@jobartis/gems/draper-2.1.0/lib/draper/automatic_delegation.rb:21:in `delegatable?'
     # /Users/maciej/.rvm/gems/ruby-2.1.7@jobartis/gems/draper-2.1.0/lib/draper/automatic_delegation.rb:7:in `method_missing'
     # ./spec/decorators/candidate_decorator_spec.rb:104:in `block (3 levels) in <top (required)>'

https://github.com/drapergem/draper/blob/master/lib/draper/automatic_delegation.rb#L21

after

      allow(subject).to receive(:current_job_period).and_return(job_period)

(where subject is an instance of a Draper decorator) and then calling a decorator's method that calls current_job_period. By stack trace I'd say it's rather a problem with Draper, but in the delegatable? method definition there's nothing suspicious. Could it bo something with rspec or shall I rather create on issue for Draper?

@JonRowe
Copy link
Member

JonRowe commented Sep 9, 2015

Given we've gone to lengths to remove the deadlocks this could be a draper issue but I'm not going to rule out RSpec's involvement here, any chance you can reproduce the particular scenario draper is inducing? (Pull the bits of code out until it fails etc)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants