passenger 5.0.1 → 5.0.2

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 (42) hide show
  1. checksums.yaml +8 -8
  2. checksums.yaml.gz.asc +7 -7
  3. data.tar.gz.asc +7 -7
  4. data/.editorconfig +10 -0
  5. data/CHANGELOG +12 -0
  6. data/CONTRIBUTING.md +10 -0
  7. data/CONTRIBUTORS +1 -0
  8. data/Gemfile +10 -10
  9. data/build/basics.rb +5 -1
  10. data/build/common_library.rb +33 -55
  11. data/build/debian.rb +2 -4
  12. data/build/packaging.rb +3 -3
  13. data/build/preprocessor.rb +3 -204
  14. data/debian.template/{control.template → control.erb} +18 -18
  15. data/debian.template/{locations.ini.template → locations.ini.erb} +0 -0
  16. data/debian.template/{passenger-dev.install.template → passenger-dev.install.erb} +0 -0
  17. data/debian.template/passenger-doc.install.erb +2 -0
  18. data/debian.template/{passenger.install.template → passenger.install.erb} +0 -0
  19. data/debian.template/{rules.template → rules.erb} +24 -23
  20. data/doc/users_guide_snippets/tips.txt +9 -1
  21. data/ext/common/Constants.h +3 -1
  22. data/ext/common/ServerKit/Channel.h +22 -4
  23. data/ext/common/ServerKit/Context.h +3 -1
  24. data/ext/common/ServerKit/FdSinkChannel.h +9 -1
  25. data/ext/common/ServerKit/FdSourceChannel.h +9 -1
  26. data/ext/common/ServerKit/FileBufferedChannel.h +19 -7
  27. data/ext/common/Utils/SystemMetricsCollector.h +0 -1
  28. data/ext/common/Utils/VariantMap.h +22 -1
  29. data/ext/common/agents/HelperAgent/Main.cpp +15 -0
  30. data/ext/common/agents/HelperAgent/OptionParser.h +6 -1
  31. data/ext/common/agents/HelperAgent/RequestHandler.h +3 -0
  32. data/lib/phusion_passenger.rb +1 -1
  33. data/lib/phusion_passenger/common_library.rb +13 -5
  34. data/lib/phusion_passenger/config/restart_app_command.rb +8 -4
  35. data/lib/phusion_passenger/constants.rb +1 -0
  36. data/lib/phusion_passenger/standalone/command.rb +1 -1
  37. data/lib/phusion_passenger/standalone/start_command.rb +8 -0
  38. data/lib/phusion_passenger/standalone/start_command/builtin_engine.rb +1 -0
  39. data/test/cxx/ServerKit/FileBufferedChannelTest.cpp +179 -4
  40. metadata +8 -8
  41. metadata.gz.asc +7 -7
  42. data/debian.template/passenger-doc.install.template +0 -2
