Implementación del cierre gradual de un pool de hilos en aplicaciones Tomcat para esperar la finalización de tareas

Concepto clave

Para garantizar que Tomcat espere a que las tareas en el pool de hilos finalicen antes de detenerse, se debe seguir este enfoque:

  1. Crear un pool de hilos singleton global al iniciar la aplicación.
  2. Implementar un ServletContextListener para gestionar el ciclo de vida.
  3. Durante el apagado, invocar métodos de cierre gradual del pool:
    • shutdown() para rechazar nuevas tareas.
    • awaitTermination(...) para bloquear hasta que las tareas en curso terminen.
  4. Forzar el cierre con shutdownNow() si se excede el tiempo de espera.

Implementación completa del código

1. Singleton del pool de hilos (recomendado como clase de utilidad)

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class GestorDeTareas {

    // Pool de hilos global único
    private static final ExecutorService SERVICIO_EJECUTOR = new ThreadPoolExecutor(
            5,
            10,
            60L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100)
    );

    public static ExecutorService obtenerServicio() {
        return SERVICIO_EJECUTOR;
    }
}

2. Implemetnación del oyente (elemento crítico)

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

@WebListener // Permite que Tomcat lo detecte automáticamente
public class ListenerApagadoPool implements ServletContextListener {

    @Override
    public void contextoIniciado(ServletContextEvent sce) {
        // Inicialización al arrancar la aplicación (opcional)
        System.out.println("Aplicación Tomcat iniciada");
    }

    @Override
    public void contextoDestruido(ServletContextEvent sce) {
        System.out.println("Tomcat deteniéndose, iniciando cierre del pool de hilos...");

        ExecutorService ejecutor = GestorDeTareas.obtenerServicio();

        // 1. Rechazar nuevas tareas
        ejecutor.shutdown();

        try {
            // 2. Esperar a que las tareas existentes finalicen (ejemplo: 30 segundos)
            if (!ejecutor.awaitTermination(30, TimeUnit.SECONDS)) {
                System.err.println("El pool de hilos no se cerró a tiempo, forzando cierre");
                // 3. Forzar cierre por timeout
                ejecutor.shutdownNow();
            }
        } catch (InterruptedException e) {
            System.err.println("Cierre del pool interrumpido");
            ejecutor.shutdownNow();
            Thread.currentThread().interrupt();
        }

        System.out.println("Pool de hilos cerrado de forma segura");
    }
}

3. Alternativa sin anotaciones: configuración en web.xml

<listener>
    <listener-class>com.miempresa.miproyecto.ListenerApagadoPool</listener-class>
</listener>

¿Por qué este método garantiza que Tomcat espere a las tareas?

  • Durante el apagado, Tomcat invoca secuencialmente todos los métodos contextoDestruido de los ServletContextListener.
  • La llamada awaitTermination(30, SECONDS) bloquea el hilo actual hasta que:
    • Todas las tareas finalican, o
    • Se alcence el tiempo límite.
  • Durante este bloqueo, Tomcat no continúa con la destrucción de filtros y servlets, logrando el efecto de espera deseado.

Puntos importantes a considerar

  1. No usar Executors.newFixedThreadPool: su cola es ilimitada, lo que puede acumular demasiadas tareas y prolongar el apagado.
  2. El tiempo en awaitTermination debe ser razonable: típicamente entre 10 y 60 segundos, ajustado según la duración de tus tareas.
  3. Las tareas deberían responder a interrupciones: de lo contrario, shutdownNow() no podrá detenerlas, bloqueando el proceso hasta el timeout.
  4. Evitar bucles infinitos en tareas asíncronas o programadas: esto impediría un cierre definitivo.

Etiquetas: tomcat java servlet Pool de hilos Cierre Gradual

Publicado el 7-20 02:52