Working with Legacy Ruby on Rails: spring.gem fork() Crash
Reading time: 4 minutes
Working with legacy Ruby on Rails applications is for most developers a problem, as they have a lot of extra work to do:
- Use older documentation, which matches the legacy project
- can’t use fixes and improvements of programming language
- can’t use improvements and features of up-to-date framework version
- have to use older and mostly unsupported third-party extensions
- are maybe forced to write code, which must be changed for updates and upgrades
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/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-220.127.116.11/lib/active_support/dependencies.rb:240:in `load_dependency` objc: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called. objc: +[__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
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
.zshrc (according to the shell you
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
spec/rails_helper.rb. For a
minitest-based test suite check
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
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.