diff --git a/.bundle/config b/.bundle/config new file mode 100644 index 000000000..dc82af0e9 --- /dev/null +++ b/.bundle/config @@ -0,0 +1,2 @@ +--- +BUNDLE_VERSION: "system" diff --git a/examples/camping/.bundle/config b/examples/camping/.bundle/config new file mode 100644 index 000000000..dc82af0e9 --- /dev/null +++ b/examples/camping/.bundle/config @@ -0,0 +1,2 @@ +--- +BUNDLE_VERSION: "system" diff --git a/examples/rails8/.bundle/config b/examples/rails8/.bundle/config new file mode 100644 index 000000000..dc82af0e9 --- /dev/null +++ b/examples/rails8/.bundle/config @@ -0,0 +1,2 @@ +--- +BUNDLE_VERSION: "system" diff --git a/examples/sinatra/.bundle/config b/examples/sinatra/.bundle/config new file mode 100644 index 000000000..dc82af0e9 --- /dev/null +++ b/examples/sinatra/.bundle/config @@ -0,0 +1,2 @@ +--- +BUNDLE_VERSION: "system" diff --git a/gemfiles/.bundle/config b/gemfiles/.bundle/config index c127f8025..dc82af0e9 100644 --- a/gemfiles/.bundle/config +++ b/gemfiles/.bundle/config @@ -1,2 +1,2 @@ --- -BUNDLE_RETRY: "1" +BUNDLE_VERSION: "system" diff --git a/src/main/java/org/jruby/rack/RackApplicationFactoryDecorator.java b/src/main/java/org/jruby/rack/RackApplicationFactoryDecorator.java index 69ceff3cc..1858afb1c 100644 --- a/src/main/java/org/jruby/rack/RackApplicationFactoryDecorator.java +++ b/src/main/java/org/jruby/rack/RackApplicationFactoryDecorator.java @@ -133,8 +133,8 @@ public void destroy() { public RackApplication getApplication() throws RackException { final RuntimeException error = getInitError(); if ( error != null ) { - log(DEBUG, "due a previous initialization failure application instance can not be returned"); - throw error; // this is better - we shall never return null here ... + log(DEBUG, "due to a previous initialization failure application instance can not be returned"); + throw error; } return getApplicationImpl(); } diff --git a/src/spec/ruby/jruby/rack/booter_spec.rb b/src/spec/ruby/jruby/rack/booter_spec.rb index 4058aa826..14e99e80e 100644 --- a/src/spec/ruby/jruby/rack/booter_spec.rb +++ b/src/spec/ruby/jruby/rack/booter_spec.rb @@ -294,17 +294,17 @@ @runtime.evalScriptlet("load 'jruby/rack/boot/rack.rb'") # booter got setup : - should_not_eval_as_nil "defined?(JRuby::Rack.booter)" - should_not_eval_as_nil "JRuby::Rack.booter" + should_eval_as_not_nil "defined?(JRuby::Rack.booter)" + should_eval_as_not_nil "JRuby::Rack.booter" should_eval_as_eql_to "JRuby::Rack.booter.class.name", 'JRuby::Rack::Booter' # Booter.boot! run : - should_not_eval_as_nil "ENV['RACK_ENV']" + should_eval_as_not_nil "ENV['RACK_ENV']" # rack got required : - should_not_eval_as_nil "defined?(Rack::RELEASE)" - should_not_eval_as_nil "defined?(Rack.release)" + should_eval_as_not_nil "defined?(Rack::RELEASE)" + should_eval_as_not_nil "defined?(Rack.release)" # check if it got loaded correctly : - should_not_eval_as_nil "Rack::Request.new({}) rescue nil" + should_eval_as_not_nil "Rack::Request.new({}) rescue nil" end end @@ -341,13 +341,13 @@ @runtime.evalScriptlet("load 'jruby/rack/boot/rails.rb'") # booter got setup : - should_not_eval_as_nil "defined?(JRuby::Rack.booter)" - should_not_eval_as_nil "JRuby::Rack.booter" + should_eval_as_not_nil "defined?(JRuby::Rack.booter)" + should_eval_as_not_nil "JRuby::Rack.booter" should_eval_as_eql_to "JRuby::Rack.booter.class.name", 'JRuby::Rack::RailsBooter' # Booter.boot! run : - should_not_eval_as_nil "ENV['RACK_ENV']" - should_not_eval_as_nil "ENV['RAILS_ENV']" + should_eval_as_not_nil "ENV['RACK_ENV']" + should_eval_as_not_nil "ENV['RAILS_ENV']" # rack not yet required (let bundler decide which rack version to load) : should_eval_as_nil "defined?(Rack::RELEASE)" diff --git a/src/spec/ruby/jruby/rack/rails_booter_spec.rb b/src/spec/ruby/jruby/rack/rails_booter_spec.rb index 0fdc4163c..ea394c46d 100644 --- a/src/spec/ruby/jruby/rack/rails_booter_spec.rb +++ b/src/spec/ruby/jruby/rack/rails_booter_spec.rb @@ -122,10 +122,6 @@ end end - after :all do - $servlet_context = nil - end - let(:railtie_class) { Class.new(Rails::Railtie) } it "should have loaded the railtie" do diff --git a/src/spec/ruby/rack/application_spec.rb b/src/spec/ruby/rack/application_spec.rb index 30dcd43a3..dee169b28 100644 --- a/src/spec/ruby/rack/application_spec.rb +++ b/src/spec/ruby/rack/application_spec.rb @@ -104,6 +104,8 @@ def it_should_rewind_body before :each do @app_factory = DefaultRackApplicationFactory.new + reset_booter + reset_servlet_context_global end it "should receive a rackup script via the 'rackup' parameter" do @@ -161,11 +163,6 @@ def it_should_rewind_body expect(input_stream.getMaximumBufferSize).to eq 420 end - before do - reset_booter - JRuby::Rack.context = $servlet_context = nil - end - it "should init and create application object without a rackup script" do $servlet_context = @servlet_context # NOTE: a workaround to be able to mock it : @@ -303,8 +300,8 @@ def newRuntime() it "creates a new Ruby runtime with the jruby-rack environment pre-loaded" do @runtime = app_factory.newRuntime - should_not_eval_as_nil "defined?(::Rack)" - should_not_eval_as_nil "defined?(::Rack::Handler::Servlet)" + should_eval_as_not_nil "defined?(::Rack)" + should_eval_as_not_nil "defined?(::Rack::Handler::Servlet)" should_eval_as_nil "defined?(Rack::Handler::Bogus)" end @@ -333,14 +330,14 @@ def newRuntime() app_factory.checkAndSetRackVersion(@runtime) @runtime.evalScriptlet "require 'rack'" - should_not_eval_as_nil "defined?(Bundler)" + should_eval_as_not_nil "defined?(Bundler)" should_eval_as_eql_to "Rack.release", '2.2.0' should_eval_as_eql_to "Gem.loaded_specs['rack'].version.to_s", '2.2.0' end it "initializes the $servlet_context global variable" do @runtime = app_factory.new_runtime - should_not_eval_as_nil "defined?($servlet_context)" + expect(@runtime.evalScriptlet("defined?($servlet_context)")).to be_truthy end it "clears environment variables if the configuration ignores the environment" do @@ -516,7 +513,7 @@ def reset_config describe org.jruby.rack.rails.RailsRackApplicationFactory do - java_import org.jruby.rack.rails.RailsRackApplicationFactory + require 'jruby/rack/rails_booter' before :each do @app_factory = RailsRackApplicationFactory.new @@ -557,8 +554,20 @@ def createRackServletWrapper(runtime, rackup, filename) describe org.jruby.rack.PoolingRackApplicationFactory do + # Workaround rspec mocks/proxies not being thread-safe which causes occasional failures + class Synchronized + def initialize(obj) + @delegate = obj + @lock = Mutex.new + end + + def method_missing(name, *args, &block) + @lock.synchronize { @delegate.send(name, *args, &block) } + end + end + before :each do - @factory = double "factory" + @factory = Synchronized.new(double("factory").as_null_object) @pooling_factory = org.jruby.rack.PoolingRackApplicationFactory.new @factory @pooling_factory.context = @rack_context end @@ -608,7 +617,7 @@ def createRackServletWrapper(runtime, rackup, filename) it "creates applications during initialization according to the jruby.min.runtimes context parameter" do allow(@factory).to receive(:init) allow(@factory).to receive(:newApplication) do - app = double "app" + app = Synchronized.new(double("app").as_null_object) expect(app).to receive(:init) app end @@ -641,7 +650,7 @@ def createRackServletWrapper(runtime, rackup, filename) it "forces the maximum size to be greater or equal to the initial size" do allow(@factory).to receive(:init) allow(@factory).to receive(:newApplication) do - app = double "app" + app = Synchronized.new(double("app").as_null_object) expect(app).to receive(:init) app end @@ -655,7 +664,7 @@ def createRackServletWrapper(runtime, rackup, filename) end it "retrieves the error application from the delegate factory" do - app = double("app") + app = double "app" expect(@factory).to receive(:getErrorApplication).and_return app expect(@pooling_factory.getErrorApplication).to eq app end @@ -663,9 +672,9 @@ def createRackServletWrapper(runtime, rackup, filename) it "waits till initial runtimes get initialized (with wait set to true)" do allow(@factory).to receive(:init) allow(@factory).to receive(:newApplication) do - app = double "app" + app = Synchronized.new(double("app").as_null_object) allow(app).to receive(:init) do - sleep(0.10) + sleep(0.05) end app end @@ -679,15 +688,16 @@ def createRackServletWrapper(runtime, rackup, filename) it "throws an exception from getApplication when an app failed to initialize " + "(even when only a single application initialization fails)" do + app_init_secs = 0.05 allow(@factory).to receive(:init) app_count = java.util.concurrent.atomic.AtomicInteger.new(0) allow(@factory).to receive(:newApplication) do - app = double "app" + app = Synchronized.new(double("app").as_null_object) allow(app).to receive(:init) do if app_count.addAndGet(1) == 2 raise org.jruby.rack.RackInitializationException.new('failed app init') end - sleep(0.05) + sleep(app_init_secs) end app end @@ -701,7 +711,7 @@ def createRackServletWrapper(runtime, rackup, filename) rescue org.jruby.rack.RackInitializationException # ignore - sometimes initialization happens fast enough that the init error is thrown already end - sleep(0.20) + sleep(num_runtimes * app_init_secs + 0.07) # sleep with a buffer failed = 0 num_runtimes.times do @@ -717,10 +727,11 @@ def createRackServletWrapper(runtime, rackup, filename) end it "wait until pool is filled when invoking getApplication (with wait set to false)" do + app_init_secs = 0.2 allow(@factory).to receive(:init) allow(@factory).to receive(:newApplication) do - app = double "app" - allow(app).to receive(:init) { sleep(0.2) } + app = Synchronized.new(double("app").as_null_object) + allow(app).to receive(:init) { sleep(app_init_secs) } app end allow(@rack_config).to receive(:getBooleanProperty).with("jruby.runtime.init.wait").and_return false @@ -728,17 +739,17 @@ def createRackServletWrapper(runtime, rackup, filename) expect(@rack_config).to receive(:getMaximumRuntimes).and_return 4 @pooling_factory.init(@rack_context) - millis = java.lang.System.currentTimeMillis + start = java.lang.System.currentTimeMillis expect(@pooling_factory.getApplication).not_to be nil - millis = java.lang.System.currentTimeMillis - millis - expect(millis).to be >= 150 # getApplication waited ~ 0.2 secs + expect(java.lang.System.currentTimeMillis - start).to be_within(70).of(app_init_secs * 1000) # getApplication waited ~ sleep time end it "waits acquire timeout till an application is available from the pool (than raises)" do + app_init_secs = 0.2 allow(@factory).to receive(:init) expect(@factory).to receive(:newApplication).twice do - app = double "app" - expect(app).to receive(:init) { sleep(0.2) } + app = Synchronized.new(double("app").as_null_object) + expect(app).to receive(:init) { sleep(app_init_secs) } app end allow(@rack_config).to receive(:getBooleanProperty).with("jruby.runtime.init.wait").and_return false @@ -747,86 +758,90 @@ def createRackServletWrapper(runtime, rackup, filename) @pooling_factory.init(@rack_context) @pooling_factory.acquire_timeout = 1.to_java # second - millis = java.lang.System.currentTimeMillis + start = java.lang.System.currentTimeMillis expect(@pooling_factory.getApplication).not_to be nil - millis = java.lang.System.currentTimeMillis - millis - expect(millis).to be >= 150 # getApplication waited ~ 0.2 secs + expect(java.lang.System.currentTimeMillis - start).to be_within(70).of(app_init_secs * 1000) app2 = @pooling_factory.getApplication # now the pool is empty - - @pooling_factory.acquire_timeout = 0.1.to_java # second - millis = java.lang.System.currentTimeMillis + timeout_secs = 0.1 + @pooling_factory.acquire_timeout = (timeout_secs).to_java + start = java.lang.System.currentTimeMillis expect { @pooling_factory.getApplication }.to raise_error(org.jruby.rack.AcquireTimeoutException) - millis = java.lang.System.currentTimeMillis - millis - expect(millis).to be >= 90 # waited about ~ 0.1 secs + expect(java.lang.System.currentTimeMillis - start).to be_within(20).of(timeout_secs * 1000) @pooling_factory.finishedWithApplication(app2) # gets back to the pool expect(@pooling_factory.getApplication).to eq app2 end it "gets and initializes new applications until maximum allows to create more" do + app_init_secs = 0.1 allow(@factory).to receive(:init) expect(@factory).to receive(:newApplication).twice do - app = double "app (new)" - expect(app).to receive(:init) { sleep(0.1) } + app = Synchronized.new(double("app (new)").as_null_object) + expect(app).to receive(:init) { sleep(app_init_secs) } app end allow(@rack_config).to receive(:getBooleanProperty).with("jruby.runtime.init.wait").and_return false allow(@rack_config).to receive(:getInitialRuntimes).and_return 2 allow(@rack_config).to receive(:getMaximumRuntimes).and_return 4 + timeout_secs = 0.1 @pooling_factory.init(@rack_context) - @pooling_factory.acquire_timeout = 0.10.to_java # second + @pooling_factory.acquire_timeout = (timeout_secs).to_java # second 2.times { expect(@pooling_factory.getApplication).not_to be nil } + app_get_secs = 0.15 expect(@factory).to receive(:getApplication).twice do - app = double "app (get)"; sleep(0.15); app + app = Synchronized.new(double("app (get)").as_null_object) + sleep(app_get_secs) + app end - millis = java.lang.System.currentTimeMillis + start = java.lang.System.currentTimeMillis 2.times { expect(@pooling_factory.getApplication).not_to be nil } - millis = java.lang.System.currentTimeMillis - millis - expect(millis).to be >= 300 # waited about 2 x 0.15 secs + expect(java.lang.System.currentTimeMillis - start).to be_within(70).of(2 * app_get_secs * 1000) - millis = java.lang.System.currentTimeMillis + start = java.lang.System.currentTimeMillis expect { @pooling_factory.getApplication }.to raise_error(org.jruby.rack.AcquireTimeoutException) - millis = java.lang.System.currentTimeMillis - millis - expect(millis).to be >= 90 # waited about ~ 0.10 secs + expect(java.lang.System.currentTimeMillis - start).to be_within(20).of(timeout_secs * 1000) end - it "initializes initial runtimes in paralel (with wait set to false)" do + it "initializes initial runtimes in parallel (with wait set to false)" do + app_init_secs = 0.15 allow(@factory).to receive(:init) allow(@factory).to receive(:newApplication) do - app = double "app" - allow(app).to receive(:init) do - sleep(0.15) - end + app = Synchronized.new(double("app").as_null_object) + allow(app).to receive(:init) { sleep(app_init_secs) } app end + + init_threads = 4 + init_runtimes = 6 allow(@rack_config).to receive(:getBooleanProperty).with("jruby.runtime.init.wait").and_return false - allow(@rack_config).to receive(:getInitialRuntimes).and_return 6 + allow(@rack_config).to receive(:getRuntimeInitThreads).and_return init_threads + allow(@rack_config).to receive(:getInitialRuntimes).and_return init_runtimes allow(@rack_config).to receive(:getMaximumRuntimes).and_return 8 + expected_initial_init_time = 1.1 * (init_runtimes.to_f / init_threads.to_f).ceil * app_init_secs # 10% margin @pooling_factory.init(@rack_context) - sleep(0.10) - expect(@pooling_factory.getApplicationPool.size).to be < 6 - sleep(0.9) - expect(@pooling_factory.getApplicationPool.size).to be >= 6 + sleep(app_init_secs) # wait for at some (but not possibly all) to finish + expect(@pooling_factory.getApplicationPool.size).to be < init_runtimes + sleep(expected_initial_init_time - app_init_secs) # remaining time + expect(@pooling_factory.getApplicationPool.size).to be >= init_runtimes expect(@pooling_factory.getManagedApplications).to_not be_empty - expect(@pooling_factory.getManagedApplications.size).to eql 6 + expect(@pooling_factory.getManagedApplications.size).to eql init_runtimes end it "throws from init when application initialization in thread failed" do + app_init_secs = 0.05 allow(@factory).to receive(:init) allow(@factory).to receive(:newApplication) do - app = double "app" - allow(app).to receive(:init) do - sleep(0.05); raise "app.init raising" - end + app = Synchronized.new(double("app").as_null_object) + allow(app).to receive(:init) { sleep(app_init_secs); raise "app.init raising" } app end allow(@rack_config).to receive(:getInitialRuntimes).and_return 2 @@ -845,9 +860,6 @@ def createRackServletWrapper(runtime, rackup, filename) expect { @pooling_factory.init(@rack_context) }.to raise_error org.jruby.rack.RackInitializationException expect(raise_error_logged).to eql 1 # logs same init exception once - - # NOTE: seems it's not such a good idea to return empty on init error - # expect(@pooling_factory.getManagedApplications).to be_empty end end diff --git a/src/spec/ruby/spec_helper.rb b/src/spec/ruby/spec_helper.rb index d8c354496..7af5be903 100644 --- a/src/spec/ruby/spec_helper.rb +++ b/src/spec/ruby/spec_helper.rb @@ -10,6 +10,7 @@ java_import 'org.jruby.rack.RackApplicationFactory' java_import 'org.jruby.rack.DefaultRackApplicationFactory' +java_import 'org.jruby.rack.rails.RailsRackApplicationFactory' java_import 'org.jruby.rack.servlet.RequestCapture' java_import 'org.jruby.rack.servlet.ResponseCapture' java_import 'org.jruby.rack.servlet.RewindableInputStream' @@ -50,6 +51,11 @@ def servlet_context mock_servlet_context end + def reset_servlet_context_global + $servlet_context = nil if defined? $servlet_context + JRuby::Rack.context = nil if defined?(JRuby::Rack) and JRuby::Rack.respond_to?(:context=) + end + def silence_warnings(&block) JRuby::Rack::Helpers.silence_warnings(&block) end @@ -80,42 +86,25 @@ def expect_eql_java_bytes(actual, expected) # org.jruby.Ruby.evalScriptlet helpers - comparing values from different runtimes - def should_eval_as_eql_to(code, expected, options = {}) - if options.is_a?(Hash) - runtime = options[:runtime] || @runtime - else - runtime, options = options, {} - end - message = options[:message] || "expected eval #{code.inspect} to be == $expected but was $actual" - be_flag = options.has_key?(:should) ? options[:should] : be_truthy - - expected = expected.inspect.to_java - actual = runtime.evalScriptlet(code).inspect.to_java - expect(actual.equals(expected)).to be_flag, message.gsub('$expected', expected.to_s).gsub('$actual', actual.to_s) + def should_eval_as_eql_to(code, expected) + actual = @runtime.evalScriptlet(code) + expect(actual.inspect).to eq(expected.inspect), "expected eval #{code.inspect} to be == #{expected.to_s} but was #{actual.to_s}" end - def should_eval_as_not_eql_to(code, expected, options = {}) - should_eval_as_eql_to(code, expected, options.merge( - :should => be_falsy, - :message => options[:message] || "expected eval #{code.inspect} to be != $expected but was not") - ) + def should_eval_as_not_eql_to(code, expected) + actual = @runtime.evalScriptlet(code) + expect(actual.inspect).not_to eq(expected.inspect), "expected eval #{code.inspect} NOT to be == #{expected.to_s}" end - def should_eval_as_nil(code, runtime = @runtime) - should_eval_as_eql_to code, nil, :runtime => runtime, - :message => "expected eval #{code.inspect} to be nil but was $actual" + def should_eval_as_nil(code) + actual = @runtime.evalScriptlet(code) + expect(actual).to be_nil, "expected eval #{code.inspect} to be nil but was #{actual.to_s}" end - def should_eval_as_not_nil(code, runtime = @runtime) - should_eval_as_eql_to code, nil, :should => be_falsy, :runtime => runtime, - :message => "expected eval #{code.inspect} to not be nil but was" + def should_eval_as_not_nil(code) + actual = @runtime.evalScriptlet(code) + expect(actual).to_not be_nil, "expected eval #{code.inspect} to not be nil" end - - def should_not_eval_as_nil(code, runtime = @runtime) - # alias - should_eval_as_not_nil(code, runtime) - end - end # NOTE: avoid chunked-patch (loaded by default from a hook at @@ -124,7 +113,7 @@ def should_not_eval_as_nil(code, runtime = @runtime) STUB_DIR = File.expand_path('../stub', File.dirname(__FILE__)) -WD_START = Dir.getwd +ORIGINAL_WORKING_DIR = Dir.getwd begin # NOTE: only if running with a `bundle exec` to better isolate @@ -153,8 +142,8 @@ def should_not_eval_as_nil(code, runtime = @runtime) config.after(:each) do (ENV.keys - @env_save.keys).each { |k| ENV.delete k } @env_save.each { |k, v| ENV[k] = v } - Dir.chdir(WD_START) unless Dir.getwd == WD_START - $servlet_context = nil if defined? $servlet_context + Dir.chdir(ORIGINAL_WORKING_DIR) unless Dir.getwd == ORIGINAL_WORKING_DIR + reset_servlet_context_global end # NOTE: only works when no other example filtering is in place: e.g. `rspec ... --example=logger` won't filter here