passenger 4.0.0.rc6 → 4.0.1

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 (49) hide show
  1. data.tar.gz.asc +7 -7
  2. data/NEWS +14 -0
  3. data/build/basics.rb +2 -1
  4. data/build/packaging.rb +33 -0
  5. data/dev/run_travis.sh +1 -0
  6. data/doc/Architectural overview.html +1 -3
  7. data/doc/Packaging.txt.md +6 -6
  8. data/doc/Security of user switching support.html +1 -3
  9. data/doc/Users guide Apache.html +53 -20
  10. data/doc/Users guide Apache.idmap.txt +4 -0
  11. data/doc/Users guide Apache.txt +8 -0
  12. data/doc/Users guide Nginx.html +41 -18
  13. data/doc/Users guide Standalone.html +1 -3
  14. data/doc/users_guide_snippets/analysis_and_system_maintenance.txt +11 -5
  15. data/doc/users_guide_snippets/installation.txt +5 -1
  16. data/doc/users_guide_snippets/tips.txt +10 -7
  17. data/doc/users_guide_snippets/under_the_hood/page_caching_support.txt +2 -2
  18. data/ext/apache2/Configuration.cpp +5 -6
  19. data/ext/apache2/Configuration.hpp +0 -4
  20. data/ext/common/ApplicationPool2/Group.h +25 -43
  21. data/ext/common/ApplicationPool2/Implementation.cpp +51 -32
  22. data/ext/common/ApplicationPool2/Pool.h +6 -7
  23. data/ext/common/ApplicationPool2/Process.h +61 -44
  24. data/ext/common/ApplicationPool2/Spawner.h +5 -0
  25. data/ext/common/BackgroundEventLoop.cpp +5 -11
  26. data/ext/common/Constants.h +1 -1
  27. data/ext/common/Utils.cpp +1 -1
  28. data/ext/common/agents/HelperAgent/AgentOptions.h +1 -1
  29. data/ext/common/agents/HelperAgent/RequestHandler.h +2 -0
  30. data/ext/common/agents/LoggingAgent/LoggingServer.h +4 -1
  31. data/ext/common/agents/LoggingAgent/RemoteSender.h +58 -6
  32. data/lib/phusion_passenger.rb +2 -2
  33. data/lib/phusion_passenger/loader_shared_helpers.rb +6 -6
  34. data/lib/phusion_passenger/platform_info/compiler.rb +16 -0
  35. data/lib/phusion_passenger/platform_info/depcheck_specs/apache2.rb +1 -1
  36. data/lib/phusion_passenger/request_handler.rb +1 -0
  37. data/lib/phusion_passenger/standalone/start_command.rb +0 -4
  38. data/lib/phusion_passenger/utils/robust_interruption.rb +47 -28
  39. data/resources/templates/standalone/config.erb +4 -22
  40. data/test/config.json.example +1 -1
  41. data/test/cxx/ApplicationPool2/DirectSpawnerTest.cpp +5 -3
  42. data/test/cxx/ApplicationPool2/PoolTest.cpp +75 -2
  43. data/test/cxx/ApplicationPool2/SmartSpawnerTest.cpp +12 -7
  44. data/test/cxx/ApplicationPool2/SpawnerTestCases.cpp +18 -5
  45. data/test/cxx/RequestHandlerTest.cpp +3 -0
  46. data/test/ruby/shared/loader_sharedspec.rb +4 -0
  47. metadata +5 -6
  48. metadata.gz.asc +7 -7
  49. data/doc/Users guide Apache.index.sqlite3 +0 -0
@@ -31,9 +31,9 @@ module PhusionPassenger
31
31
  PACKAGE_NAME = 'passenger'
32
32
 
33
33
  # Phusion Passenger version number. Don't forget to edit ext/common/Constants.h too.
34
- VERSION_STRING = '4.0.0.rc6'
34
+ VERSION_STRING = '4.0.1'
35
35
 
36
- PREFERRED_NGINX_VERSION = '1.2.7'
36
+ PREFERRED_NGINX_VERSION = '1.4.0'
37
37
  PREFERRED_PCRE_VERSION = '8.32'
