Guía 47 – Control de un único Proceso en ejecución

Es frecuente que en las aplicaciones  existan procesos «pesados» y por lo tanto «lentos» que deseamos que los usuarios no los puedan ejecutar varias veces en un periodo corto de tiempo o que estos procesos se ejecuten de uno en uno, para no sobrecargar el servidor, y así seguir dando buen tiempo de respuesta al resto de procesos y usuarios.

También, nos puede surgir el mismo problema cuando queremos que un recurso de nuestro sistema sólo esté accedido por un único proceso, quedándose a la espera de ser liberado para el resto de procesos que requieran ese recurso.

Algunos ejemplos que pueden requerir esta gestión de proceso único, son:

  • La recarga o copia de información desde otro sistema (proceso batch masivo).
  • Copia de seguridad de datos o copia de respaldo de backup previos
  • Elaboración de informes muy pesados. Procesos de cierre o reconfiguración del sistema.
  • Utilización de un recurso del server (fichero, puerto serie, USB, certificado digital, etc.)
  • Etc.

Objetivo

Disponer de una lógica, en los proyectos desarrollados en PHPRunner, para controlar:

  • Que un proceso específico sólo este en ejecución una única vez y el resto de peticiones que concurran las rechace el sistema.
  • Que un proceso específico sólo este en ejecución una única vez y que el resto de peticiones concurrentes se encolen y se liberen según el orden de llegada (cola FIFO).

DEMO:  https://fhumanes.com/single_execution/

Solución Técnica

El control de la concurrencia se puede realizar al menos con estos 3 medios.

  • A través de un «semáforo» del sistema operativo
  • A través del bloqueo de un fichero.
  • A través de una tabla del gestor de base de datos.

En este ejemplo se ha utilizado el control a través del bloqueo de un fichero (ficheros, para disponer de varios controles).

Esta es la imagen de la aplicación:

Se ha implementado 2 botones:

(1) – Botón de ejecución del proceso y rechazado de las peticiones que concurran en la ejecución.

(2) – Botón de ejecución del proceso y encolamiento de las peticiones hasta que no se termine la anterior

El proceso es muy simple, retarda el proceso 15 segundos:

<?php
// time initial
$time_initial = date('h:i:s');
// sleep 15 seg
sleep(15);
// Time final
$time_final = date('h:i:s');
?>

La función que realiza el control de un proceso y del resto, su rechazo, es:

<?php
function single_execution( $semaphore, $process ) {
    $path_semaphore = __DIR__."/semaphores/".$semaphore.".lock";
    $fp = fopen($path_semaphore, "r+");
    if ($fp == false ) { // file not found
        return array(false ,"The semaphore file does not exist!");
    }    
    if (!flock($fp, LOCK_EX|LOCK_NB, $blocked)) {
        if ($blocked) {    
            // another process holds the lock
            return array(false, "Couldn't get the lock! Other script in run!");     
        }
        else {
            // couldn't lock for another reason, e.g. no such file
            return array(false , "Error! Nothing done.");
        }
    }
    else {
        // lock obtained
        ftruncate($fp, 0);  // truncate file        
        // execute Proccess
        include ($process);    
        fflush($fp);            // flush output before releasing the lock
        flock($fp, LOCK_UN);    // release the lock        
        return array(true, "Process executed correctly");    
    }
}

La función que realiza la cola FIFO es:

<?php

function fifo_execution( $semaphore, $process ) {
    $path_semaphore = __DIR__."/semaphores/".$semaphore.".lock";
    $fp = fopen($path_semaphore, "r+");    
    if ($fp == false ) { // file not found
        return array(false ,"The semaphore file does not exist!");
    }    
    if (flock($fp, LOCK_EX)) {  // acquire an exclusive lock
        ftruncate($fp, 0);      // truncate file    
        // execute Proccess
        include ($process);    
        fflush($fp);            // flush output before releasing the lock
        flock($fp, LOCK_UN);    // release the lock       
    }    
    fclose($fp);
    return array(true, "Process executed correctly");
}

La programación de los botones de 3 estados es:

  • Previo (Javascript)
    $('a[id^="Process_unique"]').attr("disabled", true); // Deshabilitar botón
  • Server (PHP)
    include ("MyCode/semaphore_code.php");
    
    $status = single_execution("semaphore_1", __DIR__."/MyCode/slow_processes/process_1.php");
    $result['status'] = $status[0];
    $result['message'] = $status[1];
  • Después (JavaScript)
    $('a[id^="Process_unique"]').removeAttr("disabled") // Habilitar botón
    var message = 'Status = '+ result["status"]+' '+result["message"];
    ajax.setMessage(message);

Para testear la solución deberá disponer de varios navegadores en un único PC o varios navegadores en distintos equipos y cliquear los botones lo más concurrente posible.

Como en los demás ejemplos, para cualquier duda contactar conmigo a través de mi email [email protected] .

También os dejo el proyecto para que lo descarguéis y probéis en vuestros PC’s.

Adjuntos

Archivo Tamaño de archivo Descargas
zip PHPRunner 10.8 + backup de tabla 38 KB 127

Blog personal para facilitar soporte gratuito a usuarios de React y PHPRunner