WordPress

¿Cómo funciona el cron de WordPress? Analizamos su código línea a línea

Helen Kayda - pexels.com

wp_cron.php al detalle

Cuando programamos una noticia, encargamos a un plugin que nos haga una copia de seguridad cada noche, a un antivirus que vigile nuestros archivos, o un tema nos avisa de que hay una actualización pendiente, se han ejecutado tareas de las que no hemos sido conscientes y eso, pasa cada segundo en un servidor.

Al igual que en nuestro cuerpo, es el bulbo raquídeo el que controla los latidos del corazón, la respiración o la presión arterial sin ser nosotros conscientes, en un servidor, esa tarea la realizan las tareas programadas o cron.

En el artículo ¿Cansado de las tareas repetitivas? ¿Qué es cron?, tratábamos al detalle este tema y, una vez conocemos la importancia de estos procesos, y el uso que podemos darle, llega el momento de entender exactamente cómo funciona el cron de WordPress, llamado wp_cron.php

 

Localizado en la raíz de la instalación de WordPress, el código del archivo es el siguiente:

<?php
/**
 * WordPress Cron Implementation for hosts, which do not offer CRON or for which
 * the user has not set up a CRON job pointing to this file.
 *
 * The HTTP request to this file will not slow down the visitor who happens to
 * visit when the cron job is needed to run.
 *
 * @package WordPress
 */

ignore_user_abort(true);

if ( !empty($_POST) || defined('DOING_AJAX') || defined('DOING_CRON') )
  die();

/**
 * Tell WordPress we are doing the CRON task.
 *
 * @var bool
 */
define('DOING_CRON', true);

if ( !defined('ABSPATH') ) {
  /** Set up WordPress environment */
  require_once( dirname( __FILE__ ) . '/wp-load.php' );
}

/**
 * Retrieves the cron lock.
 *
 * Returns the uncached `doing_cron` transient.
 *
 * @ignore
 * @since 3.3.0
 *
 * @return string|false Value of the `doing_cron` transient, 0|false otherwise.
 */
function _get_cron_lock() {
  global $wpdb;

  $value = 0;
  if ( wp_using_ext_object_cache() ) {
    /*
     * Skip local cache and force re-fetch of doing_cron transient
     * in case another process updated the cache.
     */
    $value = wp_cache_get( 'doing_cron', 'transient', true );
  } else {
    $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", '_transient_doing_cron' ) );
    if ( is_object( $row ) )
      $value = $row->option_value;
  }

  return $value;
}

if ( false === $crons = _get_cron_array() )
  die();

$keys = array_keys( $crons );
$gmt_time = microtime( true );

if ( isset($keys[0]) && $keys[0] > $gmt_time )
  die();


// The cron lock: a unix timestamp from when the cron was spawned.
$doing_cron_transient = get_transient( 'doing_cron' );

// Use global $doing_wp_cron lock otherwise use the GET lock. If no lock, trying grabbing a new lock.
if ( empty( $doing_wp_cron ) ) {
  if ( empty( $_GET[ 'doing_wp_cron' ] ) ) {
    // Called from external script/job. Try setting a lock.
    if ( $doing_cron_transient && ( $doing_cron_transient + WP_CRON_LOCK_TIMEOUT > $gmt_time ) )
      return;
    $doing_cron_transient = $doing_wp_cron = sprintf( '%.22F', microtime( true ) );
    set_transient( 'doing_cron', $doing_wp_cron );
  } else {
    $doing_wp_cron = $_GET[ 'doing_wp_cron' ];
  }
}

/*
 * The cron lock (a unix timestamp set when the cron was spawned),
 * must match $doing_wp_cron (the "key").
 */
if ( $doing_cron_transient != $doing_wp_cron )
  return;

