FCGI: Prozess nach Request beenden, falls belegter Speicher größer als konfigurierbar...
authorMoritz Bunkus <m.bunkus@linet-services.de>
Thu, 17 Dec 2015 10:56:09 +0000 (11:56 +0100)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Thu, 17 Dec 2015 10:56:09 +0000 (11:56 +0100)
Perl gibt Speicher nicht wieder ans Betriebssystem zurück. Um zu
erreichen, dass nach einigen sehr speicherintensiven Aktionen der Server
nicht zu swappen anfangen muss, kann der Administrator nun in der
Konfigurationsdatei Limits für den Speicherverbrauch definieren.

Werden diese Limits erreicht, so beendet sich der aktuelle
Prozess. Diese Prüfung wird erst nach vollständiger Abarbeitung eines
Requests durchgeführt. Der FCGI-Manager startet dann bei der nächsten
Anfrage automatisch einen neuen Prozess.

config/kivitendo.conf.default
dispatcher.fpl

index 4242d65..1639849 100644 (file)
@@ -65,6 +65,21 @@ bind_password =
 # and "en" (English, not perfect) are available.
 language = de
 
+# The memory limits given here determine the maximum process size
+# (vsz, the total amount of memory this process uses including memory
+# swapped out or shared with other processes) or resident set size
+# (rss, the amount of memory not swapped out/shared with other
+# processes). If either limit is reached at the end of the request
+# then the kivitendo process will exit.
+#
+# This only makes sense when running under FCGI. The FCGI manager will
+# then automatically start a new process.
+#
+# Numbers can be postfixed with KB, MB, GB. If no number is given or
+# the number is 0 then no checking will be performed.
+memory_limit_rss =
+memory_limit_vsz =
+
 [paths]
 # path to temporary files (must be writeable by the web server)
 userspath = users
index 7d2dfb5..5e384ce 100755 (executable)
@@ -3,8 +3,58 @@
 use strict;
 
 use FCGI;
+use IO::File;
 use SL::Dispatcher;
 use SL::FCGIFixes;
+use SL::LXDebug;
+
+sub _parse_number_with_unit {
+  my ($number) = @_;
+
+  return undef   unless defined $number;
+  return $number unless $number =~ m{^ \s* (\d+) \s* ([kmg])b \s* $}xi;
+
+  my %factors = (K => 1024, M => 1024 * 1024, G => 1024 * 1024 * 1024);
+
+  return $1 * $factors{uc $2};
+}
+
+sub _memory_usage_is_too_high {
+  return undef unless $::lx_office_conf{system};
+
+  my %limits = (
+    rss  => _parse_number_with_unit($::lx_office_conf{system}->{memory_limit_rss}),
+    size => _parse_number_with_unit($::lx_office_conf{system}->{memory_limit_vsz}),
+  );
+
+  # $::lxdebug->dump(0, "limits", \%limits);
+
+  return undef unless $limits{rss} || $limits{vsz};
+
+  my %usage;
+
+  my $in = IO::File->new("/proc/$$/status", "r") or return undef;
+
+  while (<$in>) {
+    chomp;
+    $usage{lc $1} = _parse_number_with_unit($2) if m{^ vm(rss|size): \s* (\d+ \s* [kmg]b) \s* $}ix;
+  }
+
+  $in->close;
+
+  # $::lxdebug->dump(0, "usage", \%usage);
+
+  foreach my $type (keys %limits) {
+    next if !$limits{$type};
+    next if $limits{$type} >= ($usage{$type} // 0);
+
+    $::lxdebug->message(LXDebug::WARN(), "Exiting due to memory size limit reached for type '${type}': limit " . $limits{$type} . " bytes, usage " . $usage{$type} . " bytes");
+
+    return 1;
+  }
+
+  return 0;
+}
 
 our $dispatcher = SL::Dispatcher->new('FastCGI');
 $dispatcher->pre_startup_setup;
@@ -12,6 +62,9 @@ SL::FCGIFixes::apply_fixes();
 $dispatcher->pre_startup_checks;
 
 my $request = FCGI::Request();
-$dispatcher->handle_request($request) while $request->Accept() >= 0;
+while ($request->Accept() >= 0) {
+  $dispatcher->handle_request($request);
+  exit if _memory_usage_is_too_high();
+}
 
 1;