diff --git a/ecrire/action/super_cron.php b/ecrire/action/super_cron.php index 5b232279e3cb25dca295e35000e271e231e4ce4e..56d63c8d2aa4796faedc516bf1df8b0abb70c171 100644 --- a/ecrire/action/super_cron.php +++ b/ecrire/action/super_cron.php @@ -19,30 +19,30 @@ if (!defined('_ECRIRE_INC_VERSION')) return; /** - * Url pour lancer le cron de manière asynchrone si le serveur - * le permet - * - * On se base sur le même code que celui du pipeline affichage final + * Url pour lancer le cron de manière asynchrone si le serveur le permet * * Cette fonction est utile pour être appelée depuis un cron UNIX par exemple - * car elle retourne tout de suite + * car elle se termine tout de suite * * Exemple de tache cron Unix pour un appel toutes les minutes : - * "* * * * * curl http://www.mondomaine.tld/spip.php?action=super_cron" + * `* * * * * curl http://www.mondomaine.tld/spip.php?action=super_cron` + * + * @see queue_affichage_cron() Dont une partie du code est repris ici. + * @see action_cron() URL appelée en asynchrone pour excécuter le cron */ function action_super_cron_dist(){ // Si fsockopen est possible, on lance le cron via un socket // en asynchrone - if(function_exists('fsockopen')){ + if (function_exists('fsockopen')) { $url = generer_url_action('cron'); - $parts=parse_url($url); + $parts = parse_url($url); $fp = fsockopen($parts['host'], - isset($parts['port'])?$parts['port']:80, - $errno, $errstr, 30); + isset($parts['port'])?$parts['port']:80, + $errno, $errstr, 30); if ($fp) { - $out = "GET ".$parts['path']."?".$parts['query']." HTTP/1.1\r\n"; - $out.= "Host: ".$parts['host']."\r\n"; - $out.= "Connection: Close\r\n\r\n"; + $out = "GET ".$parts['path']."?".$parts['query']." HTTP/1.1\r\n"; + $out.= "Host: ".$parts['host']."\r\n"; + $out.= "Connection: Close\r\n\r\n"; fwrite($fp, $out); fclose($fp); return; diff --git a/ecrire/inc/genie.php b/ecrire/inc/genie.php index 71c1edd8c3d904500e7a436e2f45adafed4cec58..844323574876ace042b43fc426bd02c1d310fee3 100644 --- a/ecrire/inc/genie.php +++ b/ecrire/inc/genie.php @@ -12,58 +12,83 @@ if (!defined('_ECRIRE_INC_VERSION')) return; -// -------------------------- -// Gestion des taches de fond -// -------------------------- - -// Deux difficultes: -// - la plupart des hebergeurs ne fournissent pas le Cron d'Unix -// - les scripts usuels standard sont limites a 30 secondes - -// Solution: -// Toute connexion a SPIP s'acheve par un appel a la fonction cron() -// qui appelle la fonction surchargeable genie dans inc/ -// Sa definition standard ci-dessous prend dans une liste de taches -// la plus prioritaire, leurs dates etant donnees par leur fichier-verrou. -// Une fonction executant une tache doit retourner un nombre: -// - nul, si la tache n'a pas a etre effecutee -// - positif, si la tache a ete effectuee -// - negatif, si la tache doit etre poursuivie ou recommencee -// Elle recoit en argument la date de la derniere execution de la tache. - -// On peut appeler cette fonction avec d'autres taches (pour etendre Spip) -// specifiee par des fonctions respectant le protocole ci-dessus -// On peut modifier la frequence de chaque tache et leur ordre d'analyse -// en modifiant les variables ci-dessous. - -//---------- - -// Les taches sont dans un tableau ('nom de la tache' => periodicite) -// Cette fonction execute la tache la plus urgente -// (celle dont la date de derniere execution + la periodicite est minimale) -// La date de la derniere intervention est donnee par un fichier homonyme, -// de suffixe ".lock", modifie a chaque intervention et des le debut -// de celle-ci afin qu'un processus concurrent ne la demarre pas aussi. -// Les taches les plus longues sont tronconnees, ce qui impose d'antidater -// le fichier de verrouillage (avec la valeur absolue du code de retour). -// La fonction executant la tache est un homonyme de prefixe "genie_". -// Le fichier homonyme du repertoire "genie/" est automatiquement lu -// et il est suppose definir cette fonction. - -// http://code.spip.net/@inc_genie_dist +/** + * Gestion des tâches de fond + * + * Deux difficultés : + * - la plupart des hebergeurs ne fournissent pas le Cron d'Unix + * - les scripts usuels standard sont limites à 30 secondes + * + * Solution + * -------- + * Toute connexion à SPIP s'achève par un appel (asynchrone si possible) + * à la fonction `cron()` qui appelle la fonction surchargeable`inc_genie_dist()` + * + * Sa definition standard ci-dessous prend dans une liste de tâches + * la plus prioritaire, leurs dates étant données par leur fichier-verrou. + * + * Une fonction exécutant une tâche doit retourner un nombre : + * - nul, si la tache n'a pas à être effecutée + * - positif, si la tache a été effectuée + * - négatif, si la tache doit être poursuivie ou recommencée + * + * Elle recoit en argument la date de la dernière exécution de la tâche. + * + * On peut appeler cette fonction avec d'autres tâches (pour étendre SPIP) + * spécifiée par des fonctions respectant le protocole ci-dessus. + * + * On peut ajouter des tâches périodiques ou modifier la fréquence + * de chaque tâche et leur priorité en utilisant le pipeline + * `taches_generales_cron`. + * + * On peut également directement en déclarer avec la balise `genie` d'un paquet.xml + * de plugin, tel que `<genie nom="nom_de_la_tache" periode="86400" />` + * + * @package SPIP\Core\Genie +**/ + + + + +/** + * Prévoit l'exécution de la tâche cron la plus urgente + * + * Les tâches sont dans un tableau `'nom de la tâche' => périodicité` + * + * Cette fonction exécute la tache la plus urgente, c'est à dire + * celle dont la date de dernière exécution + la périodicité est minimale. + * + * La date de la prochaîne exécution de chaque tâche est indiquée dans la + * table SQL `spip_jobs` + * + * La fonction exécutant la tâche est (généralement) un homonyme de préfixe "genie_". + * Le fichier homonyme du repertoire "genie/" est automatiquement lu + * et il est supposé définir cette fonction. + * + * @uses queue_add_job() Lorsqu'une tâche est à forcer + * @uses queue_schedule() + * @see taches_generales() Liste des tâches déclarées + * + * @param array $taches + * Tâches dont on force maintenant l'exécution le plus tôt possible. + * Sinon, prendra la tâche la plus prioritaire. + * @return +**/ function inc_genie_dist($taches = array()) { include_spip('inc/queue'); - if (_request('exec')=='job_queue') + if (_request('exec')=='job_queue') { return false; + } $force_jobs = array(); // l'ancienne facon de lancer une tache cron immediatement // etait de la passer en parametre a ing_genie_dist // on reroute en ajoutant simplement le job a la queue, ASAP - foreach($taches as $function=>$period) + foreach($taches as $function=>$period) { $force_jobs[] = queue_add_job($function, _T('tache_cron_asap', array('function'=>$function)), array(time()-abs($period)), "genie/"); - + } + // et on passe la main a la gestion de la queue ! // en forcant eventuellement les jobs ajoute a l'instant return queue_schedule(count($force_jobs)?$force_jobs:null); @@ -127,11 +152,15 @@ function genie_invalideur_dist($t) { } /** - * Une tache periodique pour surveiller les taches crons et les relancer si besoin - * quand ce cron s'execute, il n'est plus dans la queue, donc il se replanifie - * lui meme, avec last=time() + * Une tâche périodique pour surveiller les tâches crons et les relancer si besoin + * + * Quand ce cron s'execute, il n'est plus dans la queue, donc il se replanifie + * lui même, avec last=time() * avec une dose d'aleatoire pour ne pas planifier toutes les taches au meme moment * + * @uses taches_generales() + * @uses queue_genie_replan_job() + * * @return int */ function genie_queue_watch_dist(){ @@ -139,11 +168,12 @@ function genie_queue_watch_dist(){ if ($deja_la) return; // re-entrance si l'insertion des jobs echoue (pas de table spip_jobs a l'upgrade par exemple) $deja_la = true; $taches = taches_generales(); - $programmees = sql_allfetsel('fonction','spip_jobs',sql_in('fonction',array_keys($taches))); + $programmees = sql_allfetsel('fonction', 'spip_jobs', sql_in('fonction', array_keys($taches))); $programmees = array_map('reset',$programmees); - foreach($taches as $tache=>$periode){ - if (!in_array($tache,$programmees)) - queue_genie_replan_job($tache,$periode,time()-round(rand(1,$periode)),0); + foreach ($taches as $tache => $periode) { + if (!in_array($tache, $programmees)) { + queue_genie_replan_job($tache, $periode, time() - round(rand(1, $periode)), 0); + } } $deja_la = false; return 1; diff --git a/ecrire/inc/pipelines.php b/ecrire/inc/pipelines.php index 59c02805d26976986f765b437dcdabf1300796ed..9bafabe604bbeb31ea1586211721b686486d0c76 100644 --- a/ecrire/inc/pipelines.php +++ b/ecrire/inc/pipelines.php @@ -219,6 +219,10 @@ function f_recuperer_fond($flux) { * * Cette fonction est appelée par le pipeline affichage_final * + * @pipeline affichage_final + * @uses queue_sleep_time_to_next_job() + * @uses queue_affichage_cron() + * * @param string $texte Contenu de la page envoyée au navigateur * @return string Contenu de la page envoyée au navigateur */ diff --git a/ecrire/inc/pipelines_ecrire.php b/ecrire/inc/pipelines_ecrire.php index 645c1bad7a7d7805032218b25b7562be5b68974c..a93186847e69e2da89c0f73b05aac6ffa9a35e46 100644 --- a/ecrire/inc/pipelines_ecrire.php +++ b/ecrire/inc/pipelines_ecrire.php @@ -178,6 +178,8 @@ function f_afficher_blocs_ecrire($flux) { /** * Afficher les taches en attente liees a un objet + * + * @pipeline affiche_milieu * @param string $flux * @return string */ @@ -197,8 +199,8 @@ function f_queue_affiche_milieu($flux){ } /** - * Trouver l'objet qui correspond - * a l'exec de l'espace prive passe en argument + * Trouver l'objet qui correspond à l'exec de l'espace privé passé en argument + * * renvoie false si pas d'objet en cours, ou un tableau associatif * contenant les informations table_objet_sql,table,type,id_table_objet,edition * diff --git a/ecrire/inc/queue.php b/ecrire/inc/queue.php index c62888635214a541b8a60198c5815de66e7a0079..7ed951350a74b8c56b78be8fa211512250454231 100644 --- a/ecrire/inc/queue.php +++ b/ecrire/inc/queue.php @@ -259,18 +259,27 @@ function queue_start_job($row){ } /** - * Scheduler : - * Prend une par une les taches en attente - * et les lance, dans la limite d'un temps disponible total - * et d'un nombre maxi de taches + * Exécute les prochaînes tâches cron et replanifie les suivantes + * + * Prend une par une les tâches en attente et les lance, dans la limite + * d'un temps disponible total et d'un nombre maxi de tâches * - * La date de la prochaine tache a executer est mise a jour - * apres chaque chaque tache finie - * afin de relancer le scheduler uniquement quand c'est necessaire + * La date de la prochaine tâche à exécuter est mise à jour + * après chaque chaque tâche finie afin de relancer le scheduler uniquement + * quand c'est nécessaire * + * @uses queue_sleep_time_to_next_job() + * @uses queue_error_handler() Pour capturer les erreurs en fin de hit + * @uses queue_start_job() + * @uses queue_close_job() + * @uses queue_update_next_job_time() + * * @param array $force_jobs - * list of id_job to execute when provided - * @return null|false + * list of id_job to execute when provided + * @return null|bool + * - null : pas de tâche à réaliser maintenant + * - false : pas de connexion SQL + * - true : une planification a été faite. */ function queue_schedule($force_jobs = null){ $time = time(); @@ -352,11 +361,15 @@ function queue_schedule($force_jobs = null){ } /** - * Terminer un job au status _JQ_PENDING : + * Terminer un job au status _JQ_PENDING + * * - le reprogrammer si c'est un cron * - supprimer ses liens * - le detruire en dernier * + * @uses queue_is_cron_job() + * @uses queue_genie_replan_job() + * * @param array $row * @param int $time * @param int $result @@ -380,8 +393,10 @@ function queue_close_job(&$row, $time, $result = 0){ } /** - * Recuperer des erreurs auant que possible + * Récuperer des erreurs autant que possible * en terminant la gestion de la queue + * + * @uses queue_update_next_job_time() */ function queue_error_handler(){ // se remettre dans le bon dossier, car Apache le change parfois (toujours?) @@ -392,21 +407,27 @@ function queue_error_handler(){ /** - * Tester si une tache etait une tache periodique a reprogrammer + * Tester si une tâche était une tâche périodique à reprogrammer * - * @param <type> $function - * @param <type> $inclure - * @return <type> + * @uses taches_generales() + * + * @param string $function + * Nom de la fonction de tâche + * @param string $inclure + * Nom de l'inclusion contenant la fonction + * @return bool|int + * Périodicité de la tâche en secondes, si tâche périodique, sinon false. */ function queue_is_cron_job($function, $inclure){ static $taches = null; - if (strncmp($inclure,'genie/',6)==0){ - if (is_null($taches)){ + if (strncmp($inclure, 'genie/', 6) == 0){ + if (is_null($taches)) { include_spip('inc/genie'); $taches = taches_generales(); } - if (isset($taches[$function])) + if (isset($taches[$function])) { return $taches[$function]; + } } return false; } @@ -501,6 +522,11 @@ function queue_set_next_job_time($next) { * * Retourne le HTML à ajouter à la page pour declencher le cron * ou rien si on a réussi à le lancer en asynchrone. + * + * Un verrou (cron.lock) empêche l'exécution du cron plus d'une fois par seconde. + * + * @uses queue_sleep_time_to_next_job() + * @see action_cron() L'URL appelée pour déclencher le cron * * @return string */ @@ -513,8 +539,10 @@ function queue_affichage_cron(){ return $texte; // ne pas relancer si on vient de lancer dans la meme seconde par un hit concurent - if (file_exists($lock=_DIR_TMP."cron.lock") AND !(@filemtime($lock)<$_SERVER['REQUEST_TIME'])) + if (file_exists($lock = _DIR_TMP . "cron.lock") AND !(@filemtime($lock)<$_SERVER['REQUEST_TIME'])) { return $texte; + } + @touch($lock); // il y a des taches en attentes @@ -547,8 +575,8 @@ function queue_affichage_cron(){ $port = 80; } $fp = @fsockopen($scheme.$parts['host'], - isset($parts['port'])?$parts['port']:$port, - $errno, $errstr, 1); + isset($parts['port'])?$parts['port']:$port, + $errno, $errstr, 1); if ($fp) { $timeout = 200; // ms diff --git a/ecrire/inc/utils.php b/ecrire/inc/utils.php index f6a8ee7ae669de21a5c5526401825edf118c9e61..bb96a5fd50413b9615aa6e40be0065104c9a5d5a 100644 --- a/ecrire/inc/utils.php +++ b/ecrire/inc/utils.php @@ -770,39 +770,35 @@ function spip_touch($fichier, $duree = 0, $touch = true) { return true; } -// Ce declencheur de tache de fond, de l'espace prive (cf inc_presentation) -// et de l'espace public (cf #SPIP_CRON dans inc_balise), est appelee -// par un background-image car contrairement a un iframe vide, -// les navigateurs ne diront pas qu'ils n'ont pas fini de charger, -// c'est plus rassurant. -// C'est aussi plus discret qu'un <img> sous un navigateur non graphique. - -// http://code.spip.net/@action_cron + +/** + * Action qui déclenche une tache de fond + * + * @see queue_affichage_cron() + * @see action_super_cron_dist() + * @uses cron() +**/ function action_cron() { include_spip('inc/headers'); http_status(204); // No Content header("Connection: close"); - define('_DIRECT_CRON_FORCE',true); + define('_DIRECT_CRON_FORCE', true); cron(); } /** - * cron() : execution des taches de fond - * On peut lui passer en 1er (ou 2e arg pour compat) - * le tableau de taches attendu par inc_genie() - * Retourne Vrai si un tache a pu etre effectuee - * pas de verrou ici : les verrous sont geres sur chaque tache - * a chaque execution - * - * http://code.spip.net/@cron - * + * Exécution des tâches de fond + * + * @uses inc_genie_dist() + * * @param array $taches - * taches forcees + * Tâches forcées * @param array $taches_old - * taches forcees, pour compat avec ancienne syntaxe + * Tâches forcées, pour compat avec ancienne syntaxe * @return bool + * True si la tache a pu être effectuée */ -function cron ($taches = array(), $taches_old= array()) { +function cron($taches = array(), $taches_old = array()) { // si pas en mode cron force, laisser tomber. if (!defined('_DIRECT_CRON_FORCE')) return false; if (!is_array($taches)) $taches = $taches_old; // compat anciens appels @@ -821,24 +817,24 @@ function cron ($taches = array(), $taches_old= array()) { /** * Ajout d'une tache dans la file d'attente * - * @param $function - * The function name to call. - * @param $description - * A human-readable description of the queued job. - * @param $arguments - * Optional array of arguments to pass to the function. - * @param $file - * Optional file path which needs to be included for $function. - * if ends with '/', will do charger_fonction($function,$file); - * @param $no_duplicate - * If TRUE, do not add the job to the queue if one with the same function and - * arguments already exists. - * @param $time - * time for starting the job. If 0, job will start as soon as possible - * @param $priority - * -10 (low priority) to +10 (high priority), 0 is the default + * @param string $function + * The function name to call. + * @param string $description + * A human-readable description of the queued job. + * @param array $arguments + * Optional array of arguments to pass to the function. + * @param string $file + * Optional file path which needs to be included for $function. + * if ends with '/', will do charger_fonction($function,$file); + * @param bool $no_duplicate + * If TRUE, do not add the job to the queue if one with the same function and + * arguments already exists. + * @param int $time + * time for starting the job. If 0, job will start as soon as possible + * @param int $priority + * -10 (low priority) to +10 (high priority), 0 is the default * @return int - * id of job + * id of the job */ function job_queue_add($function, $description, $arguments = array(), $file = '', $no_duplicate = FALSE, $time = 0, $priority = 0) { include_spip('inc/queue'); @@ -861,10 +857,10 @@ function job_queue_remove($id_job){ * Associer une tache a un/des objets de SPIP * * @param int $id_job - * id of job to link + * id of job to link * @param array $objets - * can be a simple array('objet'=>'article','id_objet'=>23) - * or an array of simple array to link multiples objet in one time + * can be a simple array('objet'=>'article', 'id_objet'=>23) + * or an array of simple array to link multiples objet in one time */ function job_queue_link($id_job, $objets){ include_spip('inc/queue');