38
38
  STANDALONE_INTERFACE_VERSION = 1
39
39
 
@@ -81,12 +81,12 @@ module LoaderSharedHelpers
81
81
  f.puts "RUBY_PLATFORM = #{RUBY_PLATFORM}"
82
82
  f.puts "RUBY_ENGINE = #{defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'nil'}"
83
83
  end
84
- File.open("#{dir}/load_path", "w") do |f|
84
+ File.open("#{dir}/load_path", "wb") do |f|
85
85
  $LOAD_PATH.each do |path|
86
86
  f.puts path
87
87
  end
88
88
  end
89
- File.open("#{dir}/loaded_libs", "w") do |f|
89
+ File.open("#{dir}/loaded_libs", "wb") do |f|
90
90
  $LOADED_FEATURES.each do |filename|
91
91
  f.puts filename
92
92
  end
@@ -94,7 +94,7 @@ module LoaderSharedHelpers
94
94
 
95
95
  # We write to these files last because the 'require' calls can fail.
96
96
  require 'rbconfig' if !defined?(RbConfig::CONFIG)
97
- File.open("#{dir}/rbconfig", "w") do |f|
97
+ File.open("#{dir}/rbconfig", "wb") do |f|
98
98
  RbConfig::CONFIG.each_pair do |key, value|
99
99
  f.puts "#{key} = #{value}"
100
100
  end
@@ -103,7 +103,7 @@ module LoaderSharedHelpers
103
103
  File.open("#{dir}/ruby_info", "a") do |f|
104
104
  f.puts "RubyGems version = #{Gem::VERSION}"
105
105
  end
106
- File.open("#{dir}/activated_gems", "w") do |f|
106
+ File.open("#{dir}/activated_gems", "wb") do |f|
107
107
  if Gem.respond_to?(:loaded_specs)
108
108
  Gem.loaded_specs.each_pair do |name, spec|
109
109
  f.puts "#{name} => #{spec.version}"
@@ -119,7 +119,7 @@ module LoaderSharedHelpers
119
119
 
120
120
  def dump_envvars
121
121
  if dir = ENV['PASSENGER_DEBUG_DIR']
122
- File.open("#{dir}/envvars", "w") do |f|
122
+ File.open("#{dir}/envvars", "wb") do |f|
123
123
  ENV.each_pair do |key, value|
124
124
  f.puts "#{key} = #{value}"
125
125
  end
@@ -131,7 +131,7 @@ module LoaderSharedHelpers
131
131
 
132
132
  def dump_system_memory_stats
133
133
  if dir = ENV['PASSENGER_DEBUG_DIR']
134
- File.open("#{dir}/sysmemory", "w") do |f|
134
+ File.open("#{dir}/sysmemory", "wb") do |f|
135
135
  f.write(`"#{PhusionPassenger.helper_scripts_dir}/system-memory-stats.py"`)
136
136
  end
137
137
  end
@@ -257,6 +257,22 @@ public
257
257
  :c, '', '-Wno-ambiguous-member-template')
258
258
  end
259
259
  memoize :compiler_supports_wno_ambiguous_member_template?, true
260
+
261
+ def self.compiler_supports_feliminate_unused_debug?
262
+ create_temp_file("passenger-compile-check.c") do |filename, f|
263
+ f.close
264
+ begin
265
+ command = create_compiler_command(:c,
266
+ "-c '#{filename}' -o '#{filename}.o'",
267
+ '-feliminate-unused-debug-symbols -feliminate-unused-debug-types')
268
+ result = run_compiler("Checking for C compiler '--feliminate-unused-debug-{symbols,types}' support",
269
+ command, filename, '', true)
270
+ return result && result[:output].empty?
271
+ ensure
272
+ File.unlink("#{filename}.o") rescue nil
273
+ end
274
+ end
275
+ end
260
276
 
261
277
  # Returns whether compiling C++ with -fvisibility=hidden might result
262
278
  # in tons of useless warnings, like this:
@@ -44,7 +44,7 @@ define 'apache2-dev' do
44
44
  end
45
45
 
46
46
  on :debian do
