passenger 4.0.42 → 4.0.43

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 (67) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.asc +7 -7
  3. data.tar.gz.asc +7 -7
  4. data/CHANGELOG +13 -0
  5. data/CONTRIBUTING.md +2 -19
  6. data/build/agents.rb +4 -1
  7. data/build/cxx_tests.rb +7 -2
  8. data/build/debian.rb +1 -1
  9. data/debian.template/control.template +0 -2
  10. data/doc/CodingTipsAndPitfalls.md +56 -0
  11. data/doc/Users guide Apache.idmap.txt +16 -14
  12. data/doc/Users guide Nginx.idmap.txt +8 -6
  13. data/doc/Users guide Standalone.idmap.txt +3 -1
  14. data/doc/Users guide Standalone.txt +1 -1
  15. data/doc/users_guide_snippets/environment_variables.txt +1 -0
  16. data/doc/users_guide_snippets/installation.txt +5 -5
  17. data/doc/users_guide_snippets/support_information.txt +42 -9
  18. data/doc/users_guide_snippets/troubleshooting/default.txt +42 -0
  19. data/ext/common/ApplicationPool2/Common.h +1 -0
  20. data/ext/common/ApplicationPool2/DirectSpawner.h +2 -7
  21. data/ext/common/ApplicationPool2/DummySpawner.h +1 -1
  22. data/ext/common/ApplicationPool2/Group.h +4 -2
  23. data/ext/common/ApplicationPool2/Options.h +9 -7
  24. data/ext/common/ApplicationPool2/Pool.h +83 -40
  25. data/ext/common/ApplicationPool2/Process.h +2 -6
  26. data/ext/common/ApplicationPool2/README.md +0 -40
  27. data/ext/common/ApplicationPool2/SmartSpawner.h +2 -9
  28. data/ext/common/ApplicationPool2/Spawner.h +1 -4
  29. data/ext/common/ApplicationPool2/SpawnerFactory.h +6 -9
  30. data/ext/common/ApplicationPool2/SuperGroup.h +3 -3
  31. data/ext/common/Constants.h +1 -1
  32. data/ext/common/UnionStation/Connection.h +227 -0
  33. data/ext/common/UnionStation/Core.h +497 -0
  34. data/ext/common/UnionStation/ScopeLog.h +172 -0
  35. data/ext/common/UnionStation/Transaction.h +276 -0
  36. data/ext/common/Utils.cpp +83 -8
  37. data/ext/common/Utils.h +25 -4
  38. data/ext/common/Utils/AnsiColorConstants.h +1 -0
  39. data/ext/common/Utils/ProcessMetricsCollector.h +6 -170
  40. data/ext/common/Utils/SpeedMeter.h +258 -0
  41. data/ext/common/Utils/StrIntUtils.cpp +6 -0
  42. data/ext/common/Utils/StringScanning.h +277 -0
  43. data/ext/common/Utils/SystemMetricsCollector.h +1460 -0
  44. data/ext/common/agents/Base.cpp +8 -8
  45. data/ext/common/agents/HelperAgent/Main.cpp +12 -6
  46. data/ext/common/agents/HelperAgent/RequestHandler.h +15 -16
  47. data/ext/common/agents/HelperAgent/SystemMetricsTool.cpp +199 -0
  48. data/ext/common/agents/LoggingAgent/LoggingServer.h +2 -1
  49. data/ext/common/agents/SpawnPreparer.cpp +20 -32
  50. data/lib/phusion_passenger.rb +1 -1
  51. data/lib/phusion_passenger/config/list_instances_command.rb +118 -0
  52. data/lib/phusion_passenger/config/main.rb +22 -4
  53. data/lib/phusion_passenger/config/system_metrics_command.rb +37 -0
  54. data/lib/phusion_passenger/config/utils.rb +1 -1
  55. data/lib/phusion_passenger/loader_shared_helpers.rb +8 -5
  56. data/lib/phusion_passenger/platform_info/compiler.rb +1 -1
  57. data/resources/templates/error_layout.html.template +3 -3
  58. data/test/cxx/ApplicationPool2/DirectSpawnerTest.cpp +3 -5
  59. data/test/cxx/ApplicationPool2/PoolTest.cpp +1 -3
  60. data/test/cxx/ApplicationPool2/ProcessTest.cpp +4 -4
  61. data/test/cxx/ApplicationPool2/SmartSpawnerTest.cpp +5 -7
  62. data/test/cxx/RequestHandlerTest.cpp +9 -3
  63. data/test/cxx/UnionStationTest.cpp +61 -64
  64. metadata +13 -4
  65. metadata.gz.asc +7 -7
  66. data/ext/common/UnionStation.h +0 -968
  67. data/helper-scripts/system-memory-stats.py +0 -207
