Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mocking Thread.current.thread_variable_get causes stack overflow. #605

Open
manueljacob opened this issue Jul 12, 2024 · 2 comments
Open

Comments

@manueljacob
Copy link

Mocking Thread.current.thread_variable_get used to work with rspec-support <= 3.12.0. With rspec-support >= 3.12.1, it causes a stack overflow.

# frozen_string_literal: true

begin
  require "bundler/inline"
rescue LoadError => e
  $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
  raise e
end

gemfile(true) do
  source "https://rubygems.org"

  gem "rspec", "3.12.0"
  ##gem "rspec-support", "3.12.0" # works
  gem "rspec-support", "3.12.1" # raises SystemStackError
end

puts "Ruby version is: #{RUBY_VERSION}"
require 'rspec/autorun'

RSpec.describe do
  it do
    expect(Thread.current).to receive(:thread_variable_get).with(:test).once.and_return(true)
    expect(Thread.current.thread_variable_get(:test)).to eq true
  end
end

Your environment

  • Ruby version: 3.3.1
  • rspec-support version: 3.12.1 or 3.13.0 or 3.13.1 or current main branch (42b18c4)

Analysis

There is infinite recursion between the following steps:

  1. Internal RSpec code calls RSpec::Support.thread_local_data.
  2. RSpec::Support.thread_local_data calls Thread.current.thread_variable_get.
  3. Because Thread.current.thread_variable_get is mocked, RSpec code is called. See 1.

Workaround

Add allow(Thread.current).to receive(:thread_variable_get).with(:__rspec).and_call_original.

The intention of the original code was to mock only Thread.current.thread_variable_get(:test) but not Thread.current.thread_variable_get called with any other arguments. However, I could not find a way to do that.

Proposed solution

If some mocked method is called from internal RSpec code, it should do nothing but delegate to the original method.

@pirj
Copy link
Member

pirj commented Jul 12, 2024

Apparently caused by #581
Would you like to try a different approach? Maybe stash the original method like we do for mutexes?

@manueljacob
Copy link
Author

Apparently caused by #581

Yes. I had tracked down the issue to 01eb9b2, but I forgot to mention this in the report. Sorry for that!

Would you like to try a different approach? Maybe stash the original method like we do for mutexes?

If I understood correctly, mutexes use a copy of the stdlib code. Because Thread.current.thread_variable_get is implemented in C, I changed the code to save a reference to the original methods. See #606.

manueljacob added a commit to manueljacob/rspec-support that referenced this issue Jul 13, 2024
manueljacob added a commit to manueljacob/rspec-support that referenced this issue Jul 14, 2024
manueljacob added a commit to manueljacob/rspec-support that referenced this issue Jul 14, 2024
manueljacob added a commit to manueljacob/rspec-support that referenced this issue Jul 15, 2024
manueljacob added a commit to manueljacob/rspec-support that referenced this issue Jul 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants