Carnet Wiki

Programmer une tâche cron sans dérive horaire

La documentation https://programmer.spip.net/Declarer-une-tache présente comment déclarer une tâche cron et sa périodicité.

Cette page présente comment s’assurer qu’un cron s’exécute tous les jours à peu près à la même heure, sans dérive horaire autour du cadran, en compensant les délais impondérables pour qu’ils ne s’additionnent pas chaque jour.

Rappel

(Voir https://programmer.spip.net/100 )
La date du prochain cron dépend de la valeur renvoyée par le génie ET de la déclaration de délais faite dans le pipeline taches_generales_cron.
-  Quand la valeur renvoyée par le génie est positive, la valeur précise est ignorée, c’est juste relancé avec le délai du pipeline
-  Quand elle est négative, la valeur absolue renvoyée indique l’heure considérée par le génie comme étant celle de la dernière exécution et à partir de laquelle va s’appliquer le délai spécifié dans le pipeline. Et si le résultat est déjà dépassé, c’est programmé pour être exécuté tout de suite (dés que possible). Sinon ça prend la queue.

Déclenchement chaque jour sans dérive horaire

L’API pour les tâches cron permet de facilement déclarer des périodicités pour les déclenchements, mais il est plus délicat de déclarer des heures de déclenchements fixes chaque jour.

Voici une fonction qui aide à viser une heure fixe pour le déclenchement d’une tâche chaque jour J à une heure HH (exemple : 23).
-  Comme c’est un lancement quotidien, le délai pour ce cron est de 24h.
-  Dans le code, la constante CHAQUE_JOUR_H_DEBUT vaut l’heure de déclenchement HH = 23.
Exemple : define ('CHAQUE_JOUR_H_DEBUT', 23);
-  Le jour suivant J+1, un nouveau lancement doit se faire à la même heure HH visée environ, sans que le décalage de J ne s’ajoute au nouveau décalage de J+1.
-  Le délai de déclenchement réel de la tâche étant impossible à contrôler, on ne peut savoir quelle heure il est quand le cron se déclenche réellement : il peut être 23h, ou 23h10, le jour même... ou minuit 30, c’est à dire le lendemain.

Cette fonction évite les dérives progressives en recalant le déclenchement chaque jour, mais elle ne garantit pas que le déclenchement se fasse précisément à l’heure programmée, car cela dépend de la fréquentation du site : sur un site sans fréquentation la tâche ne se déclenchera pas, sur un site ayant une faible fréquentation, elle se déclenchera en retard.

/**
 * La date programmée pour le prochain cron dépend de la valeur renvoyée par 
 * le génie ET de la déclaration de délais faite dans le pipeline 
 * `taches_generales_cron` ou dans le `paquet.xml`.
 * Quand la valeur renvoyée par le génie > 0, 
 *   la valeur précise est ignorée, le cron est juste relancé 
 *   avec le délai du pipeline
 * Quand c'est négatif, 
 *   la valeur absolue renvoyée indique l'heure considérée comme étant celle 
 *   de la dernière exécution, à partir de laquelle va s'appliquer le délai 
 *   spécifié dans le pipeline.
 *   Si le résultat est déjà dépassé, ça sera exécuté tt de suite dés que possible
 *   Et sinon ça prend la queue en attente de l'échéance.
 * Le cron est exécutable à la date indiquée, mais dans la réalité il est exécuté 
 * après ce moment, après un délai non garanti, qui dépend de la fréquentation 
 * du site.
 * Avec le temps, les délais s'additionnent et le cron se décale tout autour 
 * du cadran horaire.
 * genie_vise_heure permet d'éviter cette dérive et de maintenir l'exécution 
 * à peu près à une heure prévue.
 * 
 * Pour cela, genie_vise_heure calcule la valeur de retour d'un génie 
 * permettant sa prochaine exécution à la même heure 
 * (le lendemain ou le jour même si le cron a tardé ou a duré longtemps).
 *
 * @param int $h_debut_cejour       heure visé le jour même. Exemple : 23
 * @return float|int
 */
function genie_vise_heure(int $h_debut_cejour) {
  // timestamp de l'heure visée (23h) du jour courant 
  $h_debut_cejour = mktime($h_debut_cejour, 0, 0); 
  $now = time();
  // S'il est plus de 23h mais moins que minuit, c'est donc dans le passé 
  if ($now > $h_debut_cejour) {
    // Le prochain cron devra se relancer "demain"
    // On veut donc garder le délai normal (de 24h) spécifié par le pipeline
    // par rapport à $h_debut_cejour reçu = 23h ce soir
    return (0-$h_debut_cejour);
  }
  // Mais si on est avant $h_debut_cejour, il est par exemple 1h ou 5h du mat,
  // C'est parceque le cron a mis très longtemps à se déclancher (gros délai) 
  // ou à s'exécuter.
  // Le 23h visé par $j_debug_cejour, c'est le jour même, dans 22 ou 18h.
  // Pour obtenir l'exécution du prochain cron à cette heure visée,
  // il faut renvoyer « 23h hier ».
  return (0 - ($h_debut_cejour - 24 * 3600));
}
JLuc - Mise à jour :10 August 2022 at 18:55