47
- apt_get_install "apache2-worker-dev"
47
+ apt_get_install "apache2-threaded-dev"
48
48
  end
49
49
  on :mandriva do
50
50
  urpmi "apache-devel"
@@ -213,6 +213,7 @@ class RequestHandler
213
213
  end
214
214
 
215
215
  install_useful_signal_handlers
216
+ RobustInterruption.install
216
217
  start_threads
217
218
  wait_until_termination
218
219
  terminate_threads
@@ -109,10 +109,6 @@ private
109
109
  require 'fileutils' unless defined?(FileUtils)
110
110
  end
111
111
 
112
- def require_app_finder
113
- require 'phusion_passenger/standalone/app_finder' unless defined?(AppFinder)
114
- end
115
-
116
112
  def parse_my_options
117
113
  description = "Starts Phusion Passenger Standalone and serve one or more Ruby web applications."
118
114
  parse_options!("start [directory]", description) do |opts|
@@ -5,6 +5,26 @@ module Utils
5
5
 
6
6
  module RobustInterruption
7
7
  class Interrupted < StandardError
8
+ def initialize
9
+ @origin_thread = Thread.current
10
+ @origin = caller
11
+ end
12
+
13
+ def set_backtrace(bt)
14
+ @origin.reverse.each do |line|
15
+ if bt.last == line
16
+ bt.pop
17
+ else
18
+ break
19
+ end
20
+ end
21
+ bt << "interruption initiator thread: #{RobustInterruption._get_thread_display_name @origin_thread}"
22
+ bt.concat(@origin)
23
+ super(bt)
24
+ end
25
+ end
26
+
27
+ class NotInstalled < StandardError
8
28
  end
9
29
 
10
30
  class Data
@@ -52,43 +72,29 @@ module RobustInterruption
52
72
  RobustInterruption.install
53
73
  end
54
74
 
55
- def installed?(thread = Thread.current)
56
- return !!thread[:robust_interruption]
57
- end
58
- module_function :installed?
59
-
60
75
  def interrupted?(thread = Thread.current)
61
76
  if data = thread[:robust_interruption]
62
77
  return data.interrupted?
63
78
  else
64
- Kernel.raise "RobustThreadInterruption not installed for #{thread}"
79
+ Kernel.raise NotInstalled, "RobustThreadInterruption not installed for #{_get_thread_display_name thread}"
65
80
  end
66
81
  end
67
82
  module_function :interrupted?
68
83
 
69
84
  def self.raise(thread, exception = Interrupted)
70
- if installed?
85
+ if data = thread[:robust_interruption]
71
86
  RobustInterruption.disable_interruptions(Thread.current) do
72
- _raise(thread, exception)
73
- end
74
- else
75
- _raise(thread, exception)
76
- end
77
- end
78
-
79
- def self._raise(thread, exception)
80
- data = thread[:robust_interruption]
81
- if data
82
- data.interrupted = true
83
- if data.try_lock
84
- begin
85
- thread.raise(exception)
86
- ensure
87
- data.unlock
87
+ data.interrupted = true
88
+ if data.try_lock
89
+ begin
90
+ thread.raise(exception)
91
+ ensure
92
+ data.unlock
93
+ end
88
94
  end
89
95
  end
90
96
  else
91
- Kernel.raise "RobustThreadInterruption not installed for #{thread}"
97
+ Kernel.raise NotInstalled, "RobustThreadInterruption not installed for #{_get_thread_display_name thread}"
92
98
  end
93
99
  end
94
100
 
@@ -105,7 +111,7 @@ module RobustInterruption
105
111
  data.unlock if was_interruptable
106
112
  end
107
113
  else
108
- Kernel.raise "RobustThreadInterruption not installed for #{thread}"
114
+ Kernel.raise NotInstalled, "RobustThreadInterruption not installed for #{_get_thread_display_name thread}"
109
115
  end
110
116
  end
111
117
  module_function :disable_interruptions
@@ -123,7 +129,7 @@ module RobustInterruption
123
129
  data.pop_interruption_flag
124
130
  end
125
131
  else
