@@ -557,8 +557,20 @@ def createRackServletWrapper(runtime, rackup, filename)
557557
558558describe 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