Skip to content

Commit 97eb7d2

Browse files
committed
[tests] Fix threading issue with parallel testing with non-threadsafe rspec mocks
(cherry picked from commit dcdecf1)
1 parent 7007f74 commit 97eb7d2

1 file changed

Lines changed: 39 additions & 21 deletions

File tree

src/spec/ruby/rack/application_spec.rb

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

576576
describe org.jruby.rack.PoolingRackApplicationFactory do
577577

578+
# Workaround rspec mocks/proxies not being thread-safe which causes occasional failures
579+
class Synchronized
580+
def initialize(obj)
581+
@delegate = obj
582+
@lock = Mutex.new
583+
end
584+
585+
def method_missing(name, *args, &block)
586+
@lock.synchronize { @delegate.send(name, *args, &block) }
587+
end
588+
end
589+
578590
before :each do
579-
@factory = double "factory"
591+
@factory = Synchronized.new(double("factory").as_null_object)
580592
@pooling_factory = org.jruby.rack.PoolingRackApplicationFactory.new @factory
581593
@pooling_factory.context = @rack_context
582594
end
@@ -626,7 +638,7 @@ def createRackServletWrapper(runtime, rackup, filename)
626638
it "creates applications during initialization according to the jruby.min.runtimes context parameter" do
627639
allow(@factory).to receive(:init)
628640
allow(@factory).to receive(:newApplication) do
629-
app = double "app"
641+
app = Synchronized.new(double("app").as_null_object)
630642
expect(app).to receive(:init)
631643
app
632644
end
@@ -659,7 +671,7 @@ def createRackServletWrapper(runtime, rackup, filename)
659671
it "forces the maximum size to be greater or equal to the initial size" do
660672
allow(@factory).to receive(:init)
661673
allow(@factory).to receive(:newApplication) do
662-
app = double "app"
674+
app = Synchronized.new(double("app").as_null_object)
663675
expect(app).to receive(:init)
664676
app
665677
end
@@ -673,15 +685,15 @@ def createRackServletWrapper(runtime, rackup, filename)
673685
end
674686

675687
it "retrieves the error application from the delegate factory" do
676-
app = double("app")
688+
app = double "app"
677689
expect(@factory).to receive(:getErrorApplication).and_return app
678690
expect(@pooling_factory.getErrorApplication).to eq app
679691
end
680692

681693
it "waits till initial runtimes get initialized (with wait set to true)" do
682694
allow(@factory).to receive(:init)
683695
allow(@factory).to receive(:newApplication) do
684-
app = double "app"
696+
app = Synchronized.new(double("app").as_null_object)
685697
allow(app).to receive(:init) do
686698
sleep(0.05)
687699
end
@@ -701,7 +713,7 @@ def createRackServletWrapper(runtime, rackup, filename)
701713
allow(@factory).to receive(:init)
702714
app_count = java.util.concurrent.atomic.AtomicInteger.new(0)
703715
allow(@factory).to receive(:newApplication) do
704-
app = double "app"
716+
app = Synchronized.new(double("app").as_null_object)
705717
allow(app).to receive(:init) do
706718
if app_count.addAndGet(1) == 2
707719
raise org.jruby.rack.RackInitializationException.new('failed app init')
@@ -739,7 +751,7 @@ def createRackServletWrapper(runtime, rackup, filename)
739751
app_init_secs = 0.2
740752
allow(@factory).to receive(:init)
741753
allow(@factory).to receive(:newApplication) do
742-
app = double "app"
754+
app = Synchronized.new(double("app").as_null_object)
743755
allow(app).to receive(:init) { sleep(app_init_secs) }
744756
app
745757
end
@@ -757,7 +769,7 @@ def createRackServletWrapper(runtime, rackup, filename)
757769
app_init_secs = 0.2
758770
allow(@factory).to receive(:init)
759771
expect(@factory).to receive(:newApplication).twice do
760-
app = double "app"
772+
app = Synchronized.new(double("app").as_null_object)
761773
expect(app).to receive(:init) { sleep(app_init_secs) }
762774
app
763775
end
@@ -786,7 +798,7 @@ def createRackServletWrapper(runtime, rackup, filename)
786798
app_init_secs = 0.1
787799
allow(@factory).to receive(:init)
788800
expect(@factory).to receive(:newApplication).twice do
789-
app = double "app (new)"
801+
app = Synchronized.new(double("app (new)").as_null_object)
790802
expect(app).to receive(:init) { sleep(app_init_secs) }
791803
app
792804
end
@@ -802,7 +814,9 @@ def createRackServletWrapper(runtime, rackup, filename)
802814

803815
app_get_secs = 0.15
804816
expect(@factory).to receive(:getApplication).twice do
805-
app = double "app (get)"; sleep(app_get_secs); app
817+
app = Synchronized.new(double("app (get)").as_null_object)
818+
sleep(app_get_secs)
819+
app
806820
end
807821

808822
start = java.lang.System.currentTimeMillis
@@ -817,33 +831,37 @@ def createRackServletWrapper(runtime, rackup, filename)
817831
end
818832

819833
it "initializes initial runtimes in parallel (with wait set to false)" do
834+
app_init_secs = 0.15
820835
allow(@factory).to receive(:init)
821836
allow(@factory).to receive(:newApplication) do
822-
app = double "app"
823-
allow(app).to receive(:init) do
824-
sleep(0.15)
825-
end
837+
app = Synchronized.new(double("app").as_null_object)
838+
allow(app).to receive(:init) { sleep(app_init_secs) }
826839
app
827840
end
841+
842+
init_threads = 4
843+
init_runtimes = 6
828844
allow(@rack_config).to receive(:getBooleanProperty).with("jruby.runtime.init.wait").and_return false
829-
allow(@rack_config).to receive(:getInitialRuntimes).and_return 6
845+
allow(@rack_config).to receive(:getRuntimeInitThreads).and_return init_threads
846+
allow(@rack_config).to receive(:getInitialRuntimes).and_return init_runtimes
830847
allow(@rack_config).to receive(:getMaximumRuntimes).and_return 8
831848

849+
expected_initial_init_time = 1.1 * (init_runtimes.to_f / init_threads.to_f).ceil * app_init_secs # 10% margin
832850
@pooling_factory.init(@rack_context)
833-
sleep(0.10)
834-
expect(@pooling_factory.getApplicationPool.size).to be < 6
835-
sleep(0.9)
836-
expect(@pooling_factory.getApplicationPool.size).to be >= 6
851+
sleep(app_init_secs) # wait for at some (but not possibly all) to finish
852+
expect(@pooling_factory.getApplicationPool.size).to be < init_runtimes
853+
sleep(expected_initial_init_time - app_init_secs) # remaining time
854+
expect(@pooling_factory.getApplicationPool.size).to be >= init_runtimes
837855

838856
expect(@pooling_factory.getManagedApplications).to_not be_empty
839-
expect(@pooling_factory.getManagedApplications.size).to eql 6
857+
expect(@pooling_factory.getManagedApplications.size).to eql init_runtimes
840858
end
841859

842860
it "throws from init when application initialization in thread failed" do
843861
app_init_secs = 0.05
844862
allow(@factory).to receive(:init)
845863
allow(@factory).to receive(:newApplication) do
846-
app = double "app"
864+
app = Synchronized.new(double("app").as_null_object)
847865
allow(app).to receive(:init) { sleep(app_init_secs); raise "app.init raising" }
848866
app
849867
end

0 commit comments

Comments
 (0)