@@ -527,6 +527,8 @@ initializeNonPrivilegedWorkingObjects() {
527
527
  two.serverKitContext->secureModePassword = wo->password;
528
528
  two.serverKitContext->defaultFileBufferedChannelConfig.bufferDir =
529
529
  options.get("data_buffer_dir");
530
+ two.serverKitContext->defaultFileBufferedChannelConfig.threshold =
531
+ options.getUint("file_buffer_threshold");
530
532
 
531
533
  UPDATE_TRACE_POINT();
532
534
  two.requestHandler = new RequestHandler(two.serverKitContext, agentsOptions, i + 1);
@@ -979,6 +981,7 @@ setAgentsOptionsDefaults() {
979
981
  options.setDefault("sticky_sessions_cookie_name", DEFAULT_STICKY_SESSIONS_COOKIE_NAME);
980
982
  options.setDefaultBool("turbocaching", true);
981
983
  options.setDefault("data_buffer_dir", getSystemTempDir());
984
+ options.setDefaultUint("file_buffer_threshold", DEFAULT_FILE_BUFFERED_CHANNEL_THRESHOLD);
982
985
  options.setDefaultInt("response_buffer_high_watermark", DEFAULT_RESPONSE_BUFFER_HIGH_WATERMARK);
983
986
  options.setDefaultBool("selfchecks", false);
984
987
  options.setDefaultBool("server_graceful_exit", true);
@@ -1068,6 +1071,18 @@ sanityCheckOptions() {
1068
1071
  ok = false;
1069
1072
  #endif
1070
1073
  }
1074
+ if (options.has("max_request_time")) {
1075
+ if (options.getInt("max_request_time", false, 0) < 1) {
1076
+ fprintf(stderr, "ERROR: the value passed to --max-request-time must be at least 1.\n");
1077
+ ok = false;
1078
+ }
1079
+ #ifndef PASSENGER_IS_ENTERPRISE
1080
+ fprintf(stderr, "ERROR: the --max-request-time option is only supported in "
1081
+ PROGRAM_NAME " Enterprise.\nYou are currently using the open source "
1082
+ PROGRAM_NAME ". Buy " PROGRAM_NAME " Enterprise here: https://www.phusionpassenger.com/enterprise\n");
1083
+ ok = false;
1084
+ #endif
1085
+ }
1071
1086
  if (RequestHandler::parseBenchmarkMode(options.get("benchmark_mode", false))
1072
1087
  == RequestHandler::BM_UNKNOWN)
1073
1088
  {
@@ -125,7 +125,9 @@ serverUsage() {
125
125
  printf(" --min-instances N Minimum number of application processes. Default: 1\n");
126
126
  printf("\n");
127
127
  printf("Request handling options (optional):\n");
128
- printf(" --sticky-sessions Enable sticky sessions\n");
128
+ printf(" --max-request-time Abort requests that take too much time (Enterprise\n");
129
+ printf(" only)\n");
130
+ printf(" --sticky-sessions Enable sticky sessions\n");
129
131
  printf(" --sticky-sessions-cookie-name NAME\n");
130
132
  printf(" Cookie name to use for sticky sessions.\n");
131
133
  printf(" Default: " DEFAULT_STICKY_SESSIONS_COOKIE_NAME "\n");
@@ -273,6 +275,9 @@ parseServerOption(int argc, const char *argv[], int &i, VariantMap &options) {
273
275
  } else if (p.isFlag(argv[i], '\0', "--disable-friendly-error-pages")) {
274
276
  options.setBool("friendly_error_pages", false);
275
277
  i++;
278
+ } else if (p.isValueFlag(argc, i, argv[i], '\0', "--max-request-time")) {
279
+ options.setInt("max_request_time", atoi(argv[i + 1]));
280
+ i += 2;
276
281
  } else if (p.isFlag(argv[i], '\0', "--sticky-sessions")) {
277
282
  options.setBool("sticky_sessions", true);
278
283
  i++;
@@ -450,6 +450,9 @@ public:
450
450
  }
451
451
  }
452
452
 
453
+ doc["app_source_state"] = req->appSource.inspectAsJson();
454
+ doc["app_sink_state"] = req->appSink.inspectAsJson();
455
+
453
456
  return doc;
454
457
  }
455
458
  };
@@ -30,7 +30,7 @@ module PhusionPassenger
30
30
 
31
31
  PACKAGE_NAME = 'passenger'
32
32
  # Run 'rake ext/common/Constants.h' after changing this number.
33
- VERSION_STRING = '5.0.1'
33
+ VERSION_STRING = '5.0.2'
34
34
 
35
35
  PREFERRED_NGINX_VERSION = '1.6.2'
36
36
  NGINX_SHA256_CHECKSUM = 'b5608c2959d3e7ad09b20fc8f9e5bd4bc87b3bc8ba5936a513c04ed8f1391a18'
@@ -100,8 +100,11 @@ class CommonLibraryBuilder
100
100
  return link_objects.join(' ')
101
101
  end
102
102
 
103
- def enable_optimizations!
103
+ def enable_optimizations!(lto = false)
104
104
  @default_optimization_level = "-O"
105
+ if lto
106
+ @default_optimization_level << " -flto"
107
+ end
105
108
  end
106
109
 
107
110
  def define_tasks(extra_compiler_flags = nil)
@@ -132,7 +135,7 @@ private
132
135
  file(object_file => dependencies_for(options)) do
133
136
  case options[:optimize]
134
137
  when :light
135
- optimize = "-O1"
138
+ optimize = "-O"
136
139
  when true, :heavy
137
140
  optimize = "-O2"
138
141
  when :very_heavy
@@ -142,14 +145,19 @@ private
142
145
  else
143
146
  raise "Unknown optimization level #{options[:optimize]}"
144
147
  end
145
- if options[:strict_aliasing] == false
148
+ if options[:strict_aliasing] == false # and not nil
146
149
  optimize = "#{optimize} -fno-strict-aliasing"
150
+ # Disable link-time optimization so that we can no-strict-aliasing
151
+ # works: http://stackoverflow.com/a/25765338/20816
152
+ optimize.sub!(/-flto/, "")
147
153
  end
148
154
  ensure_directory_exists(File.dirname(object_file))
155
+ # We put 'optimize' at the end of the command string so that it overrides
156
+ # the default optimization level embedded in 'cflags'.
149
157
  if source_file =~ /\.c$/
150
- compile_c(source_file, "#{optimize} #{cflags} -o #{object_file}".strip)
158
+ compile_c(source_file, "#{cflags} #{optimize} -o #{object_file}".strip)
151
159
  else
152
- compile_cxx(source_file, "#{optimize} #{cxxflags} -o #{object_file}".strip)
160
+ compile_cxx(source_file, "#{cxxflags} #{optimize} -o #{object_file}".strip)
153
161
  end
154
162
  end
155
163
  end
@@ -158,12 +158,16 @@ module PhusionPassenger
158
158
  def select_app_group_name_interactively
159
159
  colors = PhusionPassenger::Utils::AnsiColors.new
160
160
 
161
+ choices = query_group_names
162
+ if choices.size == 1
163
+ # No running apps
164
+ abort_app_not_found "#{PROGRAM_NAME} is currently not serving any applications."
165
+ end
166
+
161
167
  puts "Please select the application to restart."
162
168
  puts colors.ansi_colorize("<gray>Tip: re-run this command with --help to learn how to automate it.</gray>")
163
169
  puts colors.ansi_colorize("<dgray>If the menu doesn't display correctly, press '!'</dgray>")
164
170
  puts
165
-
166
- choices = query_group_names
167
171
  menu = PhusionPassenger::Utils::TerminalChoiceMenu.new(choices, :single_choice)
168
172
  begin
169
173
  index, name = menu.query
@@ -205,7 +209,7 @@ module PhusionPassenger
205
209
  :method => restart_method)
