EJB @ Schedule espere hasta que se complete el método


Quiero escribir un trabajo de back-ground (EJB 3.1), que se ejecuta cada minuto. Para ello utilizo la siguiente anotación:

@Schedule(minute = "*/1", hour = "*")

Que está funcionando bien.

Sin embargo, a veces el trabajo puede tomar más de un minuto. En este caso, el temporizador todavía se dispara, causando problemas de roscado.

¿Es posible, de alguna manera, terminar el scheduler si la ejecución actual no se ha completado?

Author: Arjan Tijms, 2013-01-18

4 answers

Si solo 1 temporizador puede estar activo al mismo tiempo, hay un par de soluciones.

En primer lugar, el @Timer probablemente debería estar presente en un @Singleton. En un Singleton, los métodos están bloqueados de escritura por defecto, por lo que el contenedor se bloqueará automáticamente cuando intente invocar el método timer mientras todavía haya actividad en él.

Lo siguiente es básicamente suficiente:

@Singleton
public class TimerBean {

    @Schedule(second= "*/5", minute = "*", hour = "*", persistent = false)
    public void atSchedule() throws InterruptedException {

        System.out.println("Called");
        Thread.sleep(10000);
    }
}

atSchedule está bloqueado de forma predeterminada y solo puede haber un hilo activo en él, incluyendo llamadas iniciadas por el contenedor.

Al ser bloqueado, el contenedor puede reintentar el temporizador, por lo que para evitar esto usaría un bloqueo de lectura en su lugar y delegaría a un segundo bean (el segundo bean es necesario porque EJB 3.1 no permite actualizar un bloqueo de lectura a un bloqueo de escritura).

El frijol temporizador:

@Singleton
public class TimerBean {

    @EJB
    private WorkerBean workerBean;

    @Lock(READ)
    @Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
    public void atSchedule() {

        try {
            workerBean.doTimerWork();
        } catch (Exception e) {
            System.out.println("Timer still busy");
        }
    }

}

El frijol obrero:

@Singleton
public class WorkerBean {

    @AccessTimeout(0)
    public void doTimerWork() throws InterruptedException {
        System.out.println("Timer work started");
        Thread.sleep(12000);
        System.out.println("Timer work done");
    }
}

Esto probablemente todavía imprimirá una excepción ruidosa en el registro, por lo que una solución más detallada pero más silenciosa es usar un booleano explícito:

El frijol temporizador:

@Singleton
public class TimerBean {

    @EJB
    private WorkerBean workerBean;

    @Lock(READ)
    @Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
    public void atSchedule() {
        workerBean.doTimerWork();
    }

}

El frijol obrero:

@Singleton
public class WorkerBean {

    private AtomicBoolean busy = new AtomicBoolean(false);

    @Lock(READ)
    public void doTimerWork() throws InterruptedException {

        if (!busy.compareAndSet(false, true)) {
            return;
        }

        try {
            System.out.println("Timer work started");
            Thread.sleep(12000);
            System.out.println("Timer work done");
        } finally {
            busy.set(false);
        }
    }

}

Hay algunas variaciones más posibles, por ejemplo, se podría delegar el busy check a un interceptor, o inyectar un singleton que solo contiene el booleano en el bean temporizador, y comprobar ese booleano allí, etc.

 61
Author: Arjan Tijms,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-16 09:28:07

Me encontré con el mismo problema, pero lo resolví de manera ligeramente diferente.

@Singleton
public class DoStuffTask {

    @Resource
    private TimerService timerSvc;

    @Timeout
    public void doStuff(Timer t) {
        try {
            doActualStuff(t);
        } catch (Exception e) {
            LOG.warn("Error running task", e);
        }
        scheduleStuff();
    }

    private void doActualStuff(Timer t) {

        LOG.info("Doing Stuff " + t.getInfo());
    }

    @PostConstruct
    public void initialise() {
        scheduleStuff();
    }

    private void scheduleStuff() {
        timerSvc.createSingleActionTimer(1000l, new TimerConfig());
    }

    public void stop() {
        for(Timer timer : timerSvc.getTimers()) {
            timer.cancel();
        }
    }

}

Esto funciona configurando una tarea para ejecutar en el futuro (en este caso, en un segundo). Al final de la tarea, se programa la tarea de nuevo.

EDIT: Actualizado para refactorizar el "stuff" en otro método para que podamos guardar excepciones para que la reprogramación del temporizador siempre suceda

 7
Author: drone.ah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-28 10:12:19

Desde Java EE 7 es posible usar un" EE-aware " ManagedScheduledExecutorService, es decir, en WildFly:

Por ejemplo, en un @Singleton @Startup @LocalBean, inyecte el "managed-scheduled-executor-service" predeterminado configurado en standalone.xml:

@Resource
private ManagedScheduledExecutorService scheduledExecutorService;

Programe alguna tarea en @PostConstruct para ser ejecutada, es decir, cada segundo con retraso fijo :

scheduledExecutorService.scheduleWithFixedDelay(this::someMethod, 1, 1, TimeUnit.SECONDS);

Horario con retraso fijo :

Crea y ejecuta una acción periódica que se habilita primero después de la retraso inicial, y posteriormente con el retraso between the termination of one execution and the commencement of the siguiente.[...]

No apague el planificador en @PreDestroy:

Las instancias de Servicio Ejecutor Programado administrado son administradas por el servidor de aplicaciones, por lo que las aplicaciones Java EE están prohibidas de invocar cualquier método relacionado con el ciclo de vida.

 4
Author: Torsten Römer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-07 10:34:54

Bueno, tuve un problema similar. Había un trabajo que se suponía que se ejecutaría cada 30 minutos y, a veces, el trabajo tardaba más de 30 minutos en completarse en este caso, otra instancia del trabajo comenzaba mientras que el anterior aún no había terminado. Lo resolví teniendo una variable booleana estática que mi trabajo establecería en true cada vez que comenzara a ejecutarse y luego la establecería de nuevo en false cada vez que terminara. Dado que es una variable estática, todas las instancias verán la misma copia en todo momento. Podrías incluso sincronizar el bloque cuando u establece y desactiva la variable estática. clase myjob{ booleano estático privado isRunning = false;

public executeJob(){
if (isRunning)
    return;
isRunning=true;
//execute job
isRunning=false;
  }

}
 0
Author: Rizwan Shaikh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-12 17:00:36