Mehr Frieden -General Kyrylo Budanov:
[kivitendo-erp.git] / scripts / task_server.pl
index 1bcb719..f6c8f59 100755 (executable)
@@ -1,24 +1,14 @@
 #!/usr/bin/perl
 
-
-use List::MoreUtils qw(any);
-
 use strict;
 
 my $exe_dir;
 
 BEGIN {
   use FindBin;
-  use lib "$FindBin::Bin/..";
-
-  use SL::System::Process;
-  $exe_dir = SL::System::Process::exe_dir;
 
-  unshift @INC, "${exe_dir}/modules/override"; # Use our own versions of various modules (e.g. YAML).
-  push    @INC, "${exe_dir}/modules/fallback"; # Only use our own versions of modules if there's no system version.
-  unshift @INC, $exe_dir;
-
-  chdir($exe_dir) || die "Cannot change directory to ${exe_dir}\n";
+  unshift(@INC, $FindBin::Bin . '/../modules/override'); # Use our own versions of various modules (e.g. YAML).
+  push   (@INC, $FindBin::Bin . '/..');                  # '.' will be removed from @INC soon.
 }
 
 use CGI qw( -no_xhtml);
@@ -26,15 +16,16 @@ use Cwd;
 use Daemon::Generic;
 use Data::Dumper;
 use DateTime;
-use Encode qw();
 use English qw(-no_match_vars);
 use File::Spec;
+use List::MoreUtils qw(any);
 use List::Util qw(first);
-use POSIX qw(setuid setgid);
+use POSIX qw(setlocale setuid setgid);
 use SL::Auth;
+use SL::DBUpgrade2;
 use SL::DB::AuthClient;
 use SL::DB::BackgroundJob;
-use SL::BackgroundJob::ALL;
+use SL::System::Process;
 use SL::Form;
 use SL::Helper::DateTime;
 use SL::InstanceConfiguration;
@@ -42,10 +33,12 @@ use SL::LXDebug;
 use SL::LxOfficeConf;
 use SL::Locale;
 use SL::Mailer;
+use SL::System::Process;
 use SL::System::TaskServer;
 use Template;
 
 our %lx_office_conf;