206
210
  response = @instance.http_request("agents.s/server_admin", request)
207
211
  if response.code.to_i / 100 == 2
208
- return REXML::Document.new(response.body)
212
+ response.body
209
213
  else
210
214
  STDERR.puts "*** An error occured while communicating with the #{PROGRAM_NAME} server:"
211
215
  STDERR.puts response.body
@@ -228,7 +232,7 @@ module PhusionPassenger
228
232
  request.basic_auth("ro_admin", obtain_read_only_admin_password(@instance))
229
233
  response = @instance.http_request("agents.s/server_admin", request)
230
234
  if response.code.to_i / 100 == 2
231
- return REXML::Document.new(response.body)
235
+ REXML::Document.new(response.body)
232
236
  else
233
237
  STDERR.puts "*** An error occured while querying the #{PROGRAM_NAME} server:"
234
238
  STDERR.puts response.body
@@ -72,6 +72,7 @@ module PhusionPassenger
72
72
  MESSAGE_SERVER_MAX_PASSWORD_SIZE = 100
73
73
  POOL_HELPER_THREAD_STACK_SIZE = 1024 * 256
74
74
  DEFAULT_MBUF_CHUNK_SIZE = 16 * 32
75
+ DEFAULT_FILE_BUFFERED_CHANNEL_THRESHOLD = 1024 * 128
75
76
  SERVER_KIT_MAX_SERVER_ENDPOINTS = 4
76
77
 
77
78
  # Time limits
@@ -37,7 +37,7 @@ module PhusionPassenger
37
37
 
38
38
  def parse_options
39
39
  load_and_merge_global_options(@options)
40
- @parsed_options = {}
40
+ @parsed_options = self.class.create_default_options
41
41
  @parser = self.class.create_option_parser(@parsed_options)
42
42
  begin
