Skip to content

Commit 28d81b1

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

File tree

1 file changed

+37
-21
lines changed

1 file changed

+37
-21
lines changed

src/spec/ruby/rack/application_spec.rb

Lines changed: 37 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")
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")
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")
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")
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")
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")
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")
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)")
772784
expect(app).to receive(:init) { sleep(app_init_secs) }
773785
app
774786
end
@@ -784,7 +796,7 @@ 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)"); sleep(app_get_secs); app
788800
end
789801

790802
start = java.lang.System.currentTimeMillis
@@ -799,33 +811,37 @@ def createRackServletWrapper(runtime, rackup, filename)
799811
end
800812

801813
it "initializes initial runtimes in parallel (with wait set to false)" do
814+
app_init_secs = 0.15
802815
allow(@factory).to receive(:init)
803816
allow(@factory).to receive(:newApplication) do
804-
app = double "app"
805-
allow(app).to receive(:init) do
806-
sleep(0.15)
807-
end
817+
app = Synchronized.new(double "app")
818+
allow(app).to receive(:init) { sleep(app_init_secs) }
808819
app
809820
end
821+
822+
init_threads = 4
823+
init_runtimes = 6
810824
allow(@rack_config).to receive(:getBooleanProperty).with("jruby.runtime.init.wait").and_return false
811-
allow(@rack_config).to receive(:getInitialRuntimes).and_return 6
825+
allow(@rack_config).to receive(:getRuntimeInitThreads).and_return init_threads
826+
allow(@rack_config).to receive(:getInitialRuntimes).and_return init_runtimes
812827
allow(@rack_config).to receive(:getMaximumRuntimes).and_return 8
813828

829+
expected_initial_init_time = 1.1 * (init_runtimes.to_f / init_threads.to_f).ceil * app_init_secs # 10% margin
814830
@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
831+
sleep(app_init_secs) # wait for at some (but not possibly all) to finish
832+
expect(@pooling_factory.getApplicationPool.size).to be < init_runtimes
833+
sleep(expected_initial_init_time - app_init_secs) # remaining time
834+
expect(@pooling_factory.getApplicationPool.size).to be >= init_runtimes
819835

820836
expect(@pooling_factory.getManagedApplications).to_not be_empty
821-
expect(@pooling_factory.getManagedApplications.size).to eql 6
837+
expect(@pooling_factory.getManagedApplications.size).to eql init_runtimes
822838
end
823839

824840
it "throws from init when application initialization in thread failed" do
825841
app_init_secs = 0.05
826842
allow(@factory).to receive(:init)
827843
allow(@factory).to receive(:newApplication) do
828-
app = double "app"
844+
app = Synchronized.new(double "app")
829845
allow(app).to receive(:init) { sleep(app_init_secs); raise "app.init raising" }
830846
app
831847
end

0 commit comments

Comments
 (0)