passenger 3.9.2.beta → 4.0.0.rc4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of passenger might be problematic. Click here for more details.

Files changed (159) hide show
  1. data/.travis.yml +3 -0
  2. data/NEWS +77 -7
  3. data/README.md +3 -11
  4. data/bin/passenger-install-apache2-module +24 -20
  5. data/bin/passenger-install-nginx-module +25 -23
  6. data/build/agents.rb +11 -0
  7. data/build/apache2.rb +9 -5
  8. data/build/basics.rb +37 -30
  9. data/build/common_library.rb +4 -1
  10. data/build/cplusplus_support.rb +5 -5
  11. data/build/cxx_tests.rb +28 -8
  12. data/build/integration_tests.rb +6 -3
  13. data/build/nginx.rb +3 -3
  14. data/build/packaging.rb +95 -57
  15. data/build/ruby_extension.rb +34 -21
  16. data/build/ruby_tests.rb +4 -2
  17. data/build/test_basics.rb +1 -1
  18. data/dev/run_travis.sh +36 -1
  19. data/doc/Users guide Apache.html +425 -308
  20. data/doc/Users guide Apache.idmap.txt +78 -70
  21. data/doc/Users guide Apache.index.sqlite3 +0 -0
  22. data/doc/Users guide Apache.txt +33 -92
  23. data/doc/Users guide Nginx.html +519 -220
  24. data/doc/Users guide Nginx.idmap.txt +78 -60
  25. data/doc/Users guide Nginx.txt +115 -26
  26. data/doc/Users guide Standalone.html +8 -2
  27. data/doc/users_guide_snippets/analysis_and_system_maintenance.txt +1 -7
  28. data/doc/users_guide_snippets/installation.txt +167 -22
  29. data/doc/users_guide_snippets/rackup_specifications.txt +4 -0
  30. data/doc/users_guide_snippets/since_version.txt +1 -0
  31. data/doc/users_guide_snippets/support_information.txt +3 -7
  32. data/doc/users_guide_snippets/tips.txt +0 -24
  33. data/ext/apache2/Configuration.cpp +11 -33
  34. data/ext/apache2/Configuration.hpp +3 -18
  35. data/ext/apache2/DirectoryMapper.h +20 -70
  36. data/ext/apache2/Hooks.cpp +2 -2
  37. data/ext/common/AgentsStarter.cpp +0 -2
  38. data/ext/common/AgentsStarter.h +0 -1
  39. data/ext/common/AgentsStarter.hpp +1 -3
  40. data/ext/common/ApplicationPool2/AppTypes.cpp +74 -0
  41. data/ext/common/ApplicationPool2/AppTypes.h +202 -0
  42. data/ext/common/ApplicationPool2/Common.h +12 -10
  43. data/ext/common/ApplicationPool2/DirectSpawner.h +256 -0
  44. data/ext/common/ApplicationPool2/DummySpawner.h +90 -0
  45. data/ext/common/ApplicationPool2/Group.h +311 -94
  46. data/ext/common/ApplicationPool2/Implementation.cpp +405 -145
  47. data/ext/common/ApplicationPool2/Options.h +24 -26
  48. data/ext/common/ApplicationPool2/PipeWatcher.h +20 -13
  49. data/ext/common/ApplicationPool2/Pool.h +326 -183
  50. data/ext/common/ApplicationPool2/Process.h +205 -55
  51. data/ext/common/ApplicationPool2/README.md +1 -1
  52. data/ext/common/ApplicationPool2/Session.h +21 -10
  53. data/ext/common/ApplicationPool2/SmartSpawner.h +801 -0
  54. data/ext/common/ApplicationPool2/Spawner.h +141 -1149
  55. data/ext/common/ApplicationPool2/SpawnerFactory.h +132 -0
  56. data/ext/common/ApplicationPool2/SuperGroup.h +146 -223
  57. data/ext/common/Constants.h +4 -2
  58. data/ext/common/Exceptions.h +23 -1
  59. data/ext/common/Logging.cpp +17 -6
  60. data/ext/common/Logging.h +37 -7
  61. data/ext/common/ResourceLocator.h +1 -1
  62. data/ext/common/Utils.cpp +49 -1
  63. data/ext/common/Utils.h +13 -4
  64. data/ext/common/{AnsiColorConstants.h → Utils/AnsiColorConstants.h} +0 -0
  65. data/ext/common/{BCrypt.cpp → Utils/BCrypt.cpp} +0 -0
  66. data/ext/common/{BCrypt.h → Utils/BCrypt.h} +0 -0
  67. data/ext/common/{Blowfish.c → Utils/Blowfish.c} +0 -0
  68. data/ext/common/{Blowfish.h → Utils/Blowfish.h} +0 -0
  69. data/ext/common/Utils/CachedFileStat.hpp +27 -25
  70. data/ext/common/Utils/Curl.h +184 -0
  71. data/ext/common/{HttpConstants.h → Utils/HttpConstants.h} +3 -0
  72. data/ext/common/Utils/IOUtils.cpp +6 -2
  73. data/ext/common/{IniFile.h → Utils/IniFile.h} +0 -0
  74. data/ext/common/Utils/LargeFiles.cpp +30 -0
  75. data/ext/common/Utils/LargeFiles.h +40 -0
  76. data/ext/common/Utils/StrIntUtils.cpp +72 -8
  77. data/ext/common/Utils/StrIntUtils.h +24 -2
  78. data/ext/common/Utils/StringMap.h +12 -2
  79. data/ext/common/Utils/VariantMap.h +51 -2
  80. data/ext/common/Utils/jsoncpp.cpp +1 -1
  81. data/ext/common/agents/Base.cpp +147 -11
  82. data/ext/common/agents/HelperAgent/AgentOptions.h +14 -6
  83. data/ext/common/agents/HelperAgent/Main.cpp +79 -19
  84. data/ext/common/agents/HelperAgent/RequestHandler.h +36 -16
  85. data/ext/common/agents/LoggingAgent/LoggingServer.h +3 -5
  86. data/ext/common/agents/LoggingAgent/Main.cpp +2 -4
  87. data/ext/common/agents/LoggingAgent/RemoteSender.h +18 -24
  88. data/ext/common/agents/SpawnPreparer.cpp +7 -0
  89. data/ext/common/agents/Watchdog/Main.cpp +96 -38
  90. data/ext/nginx/Configuration.c +26 -22
  91. data/ext/nginx/Configuration.h +4 -2
  92. data/ext/nginx/ContentHandler.c +23 -52
  93. data/ext/nginx/ContentHandler.h +5 -11
  94. data/ext/nginx/config +10 -3
  95. data/ext/nginx/ngx_http_passenger_module.c +21 -6
  96. data/ext/nginx/ngx_http_passenger_module.h +4 -1
  97. data/ext/oxt/dynamic_thread_group.hpp +9 -1
  98. data/ext/oxt/system_calls.cpp +2 -2
  99. data/ext/ruby/extconf.rb +2 -1
  100. data/helper-scripts/backtrace-sanitizer.rb +2 -0
  101. data/helper-scripts/wsgi-loader.py +54 -21
  102. data/lib/phusion_passenger.rb +5 -3
  103. data/lib/phusion_passenger/abstract_installer.rb +18 -41
  104. data/lib/phusion_passenger/admin_tools/memory_stats.rb +2 -2
  105. data/lib/phusion_passenger/admin_tools/server_instance.rb +2 -2
  106. data/lib/phusion_passenger/common_library.rb +23 -3
  107. data/lib/phusion_passenger/debug_logging.rb +10 -3
  108. data/lib/phusion_passenger/packaging.rb +1 -0
  109. data/lib/phusion_passenger/platform_info.rb +113 -115
  110. data/lib/phusion_passenger/platform_info/compiler.rb +224 -134
  111. data/lib/phusion_passenger/platform_info/cxx_portability.rb +143 -0
  112. data/lib/phusion_passenger/platform_info/depcheck.rb +371 -0
  113. data/lib/phusion_passenger/platform_info/depcheck_specs/apache2.rb +124 -0
  114. data/lib/phusion_passenger/platform_info/depcheck_specs/compiler_toolchain.rb +97 -0
  115. data/lib/phusion_passenger/platform_info/depcheck_specs/gems.rb +39 -0
  116. data/lib/phusion_passenger/platform_info/depcheck_specs/libs.rb +118 -0
  117. data/lib/phusion_passenger/platform_info/depcheck_specs/ruby.rb +137 -0
  118. data/lib/phusion_passenger/platform_info/depcheck_specs/utilities.rb +15 -0
  119. data/lib/phusion_passenger/platform_info/operating_system.rb +6 -5
  120. data/lib/phusion_passenger/platform_info/ruby.rb +45 -34
  121. data/lib/phusion_passenger/request_handler.rb +35 -22
  122. data/lib/phusion_passenger/request_handler/thread_handler.rb +5 -6
  123. data/lib/phusion_passenger/ruby_core_enhancements.rb +7 -1
  124. data/lib/phusion_passenger/standalone/runtime_installer.rb +43 -34
  125. data/lib/phusion_passenger/utils/robust_interruption.rb +34 -18
  126. data/passenger.gemspec +25 -0
  127. data/resources/templates/standalone/config.erb +3 -1
  128. data/test/config.json.travis +2 -2
  129. data/test/cxx/ApplicationPool2/DirectSpawnerTest.cpp +37 -5
  130. data/test/cxx/ApplicationPool2/PoolTest.cpp +143 -50
  131. data/test/cxx/ApplicationPool2/ProcessTest.cpp +8 -0
  132. data/test/cxx/ApplicationPool2/SmartSpawnerTest.cpp +28 -17
  133. data/test/cxx/ApplicationPool2/SpawnerTestCases.cpp +31 -26
  134. data/test/cxx/RequestHandlerTest.cpp +17 -1
  135. data/test/cxx/UtilsTest.cpp +84 -10
  136. data/test/integration_tests/apache2_tests.rb +49 -163
  137. data/test/integration_tests/hello_world_wsgi_spec.rb +2 -2
  138. data/test/integration_tests/mycook_spec.rb +1 -1
  139. data/test/integration_tests/nginx_tests.rb +37 -19
  140. data/test/ruby/request_handler_spec.rb +1 -0
  141. data/test/ruby/spec_helper.rb +52 -1
  142. data/test/stub/nginx/nginx.conf.erb +2 -0
  143. data/test/stub/rack/start.rb +5 -0
  144. data/test/stub/rails3.0/Gemfile.lock +30 -30
  145. data/test/stub/rails3.1/Gemfile +1 -1
  146. data/test/stub/rails3.1/Gemfile.lock +3 -3
  147. data/test/stub/rails3.2/Gemfile +1 -1
  148. data/test/stub/rails3.2/Gemfile.lock +4 -4
  149. data/test/stub/rails_apps/2.3/mycook/app/controllers/welcome_controller.rb +1 -1
  150. data/test/stub/rails_apps/2.3/mycook/app/helpers/recipes_helper.rb +2 -0
  151. data/test/stub/rails_apps/2.3/mycook/app/helpers/test_helper.rb +2 -0
  152. data/test/stub/rails_apps/2.3/mycook/app/helpers/uploads_helper.rb +2 -0
  153. data/test/stub/rails_apps/2.3/mycook/app/helpers/welcome_helper.rb +2 -0
  154. data/test/support/nginx_controller.rb +2 -1
  155. metadata +160 -156
  156. data/build/gempackagetask.rb +0 -99
  157. data/build/packagetask.rb +0 -186
  158. data/ext/common/StringListCreator.h +0 -83
  159. data/lib/phusion_passenger/dependencies.rb +0 -657