126
- Kernel.raise "RobustThreadInterruption not installed for #{thread}"
132
+ Kernel.raise NotInstalled, "RobustThreadInterruption not installed for #{_get_thread_display_name thread}"
127
133
  end
128
134
  end
129
135
  module_function :enable_interruptions
@@ -132,7 +138,7 @@ module RobustInterruption
132
138
  data = thread[:robust_interruption]
133
139
  if data
134
140
  if data.interruption_flags.size < 2
135
- Kernel.raise "Cannot restore interruptions state to previous value - no previous value exists"
141
+ Kernel.raise NotInstalled, "Cannot restore interruptions state to previous value - no previous value exists"
136
142
  end
137
143
  if data.interruption_flags[-2]
138
144
  enable_interruptions do
@@ -144,10 +150,23 @@ module RobustInterruption
144
150
  end
145
151
  end
146
152
  else
147
- Kernel.raise "RobustThreadInterruption not installed for #{thread}"
153
+ Kernel.raise NotInstalled, "RobustThreadInterruption not installed for #{_get_thread_display_name thread}"
148
154
  end
149
155
  end
150
156
  module_function :restore_interruptions
157
+
158
+ private
159
+ def _get_thread_display_name(thread)
160
+ if !(thread_id = thread[:id])
161
+ thread.to_s =~ /:(0x[0-9a-f]+)/i
162
+ thread_id = $1 || '?'
163
+ end
164
+ if thread_name = thread[:name]
165
+ thread_name = "(#{thread_name})"
166
+ end
167
+ return "#{thread_id}#{thread_name}"
168
+ end
169
+ module_function :_get_thread_display_name
151
170
  end
152
171
 
153
172
  end # module Utils
@@ -1,26 +1,7 @@
1
1
  #####################################################
2
- #
3
- # !!!!!!! WARNING, READ THIS !!!!!!!
4
- #
5
- #
6
- # The fact that Phusion Passenger uses Nginx
7
- # internally is considered to be an implementation
8
- # detail that the user should not bother with.
9
- # We may arbitrarily replace the Nginx core with
10
- # something else in the future.
11
- #
12
- # As such, we do not support any kind of custom
13
- # Nginx configuration in Phusion Passenger Standalone.
14
- # If you need additional Nginx modules or if you need
15
- # special Nginx configuration or whatever then you
16
- # should use Phusion Passenger for Nginx, NOT
17
- # Phusion Passenger Standalone.
18
- #
19
- # You are strongly discouraged from editing this file
20
- # and treating Phusion Passenger Standalone as an easy
21
- # way to start Nginx. We will not provide any support
22
- # for this.
23
- #
2
+ # This file is autogenerated by Phusion Passenger Standalone
3
+ # from <%= template_filename %>
4
+ # Please edit that file instead.
24
5
  #####################################################
25
6
 
26
7
 
