Skip to content

Commit dcdecf1

Browse files
committed
[tests] Fix threading issue with parallel testing with non-threadsafe rspec mocks
1 parent e739207 commit dcdecf1

File tree

1 file changed

+39
-21
lines changed

1 file changed

+39
-21
lines changed

src/spec/ruby/rack/application_spec.rb

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -557,8 +557,20 @@ def createRackServletWrapper(runtime, rackup, filename)
557557

558558
describe org.jruby.rack.PoolingRackApplicationFactory do
559559

560+
# Workaround rspec mocks/proxies not being thread-safe which causes occasional failures
561+
class Synchronized
562+
def initialize(obj)
563+
@delegate = obj
564+
@lock = Mutex.new
565+
end
566+
567+
def method_missing(name, *args, &block)
568+
@lock.synchronize { @delegate.send(name, *args, &block) }
569+
end
570+
end
571+
560572
before :each do
561-
@factory = double "factory"
573+
@factory = Synchronized.new(double("factory").as_null_object)
562574
@pooling_factory = org.jruby.rack.PoolingRackApplicationFactory.new @factory
563575
@pooling_factory.context = @rack_context
564576
end
@@ -608,7 +620,7 @@ def createRackServletWrapper(runtime, rackup, filename)
608620
it "creates applications during initialization according to the jruby.min.runtimes context parameter" do
609621
allow(@factory).to receive(:init)
610622
allow(@factory).to receive(:newApplication) do
611-
app = double "app"
623+
app = Synchronized.new(double("app").as_null_object)
612624
expect(app).to receive(:init)
613625
app
614626
end
@@ -641,7 +653,7 @@ def createRackServletWrapper(runtime, rackup, filename)
641653
it "forces the maximum size to be greater or equal to the initial size" do
642654
allow(@factory).to receive(:init)
643655
allow(@factory).to receive(:newApplication) do
644-
app = double "app"
656+
app = Synchronized.new(double("app").as_null_object)
645657
expect(app).to receive(:init)
646658
app
647659
end
@@ -655,15 +667,15 @@ def createRackServletWrapper(runtime, rackup, filename)
655667
end
656668

657669
it "retrieves the error application from the delegate factory" do
658-
app = double("app")
670+
app = double "app"
659671
expect(@factory).to receive(:getErrorApplication).and_return app
660672
expect(@pooling_factory.getErrorApplication).to eq app
661673
end
662674

663675
it "waits till initial runtimes get initialized (with wait set to true)" do
664676
allow(@factory).to receive(:init)
665677
allow(@factory).to receive(:newApplication) do
666-
app = double "app"
678+
app = Synchronized.new(double("app").as_null_object)
667679
allow(app).to receive(:init) do
668680
sleep(0.05)
669681
end
@@ -683,7 +695,7 @@ def createRackServletWrapper(runtime, rackup, filename)
683695
allow(@factory).to receive(:init)
684696
app_count = java.util.concurrent.atomic.AtomicInteger.new(0)
685697
allow(@factory).to receive(:newApplication) do
686-
app = double "app"
698+
app = Synchronized.new(double("app").as_null_object)
687699
allow(app).to receive(:init) do
688700
if app_count.addAndGet(1) == 2
689701
raise org.jruby.rack.RackInitializationException.new('failed app init')
@@ -721,7 +733,7 @@ def createRackServletWrapper(runtime, rackup, filename)
721733
app_init_secs = 0.2
722734
allow(@factory).to receive(:init)
723735
allow(@factory).to receive(:newApplication) do
724-
app = double "app"
736+
app = Synchronized.new(double("app").as_null_object)
725737
allow(app).to receive(:init) { sleep(app_init_secs) }
726738
app
727739
end
@@ -739,7 +751,7 @@ def createRackServletWrapper(runtime, rackup, filename)
739751
app_init_secs = 0.2
740752
allow(@factory).to receive(:init)
741753
expect(@factory).to receive(:newApplication).twice do
742-
app = double "app"
754+
app = Synchronized.new(double("app").as_null_object)
743755
expect(app).to receive(:init) { sleep(app_init_secs) }
744756
app
745757
end
@@ -768,7 +780,7 @@ def createRackServletWrapper(runtime, rackup, filename)
768780
app_init_secs = 0.1
769781
allow(@factory).to receive(:init)
770782
expect(@factory).to receive(:newApplication).twice do
771-
app = double "app (new)"
783+
app = Synchronized.new(double("app (new)").as_null_object)
772784
expect(app).to receive(:init) { sleep(app_init_secs) }
773785
app
774786
end
@@ -784,7 +796,9 @@ def createRackServletWrapper(runtime, rackup, filename)
784796

785797
app_get_secs = 0.15
786798
expect(@factory).to receive(:getApplication).twice do
787-
app = double "app (get)"; sleep(app_get_secs); app
799+
app = Synchronized.new(double("app (get)").as_null_object)
800+
sleep(app_get_secs)
801+
app
788802
end
789803

790804
start = java.lang.System.currentTimeMillis
@@ -799,33 +813,37 @@ def createRackServletWrapper(runtime, rackup, filename)
799813
end
800814

801815
it "initializes initial runtimes in parallel (with wait set to false)" do
816+
app_init_secs = 0.15
802817
allow(@factory).to receive(:init)
803818
allow(@factory).to receive(:newApplication) do
804-
app = double "app"
805-
allow(app).to receive(:init) do
806-
sleep(0.15)
807-
end
819+
app = Synchronized.new(double("app").as_null_object)
820+
allow(app).to receive(:init) { sleep(app_init_secs) }
808821
app
809822
end
823+
824+
init_threads = 4
825+
init_runtimes = 6
810826
allow(@rack_config).to receive(:getBooleanProperty).with("jruby.runtime.init.wait").and_return false
811-
allow(@rack_config).to receive(:getInitialRuntimes).and_return 6
827+
allow(@rack_config).to receive(:getRuntimeInitThreads).and_return init_threads
828+
allow(@rack_config).to receive(:getInitialRuntimes).and_return init_runtimes
812829
allow(@rack_config).to receive(:getMaximumRuntimes).and_return 8
813830

831+
expected_initial_init_time = 1.1 * (init_runtimes.to_f / init_threads.to_f).ceil * app_init_secs # 10% margin
814832
@pooling_factory.init(@rack_context)
815-
sleep(0.10)
816-
expect(@pooling_factory.getApplicationPool.size).to be < 6
817-
sleep(0.9)
818-
expect(@pooling_factory.getApplicationPool.size).to be >= 6
833+
sleep(app_init_secs) # wait for at some (but not possibly all) to finish
834+
expect(@pooling_factory.getApplicationPool.size).to be < init_runtimes
835+
sleep(expected_initial_init_time - app_init_secs) # remaining time
836+
expect(@pooling_factory.getApplicationPool.size).to be >= init_runtimes
819837

820838
expect(@pooling_factory.getManagedApplications).to_not be_empty
821-
expect(@pooling_factory.getManagedApplications.size).to eql 6
839+
expect(@pooling_factory.getManagedApplications.size).to eql init_runtimes
822840
end
823841

824842
it "throws from init when application initialization in thread failed" do
825843
app_init_secs = 0.05
826844
allow(@factory).to receive(:init)
827845
allow(@factory).to receive(:newApplication) do
828-
app = double "app"
846+
app = Synchronized.new(double("app").as_null_object)
829847
allow(app).to receive(:init) { sleep(app_init_secs); raise "app.init raising" }
830848
app
831849
end

0 commit comments

Comments
 (0)