El fin del servlet

(Enero de 2004)

Introducción al problema

La función destroy() del servlet se ejecuta cuando éste va a ser destruido (normalmente por la detención del servidor). No se invoca cuando se termina la petición, sino cuando se va a descargar el servlet de memoria.

Cuando se destruye el servlet las peticiones que están pendientes de respuesta (no han entrado en doGet, doPost o service) no serán atendidas. El problema no es este, sino la posibilidad de que en destroy() liberemos recursos que son necesarios a peticiones que están en tramite de respuesta (peticiones que están dentro de doGet, doPost o service). Esto puede producir excepciones, inconsistencias, etc.

El programador debe liberar o cerrar recursos, como conexiones o flujos de entrada/salida. Ante esto, surge un interrogante, en peticiones de larga resolución pudiera ocurrir que destroy() libere recursos que todavía son necesarios (todavía se está procesando la funcción doGet/doPost/service). Para evitar esto el servidor tiene dos estrategias: no ejecuta destroy() hasta que han terminado las llamadas a las funciones de respuesta (doGet, doPost y service) o, en segundo lugar, hasta que termine un "periodo de gracia". Cada servidor implementa sus estrategias.

Para más información: Tutorial de Sun

Una estrategia para terminar de forma segura

En este capítulo vamos a ver una estrategia para liberar recursos de forma segura, algo relevante sobre todo cuando estamos procesando peticiones de largo periodo de respuesta. Esta estrategia se compone de diversas acciones:

  1. Definir un contador de peticiones o servicios activos:
    
    	private int cont_peticiones = 0; // Contador de peticiones
    
    	/*** Métodos para mantener un contador de peticiones o servicios ***/
    	protected synchronized void inc_cont_peticion() { cont_peticiones++; }
    	protected synchronized void dec_cont_peticion() { cont_peticiones--; }
    	protected synchronized int obt_cont_peticion() { return cont_peticiones; }
        

    La expresión synchronized se explica en el capítulo dedicado a programación multihilos. Aquí nos quedamos con la idea de que esta expresión nos asegura que un hilo o petición no entrará en inc_cont_peticion() (incrementa el contador) hasta que otra no termine dec_cont_peticion() (decrementa el contador).

  2. Hacer las llamadas a los métodos del contador. Es lógico suponer que incrementaremos el contador al iniciarse el método de respuesta (doGet, doPost y service) y lo decrementaremos al terminar dicho método:
    
    	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    		inc_cont_peticion(); // Incrementar contador de peticiones
    		.... definir la respuesta ....
    		dec_cont_peticion(); // Decrementar contador de peticiones
    	}
        
  3. Definir destroy() de tal forma que no se liberen recursos si hay algún servicio abierto. En el siguiente ejemplo procesamos un bucle con un periodo de espera, hasta que el contador de peticiones es cero. El periodo de espera se implementa con Thread.sleep(), que nos obliga a gestionar la excepción InterruptedException:
    
    	public void destroy() {
    		while (obt_cont_peticion() > 0) {
    			try { Thread.sleep(500); 			}
    			catch (InterruptedException e) { e.printStackTrace(); }
    		}
    		... liberar recursos ....
    	}
        

Cerrando conexiones a una base de datos

A continuación puede ver un ejemplo de como cerrar una conexión a base de datos. Lo primero que hacemos es asegurarnos que la conexión no sea null (otro proceso o hilo puede haberla liberado). La llamada a close() requiere gestionar la excepción SQLException:


	void cerrar_conexion() {
		try {
			if ( con != null )
				if (!con.isClosed())
					conexion.close();
		}
		catch (SQLException e) { e.printStackTrace(); }
	}
    

Volver al índice