@@ -96,6 +77,7 @@ http {
96
77
  union_station_key <%= app[:union_station_key] %>;
97
78
  <% end %>
98
79
 
80
+ # Rails asset pipeline support.
99
81
  location ~ ^/assets/ {
100
82
  error_page 490 = @static_asset;
101
83
  error_page 491 = @dynamic_request;
@@ -38,5 +38,5 @@
38
38
  // If you want to run the Nginx integration tests, then set the following
39
39
  // config option to the full path of the Nginx binary. This Nginx binary *must*
40
40
  // be compiled with Phusion Passenger support!
41
- // nginx: /usr/local/sbin/nginx
41
+ "nginx": "/usr/local/sbin/nginx"
42
42
  }
@@ -27,7 +27,6 @@ namespace tut {
27
27
  ~ApplicationPool2_DirectSpawnerTest() {
28
28
  setLogLevel(DEFAULT_LOG_LEVEL);
29
29
  unlink("stub/wsgi/passenger_wsgi.pyc");
30
- Process::maybeShutdown(process);
31
30
  PipeWatcher::onData = PipeWatcher::DataCallback();
32
31
  }
33
32
 
@@ -67,7 +66,8 @@ namespace tut {
67
66
  spawner.getConfig()->forwardStderr = false;
68
67
 
69
68
  try {
70
- spawner.spawn(options);
69
+ process = spawner.spawn(options);
70
+ process->requiresShutdown = false;
71
71
  fail("Timeout expected");
72
72
  } catch (const SpawnException &e) {
73
73
  ensure_equals(e.getErrorKind(),
@@ -89,7 +89,8 @@ namespace tut {
89
89
  spawner.getConfig()->forwardStderr = false;
90
90
 
91
91
  try {
92
- spawner.spawn(options);
92
+ process = spawner.spawn(options);
93
+ process->requiresShutdown = false;
93
94
  fail("SpawnException expected");
94
95
  } catch (const SpawnException &e) {
95
96
  ensure_equals(e.getErrorKind(),
@@ -108,6 +109,7 @@ namespace tut {
108
109
  options.startupFile = "start.rb";
109
110
  SpawnerPtr spawner = createSpawner(options);
110
111
  process = spawner->spawn(options);
112
+ process->requiresShutdown = false;
111
113
  ensure_equals(process->sockets->size(), 1u);
112
114
 
113
115
  Connection conn = process->sockets->front().checkoutConnection();
@@ -732,7 +732,7 @@ namespace tut {
732
732
  SessionPtr session3 = pool->get(options, &ticket);
733
733
  {
734
734
  LockGuard l(pool->syncher);
735
- ensure("(4)", !session1->getProcess()->isAlive());
735
+ ensure("(4)", session1->getProcess()->enabled == Process::DETACHED);
736
736
  ensure_equals("(6)", fooGroup->getWaitlist.size(), 1u);
737
737
  ensure_equals("(7)", pool->getWaitlist.size(), 0u);
738
738
  }
@@ -763,10 +763,17 @@ namespace tut {
763
763
 
764
764
  ProcessPtr process = currentSession->getProcess();
765
765
  pool->detachProcess(currentSession->getProcess());
766
- ensure(!process->isAlive());
766
+ {
767
+ LockGuard l(pool->syncher);
768
+ ensure(process->enabled == Process::DETACHED);
769
+ }
767
770
  EVENTUALLY(5,
768
771
  result = pool->getProcessCount() == 2;
769
772
  );
773
+ currentSession.reset();
774
+ EVENTUALLY(5,
775
+ result = process->isDead();
776
+ );
770
777
  }
771
778
 
772
779
  TEST_METHOD(31) {
@@ -866,6 +873,72 @@ namespace tut {
866
873
  ensure(!superGroup->garbageCollectable());
867
874
  }
868
875
 
876
+ TEST_METHOD(34) {
877
+ // When detaching a process, it waits until all sessions have
878
+ // finished before telling the process to shut down.
879
+ Options options = createOptions();
880
+ options.spawnMethod = "direct";
881
+ options.minProcesses = 0;
882
+ SessionPtr session = pool->get(options, &ticket);
883
+ ProcessPtr process = session->getProcess();
884
+
885
+ ensure(pool->detachProcess(process));
886
+ {
887
+ LockGuard l(pool->syncher);
888
+ ensure_equals(process->enabled, Process::DETACHED);
889
+ }
890
+ SHOULD_NEVER_HAPPEN(100,
891
+ LockGuard l(pool->syncher);
892
+ result = !process->isAlive()
893
+ || !process->osProcessExists();
894
+ );
895
+
896
+ session.reset();
897
+ EVENTUALLY(1,
898
+ LockGuard l(pool->syncher);
899
+ result = process->enabled == Process::DETACHED
900
+ && !process->osProcessExists()
901
+ && process->isDead();
902
+ );
903
+ }
904
+
905
+ TEST_METHOD(35) {
906
+ // When detaching a process, it waits until the OS processes
907
+ // have exited before cleaning up the in-memory data structures.
908
+ Options options = createOptions();
909
+ options.spawnMethod = "direct";
910
+ options.minProcesses = 0;
911
+ ProcessPtr process = pool->get(options, &ticket)->getProcess();
912
+
913
+ ScopeGuard g(boost::bind(::kill, process->pid, SIGCONT));
914
+ kill(process->pid, SIGSTOP);
915
+
916
+ ensure(pool->detachProcess(process));
917
+ {
918
+ LockGuard l(pool->syncher);
919
+ ensure_equals(process->enabled, Process::DETACHED);
920
+ }
921
+ EVENTUALLY(1,
922
+ result = process->getLifeStatus() == Process::SHUTDOWN_TRIGGERED;
923
+ );
924
+
925
+ SHOULD_NEVER_HAPPEN(100,
926
+ LockGuard l(pool->syncher);
927
+ result = process->isDead()
928
+ || !process->osProcessExists();
929
+ );
930
+
931
+ kill(process->pid, SIGCONT);
932
+ g.clear();
933
+
934
+ EVENTUALLY(1,
935
+ LockGuard l(pool->syncher);
936
+ result = process->enabled == Process::DETACHED
937
+ && !process->osProcessExists()
938
+ && process->isDead();
939
+ );
940
+ }
941
+
869
942
 
870
943
  /*********** Test disabling and enabling processes ***********/
871
944
 
@@ -32,7 +32,6 @@ namespace tut {
32
32
  ~ApplicationPool2_SmartSpawnerTest() {
33
33
  setLogLevel(DEFAULT_LOG_LEVEL);
34
34
  unlink("stub/wsgi/passenger_wsgi.pyc");
35
- Process::maybeShutdown(process);
36
35
  PipeWatcher::onData = PipeWatcher::DataCallback();
37
36
  }
38
37
 
@@ -79,7 +78,8 @@ namespace tut {
79
78
  options.startCommand = "ruby\1" "start.rb";
80
79
  options.startupFile = "start.rb";
81
80
  shared_ptr<SmartSpawner> spawner = createSpawner(options);
82
- spawner->spawn(options)->shutdown();
81
+ process = spawner->spawn(options);
82
+ process->requiresShutdown = false;
83
83
 
84
84
  kill(spawner->getPreloaderPid(), SIGTERM);
85
85
  // Give it some time to exit.
@@ -87,7 +87,8 @@ namespace tut {
87
87
 
88
88
  // No exception at next spawn.
89
89
  setLogLevel(-1);
90
- spawner->spawn(options)->shutdown();
90
+ process = spawner->spawn(options);
91
+ process->requiresShutdown = false;
91
92
  }
92
93
 
93
94
  TEST_METHOD(81) {
@@ -100,7 +101,8 @@ namespace tut {
100
101
  setLogLevel(-1);
101
102
  shared_ptr<SmartSpawner> spawner = createSpawner(options, true);
102
103
  try {
103
- spawner->spawn(options)->shutdown();
104
+ process = spawner->spawn(options);
105
+ process->requiresShutdown = false;
104
106
  fail("SpawnException expected");
105
107
  } catch (const SpawnException &) {
106
108
  // Pass.
@@ -130,7 +132,8 @@ namespace tut {
130
132
  spawner.getConfig()->forwardStderr = false;
131
133
 
132
134
  try {
133
- spawner.spawn(options)->shutdown();
135
+ process = spawner.spawn(options);
136
+ process->requiresShutdown = false;
134
137
  fail("SpawnException expected");
135
138
  } catch (const SpawnException &e) {
136
139
  ensure_equals(e.getErrorKind(),
@@ -161,7 +164,8 @@ namespace tut {
161
164
  spawner.getConfig()->forwardStderr = false;
162
165
 
163
166
  try {
164
- spawner.spawn(options)->shutdown();
167
+ process = spawner.spawn(options);
168
+ process->requiresShutdown = false;
165
169
  fail("SpawnException expected");
166
170
  } catch (const SpawnException &e) {
167
171
  ensure_equals(e.getErrorKind(),
@@ -192,7 +196,8 @@ namespace tut {
192
196
  spawner.getConfig()->forwardStderr = false;
193
197
 
194
198
  try {
195
- spawner.spawn(options)->shutdown();
199
+ process = spawner.spawn(options);
200
+ process->requiresShutdown = false;
196
201
  fail("SpawnException expected");
197
202
  } catch (const SpawnException &e) {
198
203
  ensure(containsSubstring(e["envvars"], "PASSENGER_FOO=foo\n"));