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/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
- Analyzing SassC::SyntaxError in Ruby on Rails 7.0
- Deploy Ruby on Rails 7.0 to Dokku micro PaaS
- Fixing require LoadError as an example for the matrix gem
- Fix Flaky Rails System Tests caused by slow scrolling or animations
- Howto migrate from Webpacker to jsbundling-rails in Ruby on Rails