43
43
  @original_argv = @argv.dup
@@ -94,6 +94,9 @@ module PhusionPassenger
94
94
  ################# Configuration loading, option parsing and initialization ###################
95
95
 
96
96
  def self.create_option_parser(options)
97
+ # Clear @parsed_options so that #remerge_all_options works.
98
+ options.clear
99
+
97
100
  # If you add or change an option, make sure to update the following places too:
98
101
  # lib/phusion_passenger/standalone/start_command/builtin_engine.rb, #build_daemon_controller_options
99
102
  # resources/templates/config/standalone.erb
@@ -245,6 +248,10 @@ module PhusionPassenger
245
248
 
246
249
  opts.separator ""
247
250
  opts.separator "Request handling options:"
251
+ opts.on("--max-request-time SECONDS", "Abort requests that take too much time#{nl}" +
252
+ "(Enterprise only)") do |val|
253
+ options[:max_request_time] = val
254
+ end
248
255
  opts.on("--sticky-sessions", "Enable sticky sessions") do
249
256
  options[:sticky_sessions] = true
250
257
  end
@@ -311,6 +318,7 @@ module PhusionPassenger
311
318
  if value !~ /=.+/
312
319
  abort "*** ERROR: invalid --ctl format: #{value}"
313
320
  end
321
+ options[:ctls] ||= []
314
322
  options[:ctls] << value
315
323
  end
