rails: Tests cause ThreadError: already initialized with Ruby 2.6.0

Steps to reproduce

Test like this causes ThreadError: already initialized after updating Ruby 2.6.0.

class EmployeesControllerTest < ActionController::TestCase
  def test_list_markup
    get :list
    assert_response(:success)
  end
  ...
end
EmployeesControllerTest#test_edit_markup:
ThreadError: already initialized
    /home/shirosaki/.rbenv/versions/2.6.0/lib/ruby/2.6.0/monitor.rb:259:in `mon_initialize'
    /home/shirosaki/.rbenv/versions/2.6.0/lib/ruby/2.6.0/monitor.rb:252:in `initialize'
    /home/shirosaki/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/actionpack-4.2.11/lib/action_dispatch/http/response.rb:119:in `initialize'
    /home/shirosaki/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/actionpack-4.2.11/lib/action_controller/test_case.rb:277:in `recycle!'
    /home/shirosaki/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/actionpack-4.2.11/lib/action_controller/test_case.rb:617:in `process'
    /home/shirosaki/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/actionpack-4.2.11/lib/action_controller/test_case.rb:67:in `process'
    /home/shirosaki/.rbenv/versions/2.6.0/lib/ruby/gems/2.6.0/gems/actionpack-4.2.11/lib/action_controller/test_case.rb:514:in `get'
    /home/shirosaki/work/master/test/functional/employees_controller_test.rb:24:in `test_edit_markup'

Expected behavior

Not raise ThreadError.

Actual behavior

Raise ThreadError.

System configuration

Rails version: 4.2.11 Ruby version: 2.6.0

The error would be related to this issue. https://bugs.ruby-lang.org/issues/15000

If I remove raise ThreadError, "already initialized" in lib/monitor.rb, tests pass.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 11
  • Comments: 21 (11 by maintainers)

Commits related to this issue

Most upvoted comments

@markets this is caused by rails reusing ActionController::TestResponse object in tests, can be fixed by this monkeypatch:

if RUBY_VERSION>='2.6.0'
  if Rails.version < '5'
    class ActionController::TestResponse < ActionDispatch::TestResponse
      def recycle!
        # hack to avoid MonitorMixin double-initialize error:
        @mon_mutex_owner_object_id = nil
        @mon_mutex = nil
        initialize
      end
    end
  else
    puts "Monkeypatch for ActionController::TestResponse no longer needed"
  end
end

The summary is, there are 2 preconditions here, that if satisfied lead to the error. Rails < 5 reuses (recycles) ActionController::TestResponse objects in tests. Starting with Ruby 2.6.0 MonitorMixin has a guard code that raises an exception if its initialize method is called twice. And in Ruby 2.7.0 they renamed the @mon_mutex* variables.

Are there additional fixes needed for this monkey patch to work for rails 4.2 + ruby 2.7?

So the revised workaround is:

# a workaround to avoid MonitorMixin double-initialize error
# https://github.com/rails/rails/issues/34790#issuecomment-681034561
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6.0')
  if Gem::Version.new(Rails.version) < Gem::Version.new('5.0.0')
    class ActionController::TestResponse < ActionDispatch::TestResponse
      def recycle!
        if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7.0')
          @mon_data = nil
          @mon_data_owner_object_id = nil
        else
          @mon_mutex = nil
          @mon_mutex_owner_object_id = nil
        end
        initialize
      end
    end
  else
    $stderr.puts "Monkeypatch for ActionController::TestResponse is no longer needed"
  end
end

Although, I’m considering switching to Ruby 2.6.0, since with Rails 4 and Ruby 2.7.0 the tests’ output is swarmed with warnings.

The Rails convention for files like this is config/initializers. Thats where I put mine and I called it monkey_patches.rb

I would avoid using config/initializers and opt for adding something to spec/support as @Vasfed has suggested as this will ensure load orders are not compromised. Using an initializer did not work for me due to some libraries being loaded later than the initializer.