@@ -118,3 +118,45 @@ cd /path-to-your-app
118
118
  passenger start --temp-dir=$HOME/tmp
119
119
  ---------------------------------------
120
120
  endif::[]
121
+
122
+ === I get "command not found" when running a Phusion Passenger command through sudo
123
+
124
+ **Symptoms**::
125
+ Phusion Passenger commands can be found as a normal user, but not when run through sudo:
126
+ +
127
+ -------------------------
128
+ $ passenger-status
129
+ ...some output, but no "command not found" error...
130
+ $ passenger-install-apache2-module
131
+ ...some output, but no "command not found" error...
132
+ $ sudo passenger-status
133
+ sudo: passenger-status: command not found
134
+ $ sudo passenger-install-apache2-module
135
+ sudo: passenger-install-apache2-module: command not found
136
+ -------------------------
137
+
138
+ **Cause**::
139
+ The operating system looks up commands using <<the_path_env_var,the PATH environment variable>>. However, sudo resets all environment variables to a default value, dictated by sudo. If Phusion Passenger was installed to a location that is not in the default sudo PATH value, then sudo will not be able to find the Phusion Passenger commands.
140
+ +
141
+ In addition, if you installed Phusion Passenger using a Ruby interpreter that was installed through RVM, then you **must** use rvmsudo instead of sudo. As a rule, when you're an RVM user, always use rvmsudo instead of sudo.
142
+
143
+ **Solution**::
144
+ Execute the command using its full path. You can use `which` as a normal user to lookup the full path:
145
+ +
146
+ ---------------------------------------
147
+ $ which passenger-status
148
+ /somewhere/bin/passenger-status
149
+ ---------------------------------------
150
+ +
151
+ Next, run full path of the command using either sudo or rvmsudo:
152
+ +
153
+ ---------------------------------------
154
+ $ sudo /somewhere/bin/passenger-status
155
+
156
+ # -OR, if you're using RVM:-
157
+
158
+ $ rvmsudo /somewhere/bin/passenger-status
159
+ ---------------------------------------
160
+
161
+ **Recommended reading**::
162
+ When using sudo, you will probably run into similar "command not found" issues in the future, with components other than Phusion Passenger. We **strongly recommend** you to <<about_environment_variables,learn about environment variables>> so that you know what to do in the future.
@@ -29,6 +29,7 @@
29
29
  #include <boost/shared_ptr.hpp>
30
30
  #include <boost/function.hpp>
31
31
  #include <oxt/tracable_exception.hpp>
32
+ #include <RandomGenerator.h>
32
33
  #include <ApplicationPool2/Options.h>
33
34
  #include <Utils/StringMap.h>
34
35
 
@@ -40,8 +40,6 @@ using namespace oxt;
40
40
 
