Upgrade to Pro — share decks privately, control downloads, hide ads and more …

require() bombed my multi-threaded app!

require() bombed my multi-threaded app!

Would you like Circular Dependencies with that? In Rails we take autoloading for granted, but slow test suites and evasive multi-thread bombs too often drive us to upbraid ActiveSupport’s autoloading magic.

To dispel the magic, we will cover nothing less than the entirety of require’s modest origins, the enlightening truth of Ruby execution, a selection of minimal autoload examples, the most interesting ways to break integration specs, and utopian isolation practices. All while speeding up your test suite.

https://github.com/nybblr/ruby-code-loading

Jonathan Lee Martin

November 13, 2015
Tweet

More Decks by Jonathan Lee Martin

Other Decks in Programming

Transcript

  1. /home/nybblr/ !"" cookery # !"" ingredient.rb # $"" recipe.rb !""

    cookery.rb $"" main.rb 1 directory, 4 files > tree /home/nybblr
  2. class Cook def get_binding binding end end eval "puts to_s",

    Cook.new.get_binding > #<Cook:0x007f88918e3110> > irb
  3. dir = File.expand_path("..", __FILE__) file = File.join(dir, "cookery/recipe.rb") eval File.read(file),

    binding, "cookery/recipe.rb", 1 > Loaded Cookery::Recipe class! main.rb
  4. # main.rb header from now on! require 'active_support' ROOT =

    File.expand_path("..", __FILE__) $LOAD_PATH.unshift(ROOT) p $LOADED_FEATURES.last > ".../active_support/dependencies.rb" main.rb
  5. # ActiveSupport::Dependencies.mechanism = :load ActiveSupport::Dependencies.autoload_paths << ROOT Cookery::Recipe > Loaded

    Cookery::Recipe class! p $LOADED_FEATURES.last > ".../active_support/dependencies.rb" require 'cookery/recipe' > Loaded Cookery::Recipe class! main.rb
  6. ActiveSupport::Dependencies.mechanism = :require ActiveSupport::Dependencies.autoload_paths << ROOT Cookery::Recipe > Loaded Cookery::Recipe

    class! p $LOADED_FEATURES.last > "/home/nybblr/cookery/recipe.rb" require 'cookery/recipe' > *Eerie silence…* main.rb
  7. require 'cookery' Cookery::Recipe > Loaded Cookery::Recipe class! p $LOADED_FEATURES.last >

    "/home/nybblr/cookery/recipe.rb" require 'cookery/recipe' > *Eerie silence…* main.rb
  8. ActiveSupport::Dependencies.mechanism = :require ActiveSupport::Dependencies.autoload_paths << ROOT Cookery::Recipe > Loaded ::Recipe

    class! > Unable to autoload constant Cookery::Recipe, expected ./cookery/recipe.rb to define it (LoadError) main.rb
  9. ActiveSupport::Dependencies.mechanism = :require ActiveSupport::Dependencies.autoload_paths += ["#{ROOT}/cookery", ROOT] Recipe > Loaded

    ::Recipe class! p $LOADED_FEATURES.last > "/home/nybblr/cookery/recipe.rb" Cookery::Recipe > Unable to autoload constant Cookery::Recipe, expected ./cookery/ recipe.rb to define it (LoadError) main.rb
  10. # ActiveSupport::Dependencies.mechanism = :load ActiveSupport::Dependencies.autoload_paths << ROOT Cookery::Recipe > Loaded

    Cookery::Recipe class! p $LOADED_FEATURES.last > ".../active_support/dependencies.rb" require 'cookery/recipe' > Loaded Cookery::Recipe class! > already initialized constant Cookery::Recipe::CONSTANT main.rb
  11. ActiveSupport::Dependencies.mechanism = :require ActiveSupport::Dependencies.autoload_paths << ROOT Cookery::Recipe > Loaded Cookery::Recipe

    class! p $LOADED_FEATURES.last > "/home/nybblr/cookery.rb" require 'cookery/recipe' > *Eerie silence...* main.rb
  12. # ActiveSupport::Dependencies.mechanism = :load ActiveSupport::Dependencies.autoload_paths << ROOT Cookery::Recipe > Loaded

    Cookery::Recipe class! > Loaded Cookery::Ingredient class! > Loaded Cookery::Recipe class! > Circular dependency detected while autoloading constant Cookery::Ingredient (RuntimeError) main.rb
  13. puts "Loaded Cookery::Ingredient class!" # require 'cookery/recipe' module Cookery class

    Ingredient CONSTANT = Recipe end end cookery/ingredient.rb
  14. ENV["RAILS_ENV"] ||= 'test' if !Object.const_defined?(:Rails) require 'active_support/all' root = File.expand_path('../..',

    __FILE__) ActiveSupport::Dependencies.autoload_paths += Dir["#{root}/app/*/"] end # General config RSpec.configure do |config| config.order = :random end spec/spec_helper.rb
  15. require File.expand_path('../../config/environment', __FILE__) require 'spec_helper' require 'rspec/rails' # Rails specific

    config RSpec.configure do |config| config.use_transactional_fixtures = true config.infer_spec_type_from_file_location! end spec/rails_helper.rb