316
324
  opts.on("--binaries-url-root URL", String,
@@ -139,6 +139,7 @@ module PhusionPassenger
139
139
  add_param(command, :min_instances, "--min-instances")
140
140
  add_enterprise_param(command, :concurrency_model, "--concurrency-model")
141
141
  add_enterprise_param(command, :thread_count, "--app-thread-count")
142
+ add_enterprise_param(command, :max_request_time, "--max-request-time")
142
143
  add_enterprise_flag_param(command, :rolling_restarts, "--rolling-restarts")
143
144
  add_enterprise_flag_param(command, :resist_deployment_errors, "--resist-deployment-errors")
144
145
  add_enterprise_flag_param(command, :debugger, "--debugger")
@@ -24,6 +24,7 @@ namespace tut {
24
24
  int toConsume;
25
25
  bool endConsume;
26
26
  unsigned int counter;
27
+ unsigned int buffersFlushed;
27
28
  string log;
28
29
 
29
30
  ServerKit_FileBufferedChannelTest()
@@ -32,19 +33,21 @@ namespace tut {
32
33
  channel(&context),
33
34
  toConsume(CONSUME_FULLY),
34
35
  endConsume(false),
35
- counter(0)
36
+ counter(0),
37
+ buffersFlushed(0)
36
38
  {
37
39
  initializeLibeio();
38
40
  channel.setDataCallback(dataCallback);
41
+ channel.setBuffersFlushedCallback(buffersFlushedCallback);
39
42
  channel.setHooks(this);
40
43
  Hooks::impl = NULL;
41
44
  Hooks::userData = NULL;
42
45
  }
43
46
 
44
47
  ~ServerKit_FileBufferedChannelTest() {
48
+ bg.stop(); // Prevent any runLater callbacks from running.
45
49
  channel.deinitialize(); // Cancel any event loop next tick callbacks.
46
50
  setLogLevel(DEFAULT_LOG_LEVEL);
47
- bg.stop();
48
51
  shutdownLibeio();
49
52
  }
50
53
 
@@ -76,6 +79,13 @@ namespace tut {
76
79
  }
77
80
  }
78
81
 
82
+ static void buffersFlushedCallback(FileBufferedChannel *channel) {
83
+ ServerKit_FileBufferedChannelTest *self = (ServerKit_FileBufferedChannelTest *)
84
+ channel->getHooks();
85
+ boost::lock_guard<boost::mutex> l(self->syncher);
86
+ self->buffersFlushed++;
87
+ }
88
+
79
89
  void feedChannel(const string &data) {
80
90
  bg.safe->runLater(boost::bind(&ServerKit_FileBufferedChannelTest::_feedChannel,
81
91
  this, data));
@@ -550,6 +560,144 @@ namespace tut {
550
560
  }
551
561
 
552
562
  TEST_METHOD(33) {
563
+ set_test_name("Suppose that a data chunk from disk is being passed to the callback. "
564
+ "If the callback consumes the chunk immediately and is willing to accept "
565
+ "further data, then the FileBufferedChannel will repeat this process with the "
566
+ "next chunk from disk");
567
+
568
+ // Setup a FileBufferedChannel in the in-file mode.
569
+ toConsume = -1;
570
+ context.defaultFileBufferedChannelConfig.threshold = 1;
571
+ startLoop();
572
+ feedChannel("hello");
573
+ feedChannel("world!");
574
+ EVENTUALLY(5,
575
+ result = getChannelMode() == FileBufferedChannel::IN_FILE_MODE;
576
+ );
577
+ EVENTUALLY(5,
578
+ result = getChannelWriterState() == FileBufferedChannel::WS_INACTIVE;
579
+ );
580
+ ensure_equals(getChannelBytesBuffered(), 0u);
581
+
582
+ // Consume the initial "hello" so that the FileBufferedChannel starts
583
+ // reading "world" from disk. When "world" is read, we first consume
584
+ // "world" only, then "!" too.
585
+ context.defaultFileBufferedChannelConfig.maxDiskChunkReadSize = sizeof("world") - 1;
586
+ toConsume = CONSUME_FULLY;
587
+ channelConsumed(sizeof("hello") - 1, false);
588
+ EVENTUALLY(5,
589
+ LOCK();
590
+ result = log ==
591
+ "Data: hello\n"
592
+ "Data: world\n"
593
+ "Data: !\n";
594
+ );
595
+ }
596
+
597
+ TEST_METHOD(34) {
598
+ set_test_name("Suppose that a data chunk from disk is being passed to the callback. "
599
+ "If the callback consumes the chunk asynchronously, and is willing "
600
+ "to accept further data, then the FileBufferedChannel will repeat this process "
601
+ "with the next chunk from disk after the channel has become idle");
602
+
603
+ // Setup a FileBufferedChannel in the in-file mode.
604
+ toConsume = -1;
605
+ context.defaultFileBufferedChannelConfig.threshold = 1;
606
+ startLoop();
607
+ feedChannel("hello");
608
+ feedChannel("world!");
609
+ EVENTUALLY(5,
610
+ result = getChannelMode() == FileBufferedChannel::IN_FILE_MODE;
611
+ );
612
+ EVENTUALLY(5,
613
+ result = getChannelWriterState() == FileBufferedChannel::WS_INACTIVE;
614
+ );
615
+ ensure_equals(getChannelBytesBuffered(), 0u);
616
+
617
+ // Consume the initial "hello" so that the FileBufferedChannel starts
618
+ // reading "world" from disk.
619
+ context.defaultFileBufferedChannelConfig.maxDiskChunkReadSize = sizeof("world") - 1;
620
+ channelConsumed(sizeof("hello") - 1, false);
621
+ EVENTUALLY(5,
622
+ LOCK();
623
+ result = log ==
624
+ "Data: hello\n";
625
+ );
626
+ // We haven't consumed "world" yet, so the FileBufferedChannel should
627
+ // be waiting for it to become idle.
628
+ EVENTUALLY(5,
629
+ result = getChannelReaderState() == FileBufferedChannel::RS_WAITING_FOR_CHANNEL_IDLE;
630
+ );
631
+
632
+ // Now consume "world".
633
+ channelConsumed(sizeof("world") - 1, false);
634
+ EVENTUALLY(5,
635
+ LOCK();
636
+ result = log ==
637
+ "Data: hello\n"
638
+ "Data: world\n";
639
+ );
640
+ // We haven't consumed "!" yet, so the FileBufferedChannel should
641
+ // be waiting for it to become idle.
642
+ EVENTUALLY(5,
643
+ result = getChannelReaderState() == FileBufferedChannel::RS_WAITING_FOR_CHANNEL_IDLE;
644
+ );
645
+
646
+ // Now consume "!".
647
+ channelConsumed(sizeof("!") - 1, false);
648
+ EVENTUALLY(5,
649
+ LOCK();
650
+ result = log ==
651
+ "Data: hello\n"
652
+ "Data: world\n"
653
+ "Data: !\n";
654
+ );
655
+ }
656
+
657
+ TEST_METHOD(35) {
658
+ set_test_name("Suppose that a data chunk from disk is being passed to the callback. "
659
+ "If the callback consumes the chunk immediately, but is not willing "
660
+ "to accept further data, then the FileBufferedChannel will terminate");
661
+
662
+ // Setup a FileBufferedChannel in the in-file mode.
663
+ toConsume = -1;
664
+ context.defaultFileBufferedChannelConfig.threshold = 1;
665
+ startLoop();
666
+ feedChannel("hello");
667
+ feedChannel("world!");
668
+ EVENTUALLY(5,
669
+ result = getChannelMode() == FileBufferedChannel::IN_FILE_MODE;
670
+ );
671
+ EVENTUALLY(5,
672
+ result = getChannelWriterState() == FileBufferedChannel::WS_INACTIVE;
673
+ );
674
+ ensure_equals(getChannelBytesBuffered(), 0u);
675
+
676
+ // Consume the initial "hello" so that the FileBufferedChannel starts
677
+ // reading "world" from disk. When it is read, we will consume it fully
678
+ // while ending the channel.
679
+ context.defaultFileBufferedChannelConfig.maxDiskChunkReadSize = sizeof("world") - 1;
680
+ toConsume = CONSUME_FULLY;
681
+ endConsume = true;
682
+ channelConsumed(sizeof("hello") - 1, false);
683
+ EVENTUALLY(5,
684
+ LOCK();
685
+ result = log ==
686
+ "Data: hello\n"
687
+ "Data: world\n";
688
+ );
689
+ EVENTUALLY(5,
690
+ result = getChannelReaderState() == FileBufferedChannel::RS_TERMINATED;
691
+ );
692
+ SHOULD_NEVER_HAPPEN(100,
693
+ LOCK();
694
+ result = log !=
695
+ "Data: hello\n"
696
+ "Data: world\n";
697
+ );
698
+ }
699
+
700
+ TEST_METHOD(36) {
553
701
  set_test_name("If there is no unread data on disk, it passes the next "
554
702
  "in-memory buffer to the callback");
555
703
 
@@ -596,7 +744,7 @@ namespace tut {
596
744
  );
597
745
  }
598
746
 
599
- TEST_METHOD(34) {
747
+ TEST_METHOD(37) {
600
748
  set_test_name("Upon feeding EOF, the EOF is passed to the callback after "
601
749
  "all on-disk and in-memory data is passed");
602
750
 
@@ -658,7 +806,7 @@ namespace tut {
658
806
  );
659
807
  }
660
808
 
661
- TEST_METHOD(35) {
809
+ TEST_METHOD(38) {
662
810
  set_test_name("Upon feeding an error, it switches to the error mode immediately "
663
811
  "and it doesn't call the callback");
664
812
 
@@ -737,6 +885,33 @@ namespace tut {
737
885
  );
738
886
  }
739
887
 
888
+ TEST_METHOD(41) {
889
+ set_test_name("It calls the buffersFlushedCallback if the switching happens while "
890
+ "there are buffers in memory that haven't been written to disk yet");
891
+
892
+ toConsume = -1;
893
+ context.defaultFileBufferedChannelConfig.threshold = 1;
894
+ context.defaultFileBufferedChannelConfig.delayInFileModeSwitching = 1000;
895
+ startLoop();
896
+
897
+ feedChannel("hello");
898
+ feedChannel("world!");
899
+ EVENTUALLY(5,
900
+ result = getChannelMode() == FileBufferedChannel::IN_FILE_MODE;
901
+ );
902
+ ensure_equals(getChannelBytesBuffered(), 11u);
903
+
904
+ channelConsumed(sizeof("hello") - 1, false);
905
+ channelConsumed(sizeof("world!") - 1, false);
906
+ EVENTUALLY(5,
907
+ result = getChannelMode() == FileBufferedChannel::IN_MEMORY_MODE;
908
+ );
909
+ EVENTUALLY(5,
910
+ LOCK();
911
+ result = buffersFlushed == 1;
912
+ );
913
+ }
914
+
740
915
 
741
916
  /***** When stopped *****/
742
917