opal / opal-rspec

Opal + RSpec = ♥️
https://opalrb.com/docs/guides/master/rspec.html
61 stars 15 forks source link

Race condition in rake task #13

Closed bk2204 closed 9 years ago

bk2204 commented 9 years ago

The code in lib/opal/rspec/rake_task.rb has a race condition in the following area:

          server = fork do
            app = Opal::Server.new { |s|
              s.main = 'opal/rspec/sprockets_runner'
              s.append_path 'spec'
              s.debug = false

              block.call s if block
            }

            Rack::Server.start(:app => app, :Port => PORT, :AccessLog => [],
              :Logger => WEBrick::Log.new("/dev/null"))
          end

          system "phantomjs #{RUNNER} \"#{URL}\""
          success = $?.success?

          Process.kill(:SIGINT, server)
          Process.wait

phantomjs can attempt to run before the Rack::Server object is ready to serve, and as a result phantomjs fails, Rack gets a SIGINT in an unfortunate place, and no tests are run. I get the following traceback in this case:

vauxhall no % rake --trace opal:rspec
** Invoke opal:rspec (first_time)
** Execute opal:rspec
Cannot load: http://localhost:9999/
/usr/lib/ruby/vendor_ruby/rack/handler.rb:20:in `const_get': can't be called from trap context (ThreadError)
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:20:in `block in get'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:20:in `each'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:20:in `inject'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:20:in `get'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:38:in `block in pick'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:36:in `each'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:36:in `pick'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:57:in `default'
    from /usr/lib/ruby/vendor_ruby/rack/server.rb:268:in `server'
    from /usr/lib/ruby/vendor_ruby/rack/server.rb:257:in `block in start'
    from /usr/lib/ruby/2.1.0/rubygems/basic_specification.rb:62:in `call'
    from /usr/lib/ruby/2.1.0/rubygems/basic_specification.rb:62:in `block (2 levels) in contains_requirable_file?'
    from /usr/lib/ruby/2.1.0/rubygems/basic_specification.rb:62:in `each'
    from /usr/lib/ruby/2.1.0/rubygems/basic_specification.rb:62:in `any?'
    from /usr/lib/ruby/2.1.0/rubygems/basic_specification.rb:62:in `block in contains_requirable_file?'
    from /usr/lib/ruby/2.1.0/rubygems/basic_specification.rb:60:in `each'
    from /usr/lib/ruby/2.1.0/rubygems/basic_specification.rb:60:in `any?'
    from /usr/lib/ruby/2.1.0/rubygems/basic_specification.rb:60:in `contains_requirable_file?'
    from /usr/lib/ruby/2.1.0/rubygems/specification.rb:915:in `block in find_by_path'
    from /usr/lib/ruby/2.1.0/rubygems/specification.rb:883:in `block in each'
    from /usr/lib/ruby/2.1.0/rubygems/specification.rb:882:in `each'
    from /usr/lib/ruby/2.1.0/rubygems/specification.rb:882:in `each'
    from /usr/lib/ruby/2.1.0/rubygems/specification.rb:914:in `find'
    from /usr/lib/ruby/2.1.0/rubygems/specification.rb:914:in `find_by_path'
    from /usr/lib/ruby/2.1.0/rubygems.rb:188:in `try_activate'
    from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:132:in `rescue in require'
    from /usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:144:in `require'
    from /usr/lib/ruby/vendor_ruby/rack/handler/thin.rb:1:in `<top (required)>'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:20:in `const_get'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:20:in `block in get'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:20:in `each'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:20:in `inject'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:20:in `get'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:38:in `block in pick'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:36:in `each'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:36:in `pick'
    from /usr/lib/ruby/vendor_ruby/rack/handler.rb:57:in `default'
    from /usr/lib/ruby/vendor_ruby/rack/server.rb:268:in `server'
    from /usr/lib/ruby/vendor_ruby/rack/server.rb:264:in `start'
    from /usr/lib/ruby/vendor_ruby/rack/server.rb:141:in `start'
    from /var/lib/gems/2.1.0/gems/opal-rspec-0.4.0/lib/opal/rspec/rake_task.rb:27:in `block (2 levels) in initialize'
    from /var/lib/gems/2.1.0/gems/opal-rspec-0.4.0/lib/opal/rspec/rake_task.rb:18:in `fork'
    from /var/lib/gems/2.1.0/gems/opal-rspec-0.4.0/lib/opal/rspec/rake_task.rb:18:in `block in initialize'
    from /usr/lib/ruby/vendor_ruby/rake/task.rb:240:in `call'
    from /usr/lib/ruby/vendor_ruby/rake/task.rb:240:in `block in execute'
    from /usr/lib/ruby/vendor_ruby/rake/task.rb:235:in `each'
    from /usr/lib/ruby/vendor_ruby/rake/task.rb:235:in `execute'
    from /usr/lib/ruby/vendor_ruby/rake/task.rb:179:in `block in invoke_with_call_chain'
    from /usr/lib/ruby/2.1.0/monitor.rb:211:in `mon_synchronize'
    from /usr/lib/ruby/vendor_ruby/rake/task.rb:172:in `invoke_with_call_chain'
    from /usr/lib/ruby/vendor_ruby/rake/task.rb:165:in `invoke'
    from /usr/lib/ruby/vendor_ruby/rake/application.rb:150:in `invoke_task'
    from /usr/lib/ruby/vendor_ruby/rake/application.rb:106:in `block (2 levels) in top_level'
    from /usr/lib/ruby/vendor_ruby/rake/application.rb:106:in `each'
    from /usr/lib/ruby/vendor_ruby/rake/application.rb:106:in `block in top_level'
    from /usr/lib/ruby/vendor_ruby/rake/application.rb:115:in `run_with_threads'
    from /usr/lib/ruby/vendor_ruby/rake/application.rb:100:in `top_level'
    from /usr/lib/ruby/vendor_ruby/rake/application.rb:78:in `block in run'
    from /usr/lib/ruby/vendor_ruby/rake/application.rb:176:in `standard_exception_handling'
    from /usr/lib/ruby/vendor_ruby/rake/application.rb:75:in `run'
    from /usr/bin/rake:27:in `<main>'

Inserting a sleep 2 after the fork block makes it work correctly.

wied03 commented 9 years ago

The branch I've been working on explicitly waits for the rack server to answer on its port before continuing, so hopefully that will resolve this OK.