In case there are still poor souls on Rails 4.2 with Ruby 2.6/2.7, this is the full file working for my team, required from rails_helper.rb.

# From https://github.com/rails/rails/issues/34790
#
# This is required because of an incompatibility between Ruby 2.6 and Rails 4.2, which the Rails team is not going to fix.

rb_version = Gem::Version.new(RUBY_VERSION)

if rb_version >= Gem::Version.new('2.6') && Gem::Version.new(Rails.version) < Gem::Version.new('5')
  if ! defined?(::ActionController::TestResponse)
    raise "Needed class is not defined yet, try requiring this file later."
  end

  if rb_version >= Gem::Version.new('2.7')
    puts "Using #{__FILE__} for Ruby 2.7."

    class ActionController::TestResponse < ActionDispatch::TestResponse
      def recycle!
        @mon_data = nil
        @mon_data_owner_object_id = nil
        initialize
      end
    end

    class ActionController::LiveTestResponse < ActionController::Live::Response
      def recycle!
        @body = nil
        @mon_data = nil
        @mon_data_owner_object_id = nil
        initialize
      end
    end

  else
    puts "Using #{__FILE__} for Ruby 2.6."

    class ActionController::TestResponse < ActionDispatch::TestResponse
      def recycle!
        @mon_mutex = nil
        @mon_mutex_owner_object_id = nil
        initialize
      end
    end

    class ActionController::LiveTestResponse < ActionController::Live::Response
      def recycle!
        @body = nil
        @mon_mutex = nil
        @mon_mutex_owner_object_id = nil
        initialize
      end
    end

  end
else
  puts "#{__FILE__} no longer needed."
end

@markets this is caused by rails reusing ActionController::TestResponse object in tests, can be fixed by this monkeypatch:

if RUBY_VERSION>='2.6.0'
  if Rails.version < '5'
    class ActionController::TestResponse < ActionDispatch::TestResponse
      def recycle!
        # hack to avoid MonitorMixin double-initialize error:
        @mon_mutex_owner_object_id = nil
        @mon_mutex = nil
        initialize
      end
    end
  else
    puts "Monkeypatch for ActionController::TestResponse no longer needed"
  end
end

If you use streaming/live stuff, you’ll also need

class ActionController::LiveTestResponse < ActionController::Live::Response
  def recycle!
    @body = nil
    @mon_mutex_owner_object_id = nil
    @mon_mutex = nil
    initialize
  end
end

@Vasfed Which file did you put your solution in?

The Rails convention for files like this is config/initializers. Thats where I put mine and I called it monkey_patches.rb

Edit: Since this issue only appears to affect the test environment, the “best” place to put a monkey patch like this in in spec/support. You may need to require it, you may not. It depends entirely on the contents of rails_helper.rb or spec_helper.rb (depending on your RSpec version!).

Thank you @mattheworiordan for the correction.

@Vasfed Which file did you put your solution in?

@timomitchel somewhere in your test suite setup - test_helper/rails_helper or a separate file that is required from there (intest/support or spec/support depending on which test framework you’re using) Point is that for patch to work, it should have been run before controller tests

Thanks for your information.
As described above, 4.2 only support severe security issues.
If this only occurs in 4.2, we do nothing. So I close. Thanks.

@Vasfed thanks for the workaround 👍, I think a lot of people will need it (or something smilar at least) to continue supporting rails the 4.2 + ruby 2.6 combo in their CIs, I was allowing failures, but this is of course not ideal: https://github.com/markets/maily/commit/9857a98b5f646413c5743b779c9730bcd99a2a2e

Hi, @markets I checked maily’s CI. It seems tests were already failed in 1 week ago. https://travis-ci.org/markets/maily/jobs/470573918#L556-L563

It seems that it was allowed failures. https://travis-ci.org/markets/maily/builds/470573898