foreach ( $crons as $timestamp => $cronhooks ) {
  if ( $timestamp > $gmt_time )
    break;

  foreach ( $cronhooks as $hook => $keys ) {

    foreach ( $keys as $k => $v ) {

      $schedule = $v['schedule'];

      if ( $schedule != false ) {
        $new_args = array($timestamp, $schedule, $hook, $v['args']);
        call_user_func_array('wp_reschedule_event', $new_args);
      }

      wp_unschedule_event( $timestamp, $hook, $v['args'] );

      /**
       * Fires scheduled events.
       *
       * @ignore
       * @since 2.1.0
       *
       * @param string $hook Name of the hook that was scheduled to be fired.
       * @param array  $args The arguments to be passed to the hook.
       */
 			do_action_ref_array( $hook, $v['args'] );

      // If the hook ran too long and another cron process stole the lock, quit.
      if ( f_get_cron_lock() != $doing_wp_cron )
        return;
    }
  }
}

if ( _get_cron_lock() == $doing_wp_cron )
  delete_transient( 'doing_cron' );

die();

 

El archivo comienza con unas líneas comentadas que justifican la existencia del mismo:

/**
 * WordPress Cron Implementation for hosts, which do not offer CRON or for which
 * the user has not set up a CRON job pointing to this file.
 *
 * The HTTP request to this file will not slow down the visitor who happens to
 * visit when the cron job is needed to run.
 *
 * @package WordPress
 */

Estas líneas avisan de que existe la posibilidad de crear un cron real desde el servidor, y de que la ejecución en segundo plano del script o “demonio“, no relentizará la carga de la web.

Crear un cron real que refuerce la función este este cron virtual, no es un tema baladí. Pues previene errores frecuentes como el de “programación perdida“.

 

A continuación, arranca el código propiamente dicho:

ignore_user_abort(true);

La primera función que se nos presenta, establece que la desconexión de un usuario no interrumpa la ejecución del script.

Esta línea es realmente importante, ya que el cron de WordPress, se ejecuta únicamente cuando un usuario accede a nuestra web. De ahí que reciba la consideración de cron virtual, en lugar de cron real o de servidor.

La función en php se define como:

int ignore_user_abort ([ bool $value ] )

Siendo su value TRUE, tendrá el efecto que hemos mencionado. Y con FALSE, el contrario.

Más información sobre la función:
http://php.net/manual/es/function.ignore-user-abort.php

 

A continuación, un condicional preventivo:

if ( !empty($_POST) || defined('DOING_AJAX') || defined('DOING_CRON') )
  die();

La función die() es equivalente a exit() es decir, provoca la interrupción de la ejecución del script actual.

Por lo tanto, el condicional dice que si se cumple alguna de las tres condiciones, la ejecución del script debe finalizar.

O más detalladamente, si:

  • !empty($_POST) No se ha recibido nada a través de un formulario o
  • defined(‘DOING_AJAX’) Existe una constante llamada “DOING_AJAX” (No confundir constante con variable) o
  • defined(‘DOING_CRON’) ) Existe una constante llamada “DOING_CRON”

Finaliza el script. En caso contrario, continúa la lectura de código.

Más información sobre los conceptos tratados:
http://php.net/manual/es/function.die.php
http://php.net/manual/es/reserved.variables.post.php
http://php.net/manual/es/function.defined.php

 

Si continúa la ejecución del script, es porque la constante “DOING_CRON” no existía, pues en caso contrario, en el paso anterior se habría detenido el script [die()].

Por lo tanto, ahora la creamos:

define('DOING_CRON', true);

 

 

Tras ello se comprueba si se ha definido la constante “ABSPATH”, que es la que guarda la url absoluta donde se aloja WordPress. Por ejemplo: https://www.loopeando.com/portal/

if ( !defined('ABSPATH') ) {
  /** Set up WordPress environment */
  require_once( dirname( __FILE__ ) . '/wp-load.php' );
}

En caso de que no se haya definido, llama al archivo “wp-load.php” que, entre otras funciones, tiene la que define la constante “ABSPATH”.

¿Y cómo define dicha constante?

Así:

define( ‘ABSPATH’, dirname( __FILE__ ) . ‘/’ );

define() es una función diferente de defined().
Como hemos comentado, defined() comprueba si dicha constante existe pero, define() le asigna valor y la crea.

Por lo tanto, esa línea asigna el valor “dirname( __FILE__ )” a la constante “ABSPATH”. Y dicho valor es la url absoluta de la instalación de WordPress.

 

Una vez comprobadas y creadas las constantes, empieza el meollo:

function _get_cron_lock() {
  global $wpdb;

  $value = 0;
  if ( wp_using_ext_object_cache() ) {
    /*
     * Skip local cache and force re-fetch of doing_cron transient
     * in case another process updated the cache.
     */
    $value = wp_cache_get( 'doing_cron', 'transient', true );
  } else {
    $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", '_transient_doing_cron' ) );
    if ( is_object( $row ) )
      $value = $row->option_value;
  }

  return $value;
}

 

Crea una función llamada “_get_cron_lock”. Dentro de la cual crea una variable global (“wpdb”) e inicializa otra: $value = 0;

Y con un condicional comprueba si se está utilizando memoria caché de objetos:

if ( wp_using_ext_object_cache() ) {

En caso de que así sea, llama a la función “wp_cache_get”, cuya estructura es:

<?php wp_cache_get( $key, $group, $force, $found ); ?>

Como está reflejada como:

$value = wp_cache_get( 'doing_cron', 'transient', true );

 

En este caso, cogería la caché de ‘doing_cron’, que son los objetos destinados a ser ejecutados en segundo plano.

Si no hubiese estado definida la función para comprobar si se está utilizando la memoria caché, se ejecutaría el “else”:

} else {
    $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", '_transient_doing_cron' ) );
    if ( is_object( $row ) )
      $value = $row->option_value;
  }

  return $value;
}

 

Que a través de una consulta a la base de datos, extraería esa información.

Así que en definitiva, esa función comprueba que las acciones a realizar por cron estén asignadas a la caché, y si no lo están, las carga de la base de datos.

¿Por qué lo hace así? Porque si por defecto cogiese esa información directamente desde la base de datos, la ejecución continua de las consultas, relentizaría la carga de la web.

if ( false === $crons = _get_cron_array() )
  die();

$keys = array_keys( $crons );
$gmt_time = microtime( true );

if ( isset($keys[0]) && $keys[0] > $gmt_time )
  die();

Como lo que obtiene en realidad es un array, separa  sus componentes asignándolos a la variable “key“. Siendo $keys[0] el primer resultado del array. $keys[1] el segundo, etc

$gmt_time = microtime( true );

microtime” es una función php que devuelve la fecha actual y, con ese dato, compara si la primera tarea que hay en cola en el cron [$keys[0]], tiene una fecha mayor que la actual. En cuyo caso, finaliza la ejecución del script.

if ( isset($keys[0]) && $keys[0] > $gmt_time )
  die();

Si la fecha de la primera tarea cron hubiese sido menor que la hora actual, seguiría ejecutándose el script.

Más información sobre “microtime“:
http://php.net/manual/es/function.microtime.php

 

A continuación ejecuta un seguro:

// The cron lock: a unix timestamp from when the cron was spawned.
$doing_cron_transient = get_transient( 'doing_cron' );

get_transient“es una función de WordPress que comprueba si la función a la que referencia (‘doing_cron’) existe o tiene un valor asignado. En caso contrario, devuelve un booleano (false).

 

// Use global $doing_wp_cron lock otherwise use the GET lock. If no lock, trying grabbing a new lock.
if ( empty( $doing_wp_cron ) ) {
  if ( empty( $_GET[ 'doing_wp_cron' ] ) ) {
    // Called from external script/job. Try setting a lock.
    if ( $doing_cron_transient && ( $doing_cron_transient + WP_CRON_LOCK_TIMEOUT > $gmt_time ) )
      return;
    $doing_cron_transient = $doing_wp_cron = sprintf( '%.22F', microtime( true ) );
    set_transient( 'doing_cron', $doing_wp_cron );
  } else {
    $doing_wp_cron = $_GET[ 'doing_wp_cron' ];
  }
}

Finalmente, verifica que la variable ($doing_wp_cron) esté vacía y que no se le esté pasando nada por GET: [$_GET[ ‘doing_wp_cron’ ]]. O lo que es lo mismo, que no se esté ejecutando ninguna tarea ahora mismo.

Tras ello comprueba que si hay algo previsto en ejecución para ahora mismo o para dentro de 1 minuto:

if ( $doing_cron_transient && ( $doing_cron_transient + WP_CRON_LOCK_TIMEOUT > $gmt_time ) )
return;

¿Por qué sabemos que un minuto? Porque WP_CRON_LOCK_TIMEOUT se define a su vez como:

define('WP_CRON_LOCK_TIMEOUT', 60);  // In seconds

 

Si hubiese algo previsto, actualiza la hora del cron:

$doing_cron_transient = $doing_wp_cron = sprintf( '%.22F', microtime( true ) );

 

Siendo sprintf() una función en php que da formato a una cadena. En este caso, a la cadena microtime, que es la que marca la hora actual.

El 22F indica la precisión del valor dado por microtime. En este código, 22 decimales.

Y asigna esa hora actual a la constante “doing_cron”, quedando así actualizada a este mismo instante:

set_transient( 'doing_cron', $doing_wp_cron );

 

Es decir, ha comprobado si había algo que debía ser ejecutado (su hora de ejecución era menor que la hora actual) y, si lo había, actualiza la hora del cron a este mismo momento. Pero… ¿Y no ejecuta esa tarea pendiente? Sí, lo hace a continuación:

if ( $doing_cron_transient != $doing_wp_cron )
  return;

foreach ( $crons as $timestamp => $cronhooks ) {
  if ( $timestamp > $gmt_time )
    break;

  foreach ( $cronhooks as $hook => $keys ) {

    foreach ( $keys as $k => $v ) {

      $schedule = $v['schedule'];

      if ( $schedule != false ) {
        $new_args = array($timestamp, $schedule, $hook, $v['args']);
        call_user_func_array('wp_reschedule_event', $new_args);
      }

      wp_unschedule_event( $timestamp, $hook, $v['args'] );

      /**
       * Fires scheduled events.
       *
       * @ignore
       * @since 2.1.0
       *
       * @param string $hook Name of the hook that was scheduled to be fired.
       * @param array  $args The arguments to be passed to the hook.
       */
 			do_action_ref_array( $hook, $v['args'] );

 

A través de un foreach va recorriendo toda la cadena de tareas pendientes:

foreach ( $crons as $timestamp => $cronhooks ) {

Comprobando que su hora de ejecución es menor que la hora actual, y por lo tanto deban ser ejecutados. En caso contrario, salgo de la ejecución (“break”).

if ( $timestamp > $gmt_time )
break;

Y finalmente, ejecuta las tareas:

do_action_ref_array( $hook, $v['args'] );

 

Para finalizar el script, hay un par de seguros más:

if ( _get_cron_lock() != $doing_wp_cron )
return;
}
if ( _get_cron_lock() == $doing_wp_cron )
  delete_transient( 'doing_cron' );
die();

 

Este comprueba si el tiempo de ejecución tarda demasiado. ¿Cómo? Recordemos que la función _get_cron_lock(); asignaba las tareas pendientes de la caché de objetos, y $doing_wp_cron contiene las tareas que se están ejecutando. Si ambas son idénticas (if ( _get_cron_lock() == $doing_wp_cron )), no se están ejecutando las tareas cron, posiblemente por algún fallo en alguna de ellas. Y por lo tanto debo destruir las tareas para evitar que la web se cuelgue por un loop (delete_transient( ‘doing_cron’ );) y detener la ejecución del script (die();)

Si ambas no coinciden (if ( _get_cron_lock() != $doing_wp_cron )) quiere decir que al menos una de las tareas, ya se ha ejecutado y, por lo tanto, devuelvo el resultado: “return”.

 

Explicar un código siempre es una tarea ardua, sin embargo esperamos haber sido lo suficientemente docentes. En caso contrario, ¡no dudes en hacernos llegar tus dudas a través de los comentarios!

Cristian Sarabia Martínez

Desde que a principios de los 90 mi padre desempolvó su Spectrum, no he dejado de probar y experimentar con la tecnología.

Enamorado del mundo web, Full Stack Developer de profesión y diseñador por devoción.

Ahora hago mis pinitos en esto del blogging para compartir con vosotros un poquito de todo lo que la comunidad me ha dado.

Escribir comentario

Haz clic aquí para dejar tu comentario

Flickr

  • Pensum
  • Sur le quai
  • Happy?
  • rue Garancière
  • St. Germain
  • Thermopyles by night
  • Ella
  • Baobabs
  • Vaudou

About Author

ThemeForest

Collaboratively harness market-driven processes whereas resource-leveling internal or "organic" sources. Competently formulate.

Calendar

diciembre 2024
L M X J V S D
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

RSS Meks Blog

  • How Adding Slack Bot Boosted Our Culture of Appreciation 3 julio, 2024
    Sweet Kudos is a Slack bot that enhances employee recognition, rewards, and celebrations within your team. It empowers team members to express gratitude and appreciation effortlessly by giving virtual Kudos. The post How Adding Slack Bot Boosted Our Culture of Appreciation appeared first on Meks.
    Dusan Milovanovic
  • 10 Best Knowledge Base & Wiki WordPress Themes 2021 15 septiembre, 2021
    Running a successful online business requires an exceptional WordPress knowledge base theme that organizes documentation and helps customers. Customization options, intuitive navigation, unique layouts, and fast responsiveness are just some of the features you need. The following 10 WordPress wiki themes represent the best options for 2021 and beyond. Explore the full range to determine […]
    Dusan Milovanovic
  • How to increase WordPress Memory Limit (quick fixes) 16 junio, 2021
    Here is a post about how to increase the memory limit in WordPress. Allowed memory size exhausted error message showed up in your WordPress installation? No worries – this is one of the most common errors in WordPress. You can apply an easy fix by increasing the memory limit in your PHP. Table of Contents […]
    Dusan Milovanovic
  • How to use (and why) WordPress sitemap plugin 1 marzo, 2021
    Did you know that by knowing how to use the WordPress sitemap plugin you can significantly improve your site’s visibility and traffic? Although it isn’t mandatory to have a sitemap on your site, having one significantly improves the site’s quality, crawlability and indexing. All this is important for better optimization, which is why we wanted […]
    Ivana Cirkovic
  • 22 free and premium podcast software for your show [2021 edition] 18 enero, 2021
    You’re determined to start or improve your podcast but don’t know which podcast software to use to really make it stand out? We’ve got you! #podcasting Top 22 free and premium podcast software for your show #WordPressTips #podcasting The post 22 free and premium podcast software for your show [2021 edition] appeared first on Meks.
    Ivana Cirkovic
  • Digital storytelling with WordPress – an all-in-one guide to make your web stories pop! 23 noviembre, 2020
    Wondering how to improve digital storytelling with WordPress and build more awareness and exposure of your business? Let our guide lead the way. The post Digital storytelling with WordPress – an all-in-one guide to make your web stories pop! appeared first on Meks.
    Ivana Cirkovic
  • How to use WordPress autoposting plugin to improve your visibility and SEO? 10 septiembre, 2020
    Did you know you can use the WordPress autoposting plugin for your content efforts and improve not only your time management but your business and visibility as well? The post How to use WordPress autoposting plugin to improve your visibility and SEO? appeared first on Meks.
    Ivana Cirkovic
  • How to create a personal branding site? Step-by-step DIY guide 15 agosto, 2020
    Looking for ways and means to create a personal branding site? Well, look no further ’cause we’re giving away all the how-to’s to do it yourselves! The post How to create a personal branding site? Step-by-step DIY guide appeared first on Meks.
    Ivana Cirkovic
  • Top 15 WordPress content plugins and tools to improve your visibility and rankings 16 julio, 2020
    Let’s take a look at some of the must-have WordPress content plugins and tools to use to improve both your UX and rankings. The post Top 15 WordPress content plugins and tools to improve your visibility and rankings appeared first on Meks.
    Ivana Cirkovic
  • WCEU 2020 recap – key takeaways from the biggest online WordPress conference 9 junio, 2020
    Missed WCEU 2020 and all the exciting stuff from there? Here are all the key takeaways and main points to remember so, take notes! The post WCEU 2020 recap – key takeaways from the biggest online WordPress conference appeared first on Meks.
    Ivana Cirkovic

Text

Distinctively utilize long-term high-impact total linkage whereas high-payoff experiences. Appropriately communicate 24/365.

Archives