+our $run_single_job;
 
 sub debug {
   return if !$lx_office_conf{task_server}->{debug};
@@ -63,7 +56,6 @@ sub initialize_kivitendo {
 
   package main;
 
-  Form::disconnect_standard_dbh;
   $::lxdebug       = LXDebug->new;
   $::locale        = Locale->new($::lx_office_conf{system}->{language});
   $::form          = Form->new;
@@ -93,8 +85,7 @@ sub initialize_kivitendo {
 }
 
 sub cleanup_kivitendo {
-  eval { SL::DB::Auth->new->db->dbh->rollback; };
-  eval { SL::DB::BackgroundJob->new->db->dbh->rollback; };
+  eval { SL::DB->client->dbh->rollback; };
 
   $::auth->save_session;
   $::auth->expire_sessions;
@@ -107,9 +98,10 @@ sub cleanup_kivitendo {
 }
 
 sub clean_before_sleeping {
-  Form::disconnect_standard_dbh;
   SL::DBConnect::Cache->disconnect_all_and_clear;
   SL::DB->db_cache->clear;
+
+  File::Temp::cleanup();
 }
 
 sub drop_privileges {
@@ -165,6 +157,7 @@ sub notify_on_failure {
     EVAL_PERL   => 0,
     ABSOLUTE    => 1,
     CACHE_SIZE  => 0,
+    ENCODING    => 'utf8',
   });
 
   return debug("Could not create Template instance") unless $template;
@@ -181,7 +174,7 @@ sub notify_on_failure {
       subject      => $cfg->{email_subject},
       content_type => 'text/plain',
       charset      => 'utf-8',
-      message      => Encode::decode('utf-8', $body),
+      message      => $body,
     )->send;
 
     1;
@@ -193,6 +186,11 @@ sub notify_on_failure {
 sub gd_preconfig {
   my $self = shift;
 
+  # Initialize character type locale to be UTF-8 instead of C:
+  foreach my $locale (qw(de_DE.UTF-8 en_US.UTF-8)) {
+    last if setlocale('LC_CTYPE', $locale);
+  }
+
   SL::LxOfficeConf->read($self->{configfile});
 
   die "Missing section [task_server] in config file" unless $lx_office_conf{task_server};
@@ -210,11 +208,68 @@ EOT
     exit 2;
   }
 
+  initialize_kivitendo();
+
+  my $dbupdater_auth = SL::DBUpgrade2->new(form => $::form, auth => 1)->parse_dbupdate_controls;
+  if ($dbupdater_auth->unapplied_upgrade_scripts($::auth->dbconnect)) {
+    print STDERR <<EOT;
+The authentication database requires an upgrade. Please login to
+kivitendo's administration interface in order to apply it. The task
+server cannot start until the upgrade has been applied.
+EOT
+    exit 2;
+  }
+
   drop_privileges();
 
   return ();
 }
 
+sub run_single_job_for_all_clients {
+  initialize_kivitendo();
+
+  my $clients = enabled_clients();
+
+  foreach my $client (@{ $clients }) {
+    debug("Running single job ID $run_single_job for client ID " . $client->id . " (" . $client->name . ")");
+
+    my $ok = eval {
+      initialize_kivitendo($client);
+
+      my $job = SL::DB::Manager::BackgroundJob->find_by(id => $run_single_job);
+
+      if ($job) {
+        debug(" Executing the following job: " . $job->package_name);
+      } else {
+        debug(" No jobs to execute found");
+        next;
+      }
+
+      # Provide fresh global variables in case legacy code modifies
+      # them somehow.
+      initialize_kivitendo($client);
+
+      my $history = $job->run;
+
+      debug("   Executed job " . $job->package_name .
+            "; result: " . (!$history ? "no return value" : $history->has_failed ? "failed" : "succeeded") .
+            ($history && $history->has_failed ? "; error: " . $history->error_col : ""));
+
+      notify_on_failure(history => $history) if $history && $history->has_failed;
+
+      1;
+    };
+
+    if (!$ok) {
+      my $error = $EVAL_ERROR;
+      $::lxdebug->message(LXDebug::WARN(), "Exception during execution: ${error}");
+      notify_on_failure(exception => $error);
+    }
+
+    cleanup_kivitendo();
+  }
+}
+
 sub run_once_for_all_clients {
   initialize_kivitendo();
 
@@ -241,6 +296,10 @@ sub run_once_for_all_clients {
 
         my $history = $job->run;
 
+        debug("   Executed job " . $job->package_name .
+              "; result: " . (!$history ? "no return value" : $history->has_failed ? "failed" : "succeeded") .
+              ($history && $history->has_failed ? "; error: " . $history->error_col : ""));
+
         notify_on_failure(history => $history) if $history && $history->has_failed;
       }
 
@@ -249,7 +308,7 @@ sub run_once_for_all_clients {
 
     if (!$ok) {
       my $error = $EVAL_ERROR;
-      debug("Exception during execution: ${error}");
+      $::lxdebug->message(LXDebug::WARN(), "Exception during execution: ${error}");
       notify_on_failure(exception => $error);
     }
 
@@ -258,16 +317,30 @@ sub run_once_for_all_clients {
 }
 
 sub gd_run {
+  if ($run_single_job) {
+    run_single_job_for_all_clients();
+    return;
+  }
+  $::lxdebug->message(LXDebug::INFO(), "The task server for node " . SL::System::TaskServer::node_id() . " is up and running.");
+
   while (1) {
+    $SIG{'ALRM'} = 'IGNORE';
+
     run_once_for_all_clients();
 
     debug("Sleeping");
 
     clean_before_sleeping();
 
+    if (SL::System::Process::memory_usage_is_too_high()) {
+      debug("Memory usage too high - exiting.");
+      return;
+    }
+
     my $seconds = 60 - (localtime)[0];
     if (!eval {
-      local $SIG{'ALRM'} = sub {
+      $SIG{'ALRM'} = sub {
+        $SIG{'ALRM'} = 'IGNORE';
         debug("Got woken up by SIGALRM");
         die "Alarm!\n"
       };
@@ -279,18 +352,14 @@ sub gd_run {
   }
 }
 
-sub end_of_request {
-  $main::lxdebug->show_backtrace();
-  die <<EOF;
-Job called ::end_of_request()!
-
-This usually indicates success but should not be used by background jobs. A
-backtrace has been logged. Please tell the job author to have a look at it.
-EOF
-
+sub gd_flags_more {
+  return (
+    '--run-job=<id>' => 'Run the single job with the database ID <id> no matter if it is active or when its next execution is supposed to be; the daemon will exit afterwards',
+  );
 }
 
-chdir $exe_dir;
+$exe_dir = SL::System::Process->exe_dir;
+chdir($exe_dir) || die "Cannot change directory to ${exe_dir}\n";
 
 mkdir SL::System::TaskServer::PID_BASE() if !-d SL::System::TaskServer::PID_BASE();
 
@@ -303,6 +372,9 @@ $file = File::Spec->abs2rel(Cwd::abs_path($file), Cwd::abs_path($exe_dir));
 newdaemon(configfile => $file,
           progname   => 'kivitendo-background-jobs',
           pidbase    => SL::System::TaskServer::PID_BASE() . '/',
+          options    => {
+            'run-job=i' => \$run_single_job,
+          },
           );
 
 1;