41
41
  class DirectSpawner: public Spawner {
42
42
  private:
43
- SafeLibevPtr libev;
44
-
45
43
  static int startBackgroundThread(void *(*mainFunction)(void *), void *arg) {
46
44
  // Using raw pthread API because we don't want to register such
47
45
  // trivial threads on the oxt::thread list.
@@ -146,12 +144,10 @@ private:
146
144
  }
147
145
 
148
146
  public:
149
- DirectSpawner(const SafeLibevPtr &_libev,
150
- const ResourceLocator &_resourceLocator,
147
+ DirectSpawner(const ResourceLocator &_resourceLocator,
151
148
  const ServerInstanceDir::GenerationPtr &_generation,
152
149
  const SpawnerConfigPtr &_config = SpawnerConfigPtr())
153
- : Spawner(_resourceLocator),
154
- libev(_libev)
150
+ : Spawner(_resourceLocator)
155
151
  {
156
152
  generation = _generation;
157
153
  if (_config == NULL) {
@@ -218,7 +214,6 @@ public:
218
214
 
219
215
  NegotiationDetails details;
220
216
  details.preparation = &preparation;
221
- details.libev = libev;
222
217
  details.stderrCapturer =
223
218
  make_shared<BackgroundIOCapturer>(
224
219
  errorPipe.first,
@@ -63,7 +63,7 @@ public:
63
63
 
64
64
  boost::lock_guard<boost::mutex> l(lock);
65
65
  count++;
66
- ProcessPtr process = boost::make_shared<Process>(SafeLibevPtr(),
66
+ ProcessPtr process = boost::make_shared<Process>(
67
67
  (pid_t) count, "gupid-" + toString(count),
68
68
  toString(count),
69
69
  adminSocket.second, FileDescriptor(), sockets,
@@ -425,7 +425,9 @@ public:
425
425
  && (newOptions.maxRequestQueueSize == 0
426
426
  || getWaitlist.size() < newOptions.maxRequestQueueSize)))
427
427
  {
428
- getWaitlist.push_back(GetWaiter(newOptions.copyAndPersist().clearLogger(), callback));
428
+ getWaitlist.push_back(GetWaiter(
429
+ newOptions.copyAndPersist().detachFromUnionStationTransaction(),
430
+ callback));
429
431
  return true;
430
432
  } else {
431
433
  P_WARN("Request queue is full. Returning an error");
@@ -900,7 +902,7 @@ public:
900
902
  }
901
903
 
902
904
  if (OXT_UNLIKELY(newOptions.noop)) {
903
- ProcessPtr process = boost::make_shared<Process>(SafeLibevPtr(),
905
+ ProcessPtr process = boost::make_shared<Process>(
904
906
  0, string(), string(),
905
907
  FileDescriptor(), FileDescriptor(),
906
908
  SocketListPtr(), 0, 0);
@@ -31,7 +31,8 @@
31
31
  #include <boost/shared_array.hpp>
32
32
  #include <ApplicationPool2/AppTypes.h>
33
33
  #include <Account.h>
34
- #include <UnionStation.h>
34
+ #include <UnionStation/Core.h>
35
+ #include <UnionStation/Transaction.h>
35
36
  #include <Constants.h>
36
37
  #include <ResourceLocator.h>
37
38
  #include <StaticString.h>
@@ -361,10 +362,11 @@ public:
361
362
  StaticString uri;
362
363
 
363
364
  /**
364
- * A Union Station logger object to log things to. May be the null pointer,
365
- * in which case Union Station logging is disabled for this request.
365
+ * The Union Station log transaction that this request belongs to.
366
+ * May be the null pointer, in which case Union Station logging is
367
+ * disabled for this request.
366
368
  */
367
- UnionStation::LoggerPtr logger;
369
+ UnionStation::TransactionPtr transaction;
368
370
 
369
371
  /**
370
372
  * A sticky session ID for routing to a specific process.
@@ -529,11 +531,11 @@ public:
529
531
  uri = StaticString();
530
532
  stickySessionId = 0;
531
533
  noop = false;
532
- return clearLogger();
534
+ return detachFromUnionStationTransaction();
533
535
  }
534
536
 
535
- Options &clearLogger() {
536
- logger.reset();
537
+ Options &detachFromUnionStationTransaction() {
538
+ transaction.reset();
537
539
  return *this;
538
540
  }
539
541
 
@@ -47,7 +47,8 @@
47
47
  #include <ApplicationPool2/Session.h>
48
48
  #include <ApplicationPool2/SpawnerFactory.h>
49
49
  #include <ApplicationPool2/Options.h>
50
- #include <UnionStation.h>
50
+ #include <UnionStation/Core.h>
51
+ #include <UnionStation/Transaction.h>
51
52
  #include <Logging.h>
52
53
  #include <Exceptions.h>
53
54
  #include <RandomGenerator.h>
@@ -58,6 +59,7 @@
58
59
  #include <Utils/MessagePassing.h>
59
60
  #include <Utils/VariantMap.h>
60
61
  #include <Utils/ProcessMetricsCollector.h>
62
+ #include <Utils/SystemMetricsCollector.h>
61
63
 
62
64
  namespace Passenger {
63
65
  namespace ApplicationPool2 {
@@ -88,9 +90,6 @@ public:
88
90
  public:
89
91
  friend class SuperGroup;
90
92
  friend class Group;
91
- typedef UnionStation::LoggerFactory LoggerFactory;
92
- typedef UnionStation::LoggerFactoryPtr LoggerFactoryPtr;
93
- typedef UnionStation::LoggerPtr LoggerPtr;
94
93
 
95
94
  struct DebugSupport {
96
95
  /** Mailbox for the unit tests to receive messages on. */
@@ -126,8 +125,10 @@ public:
126
125
  typedef boost::shared_ptr<DebugSupport> DebugSupportPtr;
127
126
 
128
127
  SpawnerFactoryPtr spawnerFactory;
129
- LoggerFactoryPtr loggerFactory;
128
+ UnionStation::CorePtr unionStationCore;
130
129
  RandomGeneratorPtr randomGenerator;
130
+ SystemMetricsCollector systemMetricsCollector;
131
+ SystemMetrics systemMetrics;
131
132
 
132
133
  mutable boost::mutex syncher;
133
134
  unsigned int max;
@@ -730,14 +731,13 @@ public:
730
731
  return sleepTime;
731
732
  }
732
733
 
733
- struct ProcessAnalyticsLogEntry {
734
+ struct UnionStationLogEntry {
734
735
  string groupName;
736
+ const char *category;
735
737
  string key;
736
- stringstream data;
738
+ string data;
737
739
  };
738
740
 
739
- typedef boost::shared_ptr<ProcessAnalyticsLogEntry> ProcessAnalyticsLogEntryPtr;
740
-
741
741
  static void collectAnalytics(PoolPtr self) {
742
742
  TRACE_POINT();
743
743
  syscalls::usleep(3000000);
@@ -780,6 +780,43 @@ public:
780
780
  }
781
781
  }
782
782
 
783
+ void prepareUnionStationProcessStateLogs(vector<UnionStationLogEntry> &logEntries,
784
+ const GroupPtr &group) const
785
+ {
786
+ if (group->options.analytics && unionStationCore != NULL) {
787
+ logEntries.push_back(UnionStationLogEntry());
788
+ UnionStationLogEntry &entry = logEntries.back();
789
+ stringstream stream;
790
+
791
+ stream << "Group: <group>";
792
+ group->inspectXml(stream, false);
793
+ stream << "</group>";
794
+
795
+ entry.groupName = group->name;
796
+ entry.category = "processes";
797
+ entry.key = group->options.unionStationKey;
798
+ entry.data = stream.str();
799
+ }
800
+ }
801
+
802
+ void prepareUnionStationSystemMetricsLogs(vector<UnionStationLogEntry> &logEntries,
803
+ const GroupPtr &group) const
804
+ {
805
+ if (group->options.analytics && unionStationCore != NULL) {
806
+ logEntries.push_back(UnionStationLogEntry());
807
+ UnionStationLogEntry &entry = logEntries.back();
808
+ stringstream stream;
809
+
810
+ stream << "System metrics: ";
811
+ systemMetrics.toXml(stream);
812
+
813
+ entry.groupName = group->name;
814
+ entry.category = "system_metrics";
815
+ entry.key = group->options.unionStationKey;
816
+ entry.data = stream.str();
817
+ }
818
+ }
819
+
783
820
  unsigned long long realCollectAnalytics() {
784
821
  TRACE_POINT();
785
822
  this_thread::disable_interruption di;
@@ -813,20 +850,27 @@ public:
813
850
  }
814
851
  }
815
852
 
816
- ProcessMetricMap allMetrics;
853
+ // Collect process metrics and system and store them in the
854
+ // data structures. Later, we log them to Union Station.
855
+ ProcessMetricMap processMetrics;
817
856
  try {
818
- // Now collect the process metrics and store them in the
819
- // data structures, and log the state into the analytics logs.
820
857
  UPDATE_TRACE_POINT();
821
- allMetrics = ProcessMetricsCollector().collect(pids);
822
- } catch (const ProcessMetricsCollector::ParseException &) {
858
+ processMetrics = ProcessMetricsCollector().collect(pids);
859
+ } catch (const ParseException &) {
823
860
  P_WARN("Unable to collect process metrics: cannot parse 'ps' output.");
824
861
  goto end;
825
862
  }
863
+ try {
864
+ UPDATE_TRACE_POINT();
865
+ systemMetricsCollector.collect(systemMetrics);
866
+ } catch (const RuntimeException &e) {
867
+ P_WARN("Unable to collect system metrics: " << e.what());
868
+ goto end;
869
+ }
826
870
 
827
871
  {
828
872
  UPDATE_TRACE_POINT();
829
- vector<ProcessAnalyticsLogEntryPtr> logEntries;
873
+ vector<UnionStationLogEntry> logEntries;
830
874
  vector<ProcessPtr> processesToDetach;
831
875
  vector<Callback> actions;
832
876
  ScopedLock l(syncher);
@@ -840,22 +884,11 @@ public:
840
884
  for (g_it = superGroup->groups.begin(); g_it != g_end; g_it++) {
841
885
  const GroupPtr &group = *g_it;
842
886
 
843
- updateProcessMetrics(group->enabledProcesses, allMetrics, processesToDetach);
844
- updateProcessMetrics(group->disablingProcesses, allMetrics, processesToDetach);
845
- updateProcessMetrics(group->disabledProcesses, allMetrics, processesToDetach);
846
-
847
- // Log to Union Station.
848
- if (group->options.analytics && loggerFactory != NULL) {
849
- ProcessAnalyticsLogEntryPtr entry = boost::make_shared<ProcessAnalyticsLogEntry>();
850
- stringstream &xml = entry->data;
851
-
852
- entry->groupName = group->name;
853
- entry->key = group->options.unionStationKey;
854
- xml << "Group: <group>";
855
- group->inspectXml(xml, false);
856
- xml << "</group>";
857
- logEntries.push_back(entry);
858
- }
887
+ updateProcessMetrics(group->enabledProcesses, processMetrics, processesToDetach);
888
+ updateProcessMetrics(group->disablingProcesses, processMetrics, processesToDetach);
889
+ updateProcessMetrics(group->disabledProcesses, processMetrics, processesToDetach);
890
+ prepareUnionStationProcessStateLogs(logEntries, group);
891
+ prepareUnionStationSystemMetricsLogs(logEntries, group);
859
892
  }
860
893
  }
861
894
 
@@ -869,11 +902,14 @@ public:
869
902
  l.unlock();
870
903
  UPDATE_TRACE_POINT();
871
904
  while (!logEntries.empty()) {
872
- ProcessAnalyticsLogEntryPtr entry = logEntries.back();
905
+ UnionStationLogEntry &entry = logEntries.back();
906
+ UnionStation::TransactionPtr transaction =
907
+ unionStationCore->newTransaction(
908
+ entry.groupName,
909
+ entry.category,
910
+ entry.key);
911
+ transaction->message(entry.data);
873
912
  logEntries.pop_back();
874
- LoggerPtr logger = loggerFactory->newTransaction(entry->groupName,
875
- "processes", entry->key);
876
- logger->message(entry->data.str());
877
913
  }
878
914
 
879
915
  UPDATE_TRACE_POINT();
@@ -923,19 +959,25 @@ public:
923
959
 
924
960
  public:
925
961
  Pool(const SpawnerFactoryPtr &spawnerFactory,
926
- const LoggerFactoryPtr &loggerFactory = LoggerFactoryPtr(),
962
+ const UnionStation::CorePtr &unionStationCore = UnionStation::CorePtr(),
927
963
  const RandomGeneratorPtr &randomGenerator = RandomGeneratorPtr(),
928
964
  const VariantMap *agentsOptions = NULL)
929
965
  {
930
966
  this->spawnerFactory = spawnerFactory;
931
- this->loggerFactory = loggerFactory;
967
+ this->unionStationCore = unionStationCore;
932
968
  if (randomGenerator != NULL) {
933
969
  this->randomGenerator = randomGenerator;
934
970
  } else {
935
971
  this->randomGenerator = boost::make_shared<RandomGenerator>();
936
972
  }
937
973
  this->agentsOptions = agentsOptions;
938
-
974
+
975
+ try {
976
+ systemMetricsCollector.collect(systemMetrics);
977
+ } catch (const RuntimeException &e) {
978
+ P_WARN("Unable to collect system metrics: " << e.what());
979
+ }
980
+
939
981
  lifeStatus = ALIVE;
940
982
  max = 6;
941
983
  maxIdleTime = 60 * 1000000;
@@ -1075,7 +1117,7 @@ public:
1075
1117
  */
1076
1118
  P_DEBUG("Could not free a process; putting request to top-level getWaitlist");
1077
1119
  getWaitlist.push_back(GetWaiter(
1078
- options.copyAndPersist().clearLogger(),
1120
+ options.copyAndPersist().detachFromUnionStationTransaction(),
1079
1121
  callback));
1080
1122
  } else {
1081
1123
  /* Now that a process has been trashed we can create
@@ -1538,8 +1580,9 @@ public:
1538
1580
  ProcessList::const_iterator p_it;
1539
1581
 
1540
1582
  result << "<?xml version=\"1.0\" encoding=\"iso8859-1\" ?>\n";
1541
- result << "<info version=\"2\">";
1583
+ result << "<info version=\"3\">";
1542
1584
 
1585
+ result << "<passenger_version>" << PASSENGER_VERSION << "</passenger_version>";
1543
1586
  result << "<process_count>" << getProcessCount(false) << "</process_count>";
1544
1587
  result << "<max>" << max << "</max>";
1545
1588
  result << "<capacity_used>" << capacityUsed(false) << "</capacity_used>";
@@ -41,7 +41,6 @@
41
41
  #include <ApplicationPool2/PipeWatcher.h>
42
42
  #include <Constants.h>
43
43
  #include <FileDescriptor.h>
44
- #include <SafeLibev.h>
45
44
  #include <Logging.h>
46
45
  #include <Utils/PriorityQueue.h>
47
46
  #include <Utils/SystemTime.h>
@@ -209,8 +208,6 @@ public:
209
208
  * written to again. Reading is thread-safe.
210
209
  *************************************************************/
211
210
 
212
- /** The libev event loop to use. */
213
- SafeLibev * const libev;
214
211
  /** Process PID. */
215
212
  pid_t pid;
216
213
  /** An ID that uniquely identifies this Process in the Group, for
@@ -323,8 +320,7 @@ public:
323
320
  /** Collected by Pool::collectAnalytics(). */
324
321
  ProcessMetrics metrics;
325
322
 
326
- Process(const SafeLibevPtr _libev,
327
- pid_t _pid,
323
+ Process(pid_t _pid,
328
324
  const string &_gupid,
329
325
  const string &_connectPassword,
330
326
  const FileDescriptor &_adminSocket,
@@ -338,7 +334,6 @@ public:
338
334
  unsigned long long _spawnStartTime,
339
335
  const SpawnerConfigPtr &_config = SpawnerConfigPtr())
340
336
  : pqHandle(NULL),
341
- libev(_libev.get()),
342
337
  pid(_pid),
343
338
  stickySessionId(0),
344
339
  gupid(_gupid),
@@ -646,6 +641,7 @@ public:
646
641
  stream << "<spawn_start_time>" << spawnStartTime << "</spawn_start_time>";
647
642
  stream << "<spawn_end_time>" << spawnEndTime << "</spawn_end_time>";
648
643
  stream << "<last_used>" << lastUsed << "</last_used>";
644
+ stream << "<last_used_desc>" << distanceOfTimeInWords(lastUsed / 1000000).c_str() << " ago</last_used_desc>";
649
645
  stream << "<uptime>" << uptime() << "</uptime>";
650
646
  if (!codeRevision.empty()) {
651
647
  stream << "<code_revision>" << codeRevision << "</code_revision>";
@@ -54,43 +54,3 @@ The `Pool` class's `get` method is the main interface into the ApplicationPool2
54
54
  subsystem. When an HTTP request comes in, call `Pool::get()` with the
55
55
  appropriate arguments, and it will automatically spawn a process for you when
56
56
  needed, open a session with that process and give you the session object.
57
-
58
-
59
- ## Threading notes
60
-
61
- ApplicationPool2 depends on an event loop for handling timers and I/O. The I/O
62
- that it handles is not the request/response I/O with application processes, but
63
- things like forwarding the processes' stderr output to our stderr. In order not
64
- to block the event loop with long-running operations, it uses a lot of
65
- background threads. ApplicationPool2 is designed to be entirely thread-safe.
66
- That said, if one's not careful, one may cause deadlocks, so read this section
67
- carefully.
68
-
69
- * Many Spawner methods are blocking because they wait for a subprocess to do
70
- something (initializing, shutting down, etc). The process may output I/O
71
- which is supposed to be handled by the main loop. If the event loop is blocked
72
- on waiting for the process, and the process is blocked on a write() to the I/O
73
- channel, then we have a deadlock. Therefore Spawner methods (including the
74
- Spawner destructor) must always be called outside the event loop thread, and
75
- the event loop must be available while the Spawner is doing its work. The only
76
- exceptions are Spawner methods which are explicitly documented as not
77
- depending on the event loop.
78
-
79
- Pool must only call Spawner methods from background threads. There's still a
80
- caveat though: Pool's destructor waits for all background threads to finish.
81
- Therefore one must not destroy Pool from the event loop. Instead, I recommend
82
- running the event loop in a separate thread, destroy Pool from the main
83
- thread, and stop the event loop after Pool is destroyed.
84
-
85
- Calling other Pool methods from the event loop is ok. Calling SpawnerFactory
86
- from the event loop is ok.
87
-
88
- * Many classes contain libev watchers and unregisters them in their destructor.
89
- In order for this unregistration to succeed, one of the following conditions
90
- must hold:
91
- 1. The destructor is called from the event loop.
92
- 2. The destructor is not called from the event loop, but the event loop is
93
- still running.
94
-
95
- Therefore I recommend that you destroy all ApplicationPool2-related objects
96
- before stopping the event loop thread.