Working with Legacy Ruby on Rails: spring.gem fork() Crash

Reading time: 4 minutes

Spring Fork Crash on Ruby on Rails

Working with legacy Ruby on Rails applications is for most developers a problem, as they have a lot of extra work to do:

First of all, many developers will hit the CPU architecture change from x86/x64 to ARM64 using Apple Macs. Even installing older Ruby versions on a newer OS (operating system) on a x86/x64 architecture is hard. It produces a lot of problems, warnings, and errors.

Understand the crash introduced through a fork() call

If you could install Ruby on your system, the next thing which may hit you is starting your application using bin/rails and running bin/rake commands. If you have ever tried it, this or a similar error may stop you:

bin/rake db:schema:load

Called from /Users/developer/.asdf/installs/ruby/2.2.10/lib/ruby/gems/2.2.0/gems/activesupport-4.2.11.1/lib/active_support/dependencies.rb:240:in `load_dependency`
objc[98084]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called.
objc[98084]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called.
We cannot safely call it or ignore it in the fork() child process. Crashing instead.
Set a breakpoint on objc_initializeAfterForkError to debug.

This change was introduced in MacOS 10.13 (High Sierra) in 2017. You can have a deeper reading on it here: Objective-C and fork() in macOS 10.13. There are a lot of projects, which were hit by this problem like Python, Passenger, and Puma.

There may be more issues you need to tackle to get a legacy project running on a newer system. But how to get this?

Where does it hit my project?

First of all, you need to figure out, where a new thread was started using fork() command. The message you get does not point you to the line of code or gem, which causes this crash.

As the project was a common Ruby on Rails project without any threading built in, the fork must have been called by a third-party gem, which was defined in the Gemfile (or Gemfile.lock as dependency) and was loaded as dependency on the start of Ruby on Rails.

I’ve checked the Gemfile and could only find the spring gem, which was using threads to improve starting the Ruby on Rails and related command line tools like bin/rake, as they were loading the Rails project on start too.

Stop Initialize Fork Safety on MacOS High Sierra

This won’t work on newer MacOS versions. If you are still running MacOS High Sierra you can stop this safety check by defining the environment variable OBJC_DISABLE_INITIALIZE_FORK_SAFETY in one of these ways:

# For a single command (on MacOS High Sierra only)
OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES bin/rails s

# Stop it in the current shell (on MacOS High Sierra only)
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES

# Stop it for all bash shells (may hide problems)
echo "export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES" >> ~/.bashrc

# Stop it for all zsh shells (may hide problems)
echo "export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES" >> ~/.zshrc

IMPORTANT: This will only work on MacOS High Sierra. This workaround was removed in the next version MacOS Mojave.

Like most developers, you are working with a newer MacOS version and need another workaround or fix to tackle this problem in the long run. Keep on reading.

Stop Spring from starting

If you check the README of the spring project, you will find the environment variable you have to stop, to stop spring from starting.

# Start rails web server without spring:
DISABLE_SPRING=true bin/rails s

# define it in the current shell
export DISABLE_SPRING=true

I would not disable spring for the whole machine by defining this variable in the used shell rc file like .bashrc or .zshrc (according to the shell you use).

Removing Spring gem from your project

As spring causes quite some problems, which were never fully solved and due to the performance increase of current computer systems, many projects can remove spring - even in legacy projects, where there are many other more important things to solve.

The README of spring has a chapter describing what you have to do, to remove it from a Ruby on Rails project like:

# remove spring integration in bin/* scripts
bin/spring binstub --remove --all
# stop spring process
bin/spring stop

Remove spring from your Gemfile and run bundle install afterwards:


 group :development do
-  gem "spring"
-  gem "spring-commands-rspec"
   gem "web-console"
 end

As many projects were using spring heavily for improving the runtime of the test suite on developer machines. Check your rspec setup in spec/spec_helper.rb, spec/rails_helper.rb. For a minitest-based test suite check test/test_helper.rb.

If you are using guard check your Guardfile for entries like:

-guard :rspec, cmd: 'spring rspec -f doc ' do
+guard :rspec, cmd: 'rspec -f doc ' do

Summary

Working with legacy applications is hard and will become even harder over time. Changes in third-party libraries, OS (operation system), etc. are typical challenges for many projects.


Newsletter


See Also


Tags