From c0d36979ee63f4934dc3c41ee81773cf3c441580 Mon Sep 17 00:00:00 2001 From: Florian Charlaix Date: Mon, 22 Aug 2022 17:34:47 +0200 Subject: [PATCH] Work for #21780 Add cleanUnfinishedCronjob job Add cleanUnfinishedCronjob cronjob to clean unfinished job Set job processing to 0, last result as an error with the reason in last output --- htdocs/core/class/utils.class.php | 70 +++++++++++++++++++++++++++ htdocs/core/modules/modCron.class.php | 1 + htdocs/cron/class/cronjob.class.php | 2 +- htdocs/langs/en_US/cron.lang | 2 + 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/htdocs/core/class/utils.class.php b/htdocs/core/class/utils.class.php index 9a330c7aba5..9992f1a517e 100644 --- a/htdocs/core/class/utils.class.php +++ b/htdocs/core/class/utils.class.php @@ -1334,4 +1334,74 @@ class Utils return $result; } } + + /** + * Clean unfinished cronjob in processing when pid is no longer present in the system + * CAN BE A CRON TASK + * + * @return int 0 if OK, < 0 if KO (this function is used also by cron so only 0 is OK) + * @throws Exception + */ + public function cleanUnfinishedCronjob() + { + global $db, $user; + dol_syslog("Utils::cleanUnfinishedCronjob Starting cleaning"); + + // Import Cronjob class if not present + dol_include_once('/cron/class/cronjob.class.php'); + + // Get this job object + $this_job = new Cronjob($db); + $this_job->fetch(-1, 'Utils', 'cleanUnfinishedCronjob'); + if (empty($this_job->id) || !empty($this_job->error)) { + dol_syslog("Utils::cleanUnfinishedCronjob Unable to fetch himself: ".$this_job->error, LOG_ERR); + return -1; + } + + // Set this job processing to 0 to avoid being locked by his processing state + $this_job->processing = 0; + if ($this_job->update($user) < 0) { + dol_syslog("Utils::cleanUnfinishedCronjob Unable to update himself: ".implode(', ', $this_job->errors), LOG_ERR); + return -1; + } + + $cron_job = new Cronjob($db); + $cron_job->fetchAll('DESC', 't.rowid', 0, 0, 1, '', 1); + + // Iterate over all jobs in processing (this can't be this job since his state is set to 0 before) + foreach ($cron_job->lines as $job_line) { + // Avoid job with no PID + if (empty($job_line->pid)) { + dol_syslog("Utils::cleanUnfinishedCronjob Cronjob ".$job_line->id." don't have a PID", LOG_DEBUG); + continue; + } + + $job = new Cronjob($db); + $job->fetch($job_line->id); + if (empty($job->id) || !empty($job->error)) { + dol_syslog("Utils::cleanUnfinishedCronjob Cronjob ".$job_line->id." can't be fetch: ".$job->error, LOG_ERR); + continue; + } + + // Calling posix_kill with the 0 kill signal will return true if the process is running, false otherwise. + if (! posix_kill($job->pid, 0)) { + // Clean processing and pid values + $job->processing = 0; + $job->pid = null; + + // Set last result as an error and add the reason on the last output + $job->lastresult = -1; + $job->lastoutput = 'Job cleaned'; + + if ($job->update($user) < 0) { + dol_syslog("Utils::cleanUnfinishedCronjob Cronjob ".$job_line->id." can't be updated: ".implode(', ', $job->errors), LOG_ERR); + continue; + } + dol_syslog("Utils::cleanUnfinishedCronjob Cronjob ".$job_line->id." cleaned"); + } + } + + dol_syslog("Utils::cleanUnfinishedCronjob Cleaning completed"); + return 0; + } } diff --git a/htdocs/core/modules/modCron.class.php b/htdocs/core/modules/modCron.class.php index 290eea449f5..e5e1b86f56b 100644 --- a/htdocs/core/modules/modCron.class.php +++ b/htdocs/core/modules/modCron.class.php @@ -101,6 +101,7 @@ class modCron extends DolibarrModules 0=>array('entity'=>0, 'label'=>'PurgeDeleteTemporaryFilesShort', 'jobtype'=>'method', 'class'=>'core/class/utils.class.php', 'objectname'=>'Utils', 'method'=>'purgeFiles', 'parameters'=>'tempfilesold+logfiles', 'comment'=>'PurgeDeleteTemporaryFiles', 'frequency'=>2, 'unitfrequency'=>3600 * 24 * 7, 'priority'=>50, 'status'=>1, 'test'=>true), 1=>array('entity'=>0, 'label'=>'MakeLocalDatabaseDumpShort', 'jobtype'=>'method', 'class'=>'core/class/utils.class.php', 'objectname'=>'Utils', 'method'=>'dumpDatabase', 'parameters'=>'none,auto,1,auto,10', 'comment'=>'MakeLocalDatabaseDump', 'frequency'=>1, 'unitfrequency'=>3600 * 24 * 7, 'priority'=>90, 'status'=>0, 'test'=>'in_array($conf->db->type, array(\'mysql\', \'mysqli\'))'), 2=>array('entity'=>0, 'label'=>'MakeSendLocalDatabaseDumpShort', 'jobtype'=>'method', 'class'=>'core/class/utils.class.php', 'objectname'=>'Utils', 'method'=>'sendDumpDatabase', 'parameters'=>',,,,,sql', 'comment'=>'MakeSendLocalDatabaseDump', 'frequency'=>1, 'unitfrequency'=>604800, 'priority'=>91, 'status'=>0, 'test'=>'!empty($conf->global->MAIN_ALLOW_BACKUP_BY_EMAIL) && in_array($conf->db->type, array(\'mysql\', \'mysqli\'))'), + 3=>array('entity'=>0, 'label'=>'CleanUnfinishedCronjobShort', 'jobtype'=>'method', 'class'=>'core/class/utils.class.php', 'objectname'=>'Utils', 'method'=>'cleanUnfinishedCronjob', 'parameters'=>'', 'comment'=>'CleanUnfinishedCronjob', 'frequency'=>5, 'unitfrequency'=>60, 'priority'=>10, 'status'=>1, 'test'=>true), // 1=>array('entity'=>0, 'label'=>'My label', 'jobtype'=>'command', 'command'=>'', 'parameters'=>'', 'comment'=>'Comment', 'frequency'=>1, 'unitfrequency'=>3600*24) ); diff --git a/htdocs/cron/class/cronjob.class.php b/htdocs/cron/class/cronjob.class.php index 714622e8014..9bc8400188f 100644 --- a/htdocs/cron/class/cronjob.class.php +++ b/htdocs/cron/class/cronjob.class.php @@ -1140,7 +1140,7 @@ class Cronjob extends CommonObject $this->lastoutput = ''; $this->lastresult = ''; $this->processing = 1; // To know job was started - $this->pid = dol_getmypid(); + $this->pid = function_exists('getmypid') ? getmypid() : null; // Avoid dol_getmypid to get null if the function is not available $this->nbrun = $this->nbrun + 1; $result = $this->update($user); // This include begin/commit if ($result < 0) { diff --git a/htdocs/langs/en_US/cron.lang b/htdocs/langs/en_US/cron.lang index 9705f8823b0..d5f784248be 100644 --- a/htdocs/langs/en_US/cron.lang +++ b/htdocs/langs/en_US/cron.lang @@ -84,6 +84,8 @@ MakeLocalDatabaseDumpShort=Local database backup MakeLocalDatabaseDump=Create a local database dump. Parameters are: compression ('gz' or 'bz' or 'none'), backup type ('mysql', 'pgsql', 'auto'), 1, 'auto' or filename to build, number of backup files to keep MakeSendLocalDatabaseDumpShort=Send local database backup MakeSendLocalDatabaseDump=Send local database backup by email. Parameters are: to, from, subject, message, filename (Name of file sent), filter ('sql' for backup of database only) +CleanUnfinishedCronjobShort=Clean unfinished cronjob +CleanUnfinishedCronjob=Clean cronjob stuck in processing when the process is no longer running WarningCronDelayed=Attention, for performance purpose, whatever is next date of execution of enabled jobs, your jobs may be delayed to a maximum of %s hours, before being run. DATAPOLICYJob=Data cleaner and anonymizer JobXMustBeEnabled=Job %s must be enabled