@@ -35,11 +35,13 @@ namespace tut {
35
35
  createServerInstanceDirAndGeneration(serverInstanceDir, generation);
36
36
  retainSessions = false;
37
37
  spawnerConfig = make_shared<SpawnerConfig>();
38
- spawnerFactory = make_shared<SpawnerFactory>(bg.safe, *resourceLocator, generation,
39
- RandomGeneratorPtr(), spawnerConfig);
38
+ spawnerFactory = make_shared<SpawnerFactory>(bg.safe, *resourceLocator,
39
+ generation, spawnerConfig);
40
40
  pool = make_shared<Pool>(bg.safe.get(), spawnerFactory);
41
+ pool->initialize();
41
42
  bg.start();
42
43
  callback = boost::bind(&ApplicationPool2_PoolTest::_callback, this, _1, _2);
44
+ setLogLevel(LVL_ERROR); // TODO: change to LVL_WARN
43
45
  }
44
46
 
45
47
  ~ApplicationPool2_PoolTest() {
@@ -47,12 +49,12 @@ namespace tut {
47
49
  // additional code that depend on other fields in this
48
50
  // class.
49
51
  TRACE_POINT();
52
+ clearAllSessions();
53
+ UPDATE_TRACE_POINT();
50
54
  pool->destroy();
51
55
  UPDATE_TRACE_POINT();
52
56
  pool.reset();
53
- UPDATE_TRACE_POINT();
54
- clearAllSessions();
55
- setLogLevel(0);
57
+ setLogLevel(DEFAULT_LOG_LEVEL);
56
58
  SystemTime::releaseAll();
57
59
  }
58
60
 
@@ -559,6 +561,63 @@ namespace tut {
559
561
  ensure_equals(currentSession->getProcess()->pid, 3);
560
562
  ensure_equals(group->getWaitlist.size(), 0u);
561
563
  }
564
+
565
+ TEST_METHOD(12) {
566
+ // Test shutting down.
567
+ ensureMinProcesses(2);
568
+ ensure(pool->detachSuperGroupByName("stub/rack"));
569
+ ensure_equals(pool->getSuperGroupCount(), 0u);
570
+ }
571
+
572
+ TEST_METHOD(13) {
573
+ // Test shutting down while Group is restarting.
574
+ initPoolDebugging();
575
+ debug->messages->send("Proceed with spawn loop iteration 1");
576
+ ensureMinProcesses(1);
577
+
578
+ ensure_equals(pool->restartGroupsByAppRoot("stub/rack"), 1u);
579
+ debug->debugger->recv("About to end restarting");
580
+ ensure(pool->detachSuperGroupByName("stub/rack"));
581
+ ensure_equals(pool->getSuperGroupCount(), 0u);
582
+ }
583
+
584
+ TEST_METHOD(14) {
585
+ // Test shutting down while Group is spawning.
586
+ initPoolDebugging();
587
+ Options options = createOptions();
588
+
589
+ pool->asyncGet(options, callback);
590
+ debug->debugger->recv("Begin spawn loop iteration 1");
591
+ ensure(pool->detachSuperGroupByName("stub/rack"));
592
+ ensure_equals(pool->getSuperGroupCount(), 0u);
593
+ }
594
+
595
+ TEST_METHOD(15) {
596
+ // Test shutting down while SuperGroup is initializing.
597
+ initPoolDebugging();
598
+ debug->spawning = false;
599
+ debug->superGroup = true;
600
+ Options options = createOptions();
601
+
602
+ pool->asyncGet(options, callback);
603
+ debug->debugger->recv("About to finish SuperGroup initialization");
604
+ ensure(pool->detachSuperGroupByName("stub/rack"));
605
+ ensure_equals(pool->getSuperGroupCount(), 0u);
606
+ }
607
+
608
+ TEST_METHOD(16) {
609
+ // Test shutting down while SuperGroup is restarting.
610
+ initPoolDebugging();
611
+ debug->spawning = false;
612
+ debug->superGroup = true;
613
+ debug->messages->send("Proceed with initializing SuperGroup");
614
+ ensureMinProcesses(1);
615
+
616
+ ensure_equals(pool->restartSuperGroupsByAppRoot("stub/rack"), 1u);
617
+ debug->debugger->recv("About to finish SuperGroup restart");
618
+ ensure(pool->detachSuperGroupByName("stub/rack"));
619
+ ensure_equals(pool->getSuperGroupCount(), 0u);
620
+ }
562
621
 
563
622
 
564
623
  /*********** Test asyncGet() behavior on multiple SuperGroups,
@@ -598,7 +657,6 @@ namespace tut {
598
657
  );
599
658
 
600
659
  ensure_equals(pool->getProcessCount(), 2u);
601
- ensure(!superGroup1->detached());
602
660
  ensure_equals(superGroup1->getProcessCount(), 0u);
603
661
  }
604
662
 
@@ -637,7 +695,6 @@ namespace tut {
637
695
  );
638
696
 
639
697
  ensure_equals(pool->getProcessCount(), 2u);
640
- ensure(!superGroup1->detached());
641
698
  ensure_equals(superGroup1->getProcessCount(), 0u);
642
699
  }
643
700
 
@@ -665,8 +722,7 @@ namespace tut {
665
722
  pool->asyncGet(options, callback);
666
723
  {
667
724
  LockGuard l(pool->syncher);
668
- ensure("(1)", !session1->getProcess()->detached());
669
- ensure("(2)", !fooGroup->detached());
725
+ ensure("(1)", session1->getProcess()->isAlive());
670
726
  ensure_equals("(3)", fooGroup->getWaitlist.size(), 1u);
671
727
  }
672
728
 
@@ -676,8 +732,7 @@ namespace tut {
676
732
  SessionPtr session3 = pool->get(options, &ticket);
677
733
  {
678
734
  LockGuard l(pool->syncher);
679
- ensure("(4)", session1->getProcess()->detached());
680
- ensure("(5)", !fooGroup->detached());
735
+ ensure("(4)", !session1->getProcess()->isAlive());
681
736
  ensure_equals("(6)", fooGroup->getWaitlist.size(), 1u);
682
737
  ensure_equals("(7)", pool->getWaitlist.size(), 0u);
683
738
  }
@@ -706,8 +761,9 @@ namespace tut {
706
761
  result = number == 1;
707
762
  );
708
763
 
764
+ ProcessPtr process = currentSession->getProcess();
709
765
  pool->detachProcess(currentSession->getProcess());
710
- ensure(currentSession->getProcess()->detached());
766
+ ensure(!process->isAlive());
711
767
  EVENTUALLY(5,
712
768
  result = pool->getProcessCount() == 2;
713
769
  );
@@ -806,7 +862,7 @@ namespace tut {
806
862
  pool->detachProcess(process);
807
863
  LockGuard l(pool->syncher);
808
864
  ensure_equals(pool->superGroups.size(), 1u);
809
- ensure(!superGroup->detached());
865
+ ensure(superGroup->isAlive());
810
866
  ensure(!superGroup->garbageCollectable());
811
867
  }
812
868
 
@@ -821,12 +877,12 @@ namespace tut {
821
877
  pool->disableProcess(processes[0]->gupid), DR_SUCCESS);
822
878
 
823
879
  LockGuard l(pool->syncher);
824
- ensure(!processes[0]->detached());
880
+ ensure(processes[0]->isAlive());
825
881
  ensure_equals("Process is disabled",
826
882
  processes[0]->enabled,
827
883
  Process::DISABLED);
828
884
  ensure("Other processes are not affected",
829
- !processes[1]->detached());
885
+ processes[1]->isAlive());
830
886
  ensure_equals("Other processes are not affected",
831
887
  processes[1]->enabled, Process::ENABLED);
832
888
  }
@@ -949,19 +1005,19 @@ namespace tut {
949
1005
  }
950
1006
  }
951
1007
 
952
- // asyncGet() should not select a disabling process if there are enabled processes.
953
- // asyncGet() should not select a disabling process when non-rolling restarting.
954
- // asyncGet() should select a disabling process if there are no enabled processes
955
- // in the group. If this happens then asyncGet() will also spawn a new process.
956
- // asyncGet() should not select a disabled process.
1008
+ // TODO: asyncGet() should not select a disabling process if there are enabled processes.
1009
+ // TODO: asyncGet() should not select a disabling process when non-rolling restarting.
1010
+ // TODO: asyncGet() should select a disabling process if there are no enabled processes
1011
+ // in the group. If this happens then asyncGet() will also spawn a new process.
1012
+ // TODO: asyncGet() should not select a disabled process.
957
1013
 
958
- // If there are no enabled processes and all disabling processes are at full
959
- // utilization, and the process that was being spawned becomes available
960
- // earlier than any of the disabling processes, then the newly spawned process
961
- // should handle the request.
1014
+ // TODO: If there are no enabled processes and all disabling processes are at full
1015
+ // utilization, and the process that was being spawned becomes available
1016
+ // earlier than any of the disabling processes, then the newly spawned process
1017
+ // should handle the request.
962
1018
 
963
- // A disabling process becomes disabled as soon as it's done with
964
- // all its request.
1019
+ // TODO: A disabling process becomes disabled as soon as it's done with
1020
+ // all its request.
965
1021
 
966
1022
  TEST_METHOD(50) {
967
1023
  // Disabling a process that's already being disabled should result in the
@@ -983,9 +1039,9 @@ namespace tut {
983
1039
  ensure_equals(code, (int) DR_SUCCESS);
984
1040
  }
985
1041
 
986
- // Enabling a process that's disabled succeeds immediately.
987
- // Enabling a process that's disabling succeeds immediately. The disable
988
- // callbacks will be called with DR_CANCELED.
1042
+ // TODO: Enabling a process that's disabled succeeds immediately.
1043
+ // TODO: Enabling a process that's disabling succeeds immediately. The disable
1044
+ // callbacks will be called with DR_CANCELED.
989
1045
 
990
1046
 
991
1047
  /*********** Other tests ***********/
@@ -1010,7 +1066,8 @@ namespace tut {
1010
1066
 
1011
1067
  ensure_equals(pool->getProcessCount(), 2u);
1012
1068
  ensure(pool->atFullCapacity());
1013
- pool->detachSuperGroup(pool->getSuperGroup("test"));
1069
+ clearAllSessions();
1070
+ pool->detachSuperGroupByName("test");
1014
1071
  ensure(!pool->atFullCapacity());
1015
1072
  }
1016
1073
 
@@ -1120,7 +1177,6 @@ namespace tut {
1120
1177
  options.appRoot = "tmp.wsgi";
1121
1178
  options.appType = "wsgi";
1122
1179
  options.spawnMethod = "direct";
1123
- ProcessPtr process;
1124
1180
  pool->setMax(1);
1125
1181
 
1126
1182
  // Send normal request.
@@ -1171,7 +1227,7 @@ namespace tut {
1171
1227
 
1172
1228
  ensure(currentException != NULL);
1173
1229
  shared_ptr<SpawnException> e = dynamic_pointer_cast<SpawnException>(currentException);
1174
- ensure_equals(e->getErrorPage(), "Something went wrong!");
1230
+ ensure(e->getErrorPage().find("Something went wrong!") != string::npos);
1175
1231
  }
1176
1232
 
1177
1233
  TEST_METHOD(68) {
@@ -1210,7 +1266,7 @@ namespace tut {
1210
1266
  EVENTUALLY(5,
1211
1267
  result = pool->getProcessCount() == 2;
1212
1268
  );
1213
- EVENTUALLY(2,
1269
+ EVENTUALLY(5,
1214
1270
  result = !pool->isSpawning();
1215
1271
  );
1216
1272
  SHOULD_NEVER_HAPPEN(500,
@@ -1302,31 +1358,48 @@ namespace tut {
1302
1358
  }
1303
1359
 
1304
1360
  TEST_METHOD(72) {
1305
- // If we restart while spawning is in progress, then the spawn
1306
- // loop will exit as soon as it has detected that we're restarting.
1361
+ // If we restart while spawning is in progress, and the restart
1362
+ // finishes before the process is done spawning, then that
1363
+ // process will not be attached and the original spawn loop will
1364
+ // abort. A new spawn loop will start to ensure that resource
1365
+ // constraints are met.
1307
1366
  TempDirCopy dir("stub/wsgi", "tmp.wsgi");
1308
1367
  initPoolDebugging();
1309
1368
  Options options = createOptions();
1310
1369
  options.appRoot = "tmp.wsgi";
1311
1370
  options.minProcesses = 3;
1312
1371
 
1313
- // Trigger spawn loop and freeze it at the point where it's spawning a process.
1372
+ // Trigger spawn loop and freeze it at the point where it's spawning
1373
+ // the second process.
1314
1374
  pool->asyncGet(options, callback);
1315
1375
  debug->debugger->recv("Begin spawn loop iteration 1");
1376
+ debug->messages->send("Proceed with spawn loop iteration 1");
1377
+ debug->debugger->recv("Begin spawn loop iteration 2");
1378
+ ensure_equals("(1)", pool->getProcessCount(), 1u);
1316
1379
 
1317
- // Trigger restart, freeze the restart procedure, then let spawn loop continue.
1380
+ // Trigger restart, wait until it's finished.
1318
1381
  touchFile("tmp.wsgi/tmp/restart.txt", 1);
1319
1382
  pool->asyncGet(options, callback);
1320
- debug->debugger->recv("About to end restarting");
1321
- debug->messages->send("Proceed with spawn loop iteration 1");
1383
+ debug->messages->send("Finish restarting");
1384
+ debug->debugger->recv("Restarting done");
1385
+ ensure_equals("(2)", pool->getProcessCount(), 0u);
1322
1386
 
1323
- // The spawn loop will succeed at spawning this process.
1324
- // After the spawn loop attaches the process, it should detect the
1325
- // restart and stop, so that it never spawns the second and third processes.
1387
+ // The restarter should have created a new spawn loop and
1388
+ // instructed the old one to stop.
1389
+ debug->debugger->recv("Begin spawn loop iteration 3");
1390
+
1391
+ // We let the old spawn loop continue, which should drop
1392
+ // the second process and abort.
1393
+ debug->messages->send("Proceed with spawn loop iteration 2");
1326
1394
  debug->debugger->recv("Spawn loop done");
1327
- ensure_equals(debug->debugger->peek("At spawn loop iteration 2"), MessagePtr());
1328
- ensure_equals(debug->debugger->peek("At spawn loop iteration 3"), MessagePtr());
1329
- ensure_equals("(1)", pool->getProcessCount(), 1u);
1395
+ ensure_equals("(3)", pool->getProcessCount(), 0u);
1396
+
1397
+ // We let the new spawn loop continue.
1398
+ debug->messages->send("Proceed with spawn loop iteration 3");
1399
+ debug->messages->send("Proceed with spawn loop iteration 4");
1400
+ debug->messages->send("Proceed with spawn loop iteration 5");
1401
+ debug->debugger->recv("Spawn loop done");
1402
+ ensure_equals("(4)", pool->getProcessCount(), 3u);
1330
1403
  }
1331
1404
 
1332
1405
  TEST_METHOD(73) {
@@ -1427,7 +1500,7 @@ namespace tut {
1427
1500
  retainSessions = true;
1428
1501
  pool->asyncGet(options, callback);
1429
1502
  pool->asyncGet(options, callback);
1430
- EVENTUALLY(5,
1503
+ EVENTUALLY(10,
1431
1504
  result = number == 2;
1432
1505
  );
1433
1506
  ensure_equals(pool->getProcessCount(), 2u);
@@ -1446,10 +1519,30 @@ namespace tut {
1446
1519
  }
1447
1520
  }
1448
1521
 
1449
- // Persistent connections.
1450
- // If one closes the session before it has reached EOF, and process's maximum concurrency
1451
- // has already been reached, then the pool should ping the process so that it can detect
1452
- // when the session's connection has been released by the app.
1522
+ // TODO: Persistent connections.
1523
+ // TODO: If one closes the session before it has reached EOF, and process's maximum concurrency
1524
+ // has already been reached, then the pool should ping the process so that it can detect
1525
+ // when the session's connection has been released by the app.
1526
+
1527
+
1528
+ /*********** Test previously discovered bugs ***********/
1529
+
1530
+ TEST_METHOD(76) {
1531
+ // Test detaching, then restarting. This should not violate any invariants.
1532
+ TempDirCopy dir("stub/wsgi", "tmp.wsgi");
1533
+ Options options = createOptions();
1534
+ options.appRoot = "tmp.wsgi";
1535
+ options.appType = "wsgi";
1536
+ options.spawnMethod = "direct";
1537
+
1538
+ SessionPtr session = pool->get(options, &ticket);
1539
+ string gupid = session->getProcess()->gupid;
1540
+ session.reset();
1541
+ pool->detachProcess(gupid);
1542
+ touchFile("tmp.wsgi/tmp/restart.txt", 1);
1543
+ pool->get(options, &ticket).reset();
1544
+ }
1545
+
1453
1546
 
1454
1547
  /*****************************/
1455
1548
  }
@@ -51,6 +51,8 @@ namespace tut {
51
51
  ProcessPtr process = make_shared<Process>(bg.safe,
52
52
  123, "", "", adminSocket[0],
53
53
  errorPipe[0], sockets, 0, 0);
54
+ process->dummy = true;
55
+ process->requiresShutdown = false;
54
56
  ensure_equals(process->utilization(), 0);
55
57
  ensure(!process->atFullCapacity());
56
58
  }
@@ -60,6 +62,8 @@ namespace tut {
60
62
  ProcessPtr process = make_shared<Process>(bg.safe,
61
63
  123, "", "", adminSocket[0],
62
64
  errorPipe[0], sockets, 0, 0);
65
+ process->dummy = true;
66
+ process->requiresShutdown = false;
63
67
  SessionPtr session = process->newSession();
64
68
  SessionPtr session2 = process->newSession();
65
69
  ensure_equals(process->sessions, 2);
@@ -75,6 +79,8 @@ namespace tut {
75
79
  ProcessPtr process = make_shared<Process>(bg.safe,
76
80
  123, "", "", adminSocket[0],
77
81
  errorPipe[0], sockets, 0, 0);
82
+ process->dummy = true;
83
+ process->requiresShutdown = false;
78
84
 
79
85
  // The first 3 newSession() commands check out an idle socket.
80
86
  SessionPtr session1 = process->newSession();
@@ -118,6 +124,8 @@ namespace tut {
118
124
  ProcessPtr process = make_shared<Process>(bg.safe,
119
125
  123, "", "", adminSocket[0],
120
126
  errorPipe[0], sockets, 0, 0);
127
+ process->dummy = true;
128
+ process->requiresShutdown = false;
121
129
  vector<SessionPtr> sessions;
122
130
  for (int i = 0; i < 9; i++) {
123
131
  ensure(!process->atFullCapacity());
@@ -1,5 +1,5 @@
1
1
  #include <TestSupport.h>
2
- #include <ApplicationPool2/Spawner.h>
2
+ #include <ApplicationPool2/SmartSpawner.h>
3
3
  #include <Logging.h>
4
4
  #include <Utils/json.h>
5
5
  #include <unistd.h>
@@ -16,15 +16,24 @@ namespace tut {
16
16
  ServerInstanceDirPtr serverInstanceDir;
17
17
  ServerInstanceDir::GenerationPtr generation;
18
18
  BackgroundEventLoop bg;
19
+ ProcessPtr process;
20
+ PipeWatcher::DataCallback gatherOutput;
21
+ string gatheredOutput;
22
+ boost::mutex gatheredOutputSyncher;
19
23
 
20
24
  ApplicationPool2_SmartSpawnerTest() {
21
25
  createServerInstanceDirAndGeneration(serverInstanceDir, generation);
22
26
  bg.start();
27
+ PipeWatcher::onData = PipeWatcher::DataCallback();
28
+ gatherOutput = boost::bind(&ApplicationPool2_SmartSpawnerTest::_gatherOutput, this, _1, _2);
29
+ setLogLevel(LVL_ERROR); // TODO: should be LVL_WARN
23
30
  }
24
31
 
25
32
  ~ApplicationPool2_SmartSpawnerTest() {
26
- setLogLevel(0);
33
+ setLogLevel(DEFAULT_LOG_LEVEL);
27
34
  unlink("stub/wsgi/passenger_wsgi.pyc");
35
+ Process::maybeShutdown(process);
36
+ PipeWatcher::onData = PipeWatcher::DataCallback();
28
37
  }
29
38
 
30
39
  shared_ptr<SmartSpawner> createSpawner(const Options &options, bool exitImmediately = false) {
@@ -51,6 +60,11 @@ namespace tut {
51
60
  options.loadShellEnvvars = false;
52
61
  return options;
53
62
  }
63
+
64
+ void _gatherOutput(const char *data, unsigned int size) {
65
+ lock_guard<boost::mutex> l(gatheredOutputSyncher);
66
+ gatheredOutput.append(data, size);
67
+ }
54
68
  };
55
69
 
56
70
  DEFINE_TEST_GROUP_WITH_LIMIT(ApplicationPool2_SmartSpawnerTest, 90);
@@ -65,7 +79,7 @@ namespace tut {
65
79
  options.startCommand = "ruby\1" "start.rb";
66
80
  options.startupFile = "start.rb";
67
81
  shared_ptr<SmartSpawner> spawner = createSpawner(options);
68
- spawner->spawn(options);
82
+ spawner->spawn(options)->shutdown();
69
83
 
70
84
  kill(spawner->getPreloaderPid(), SIGTERM);
71
85
  // Give it some time to exit.
@@ -73,7 +87,7 @@ namespace tut {
73
87
 
74
88
  // No exception at next spawn.
75
89
  setLogLevel(-1);
76
- spawner->spawn(options);
90
+ spawner->spawn(options)->shutdown();
77
91
  }
78
92
 
79
93
  TEST_METHOD(81) {
@@ -86,7 +100,7 @@ namespace tut {
86
100
  setLogLevel(-1);
87
101
  shared_ptr<SmartSpawner> spawner = createSpawner(options, true);
88
102
  try {
89
- spawner->spawn(options);
103
+ spawner->spawn(options)->shutdown();
90
104
  fail("SpawnException expected");
91
105
  } catch (const SpawnException &) {
92
106
  // Pass.
@@ -116,13 +130,12 @@ namespace tut {
116
130
  spawner.getConfig()->forwardStderr = false;
117
131
 
118
132
  try {
119
- spawner.spawn(options);
133
+ spawner.spawn(options)->shutdown();
120
134
  fail("SpawnException expected");
121
135
  } catch (const SpawnException &e) {
122
136
  ensure_equals(e.getErrorKind(),
123
137
  SpawnException::PRELOADER_STARTUP_TIMEOUT);
124
- ensure_equals(e.getErrorPage(),
125
- "hello world\n");
138
+ ensure(e.getErrorPage().find("hello world\n") != string::npos);
126
139
  }
127
140
  }
128
141
 
@@ -148,13 +161,12 @@ namespace tut {
148
161
  spawner.getConfig()->forwardStderr = false;
149
162
 
150
163
  try {
151
- spawner.spawn(options);
164
+ spawner.spawn(options)->shutdown();
152
165
  fail("SpawnException expected");
153
166
  } catch (const SpawnException &e) {
154
167
  ensure_equals(e.getErrorKind(),
155
168
  SpawnException::PRELOADER_STARTUP_PROTOCOL_ERROR);
156
- ensure_equals(e.getErrorPage(),
157
- "hello world\n");
169
+ ensure(e.getErrorPage().find("hello world\n") != string::npos);
158
170
  }
159
171
  }
160
172
 
@@ -180,7 +192,7 @@ namespace tut {
180
192
  spawner.getConfig()->forwardStderr = false;
181
193
 
182
194
  try {
183
- spawner.spawn(options);
195
+ spawner.spawn(options)->shutdown();
184
196
  fail("SpawnException expected");
185
197
  } catch (const SpawnException &e) {
186
198
  ensure(containsSubstring(e["envvars"], "PASSENGER_FOO=foo\n"));
@@ -191,11 +203,10 @@ namespace tut {
191
203
  // Test that the spawned process can still write to its stderr
192
204
  // after the SmartSpawner has been destroyed.
193
205
  DeleteFileEventually d("tmp.output");
194
- FileDescriptor output(open("tmp.output", O_WRONLY | O_CREAT | O_TRUNC, 0600));
206
+ PipeWatcher::onData = gatherOutput;
195
207
  Options options = createOptions();
196
208
  options.appRoot = "stub/rack";
197
209
 
198
- ProcessPtr process;
199
210
  {
200
211
  vector<string> preloaderCommand;
201
212
  preloaderCommand.push_back("ruby");
@@ -205,9 +216,8 @@ namespace tut {
205
216
  generation,
206
217
  preloaderCommand,
207
218
  options);
208
- spawner.getConfig()->forwardStdoutTo = output;
209
- spawner.getConfig()->forwardStderrTo = output;
210
219
  process = spawner.spawn(options);
220
+ process->requiresShutdown = false;
211
221
  }
212
222
 
213
223
  SessionPtr session = process->newSession();
@@ -226,7 +236,8 @@ namespace tut {
226
236
  shutdown(session->fd(), SHUT_WR);
227
237
  readAll(session->fd());
228
238
  EVENTUALLY(2,
229
- result = readAll("tmp.output").find("hello world!\n") != string::npos;
239
+ lock_guard<boost::mutex> l(gatheredOutputSyncher);
240
+ result = gatheredOutput.find("hello world!\n") != string::npos;
230
241
  );
231
242
  }
232
243
  }