Failed and Late Executions
Esta página explica, en términos prácticos, cómo el IPF Persistent Scheduler decide cuándo falla un trabajo y cómo trata las ejecuciones que se ejecutan más tarde de lo planeado.
¿Cuándo se considera un trabajo FAILED?
Un trabajo está marcado FAILED solo cuando su ejecución en tiempo de ejecución falla, es decir, cuando su SchedulingHelper hace una de las siguientes:
-
lanza una excepción dentro de
execute(..)orlateExecute(..), o-devuelve unCompletionStageque completa excepcionalmente.
No hay un componente de fondo que marque los trabajos como failed por llegar tarde o por haber perdido un turno.
Si la especificación del trabajo proporciona un identificador de fallo y un comando de fallo, el scheduler invocará el SchedulingHelper que respalda ese comando después de un fallo.
Si esos campos no se establecieron en la especificación del trabajo, no hay acción de fallo.triggered.
Manejo de ejecución tardía (normal vs lateExecute)
La ejecución tardía es una opción, especificada a través de la especificación del trabajo.lateExecutionThreshold valor.
Cuando se establece un umbral, el scheduler compara la hora actual con la última hora de ejecución permitida y si se ha superado el umbral lateExecute se llama en lugar de execute.
El último tiempo de ejecución permitido se calcula de manera diferente para trabajos únicos y recurrentes:
-
Para trabajos puntuales, el scheduler toma el tiempo de ejecución deseado (especificado en la zona horaria del trabajo) y le añade el umbral de retraso.
-
Para recurrentes (cron) trabajos el scheduler primero determina cuándo debería haberse ejecutado este trabajo al calcular los siguientes dos scheduled tiempos y trabajando hacia atrás por un intervalo. Una vez que tiene el tiempo de ejecución planificado, añade el umbral de retraso para determinar el límite.
El manejo de la ejecución tardía de trabajos recurrentes asume que el scheduling specification es un intervalo fijo cron expression. Si su cron expression resultados en intervalos variables -- por ejemplo,`"0 0 1 * *"`(el primer día de cada mes) -- el manejo de la ejecución tardía puede no funcionar como se espera.
Ejemplos mínimos
1) Trabajo único con un umbral tardío
var job = JobSpecification.builder()
.jobRequestor("invoice-service")
.singleSchedule(LocalDateTime.now().plusSeconds(30))
.zoneId(ZoneId.of("Europe/Madrid"))
.triggerIdentifier("order-123")
.triggerCommand(new RunInvoiceCommand())
.lateExecutionThreshold(Duration.ofSeconds(10)) // after desired+10s we treat it as late
.build();
schedulingModule.scheduleJob(JobSpecificationDto.fromJobSpecification(job));
2) Trabajo recurrente con un umbral tardío
var job = JobSpecificationDto.builder()
.jobRequestor("billing")
.schedulingSpecification("0 0/5 *? * *") // every 5 minutes
.triggerIdentifier("batch-1")
.triggerCommand(new ReconcileCommand())
.lateExecutionThreshold(Duration.ofMinutes(1))
.build();
schedulingModule.scheduleJob(job);
3) Implementando SchedulingHelper con manejo de retrasos y fallos
Dada una tarea:
var job = JobSpecificationDto.builder()
.jobRequestor("invoice-service")
.singleSchedule(LocalDateTime.now().plusSeconds(30)) // desired execution
.zoneId(ZoneId.of("Europe/Madrid"))
.triggerIdentifier("order-123")
.triggerCommand(new RunInvoiceCommand())
.lateExecutionThreshold(Duration.ofSeconds(10)) // after desired+10s we treat it as late
.failureIdentifier("order-123")
.failureCommand(new RunInvoiceCommandFailed())
.build();
schedulingModule.scheduleJob(job);
Regular y late executions son gestionados por un SchedulingHandler que respalda el especificado triggerCommand:
public class BillingHelper implements SchedulingHelper {
@Override
public boolean supports(SchedulingCommand cmd) {
return cmd instanceof RunInvoiceCommand;
}
// timely execution
@Override
public CompletionStage<Void> execute(String id, SchedulingCommand cmd) {
return CompletableFuture.runAsync(() -> doWork(id, cmd));
}
// late execution, triggered if execution happens after desired+10s
@Override
public CompletionStage<Void> lateExecute(String id, SchedulingCommand cmd, Duration overBy) {
log.warn("Job {} is late by {}", id, overBy);
// Optionally adjust behavior when the job is late
return execute(id, cmd);
}
}
Si execute/lateExecute lanza o completa de manera excepcional, el scheduler registra un FAILED estado para esa ejecución y activa el comando de fallo.
El comando de fallo es a su vez gestionado por un SchedulingHandler que lo respalda:
public class BillingFailedHelper implements SchedulingHelper {
@Override
public boolean supports(SchedulingCommand cmd) {
return cmd instanceof RunInvoiceCommandFailed;
}
@Override
public CompletionStage<Void> execute(String id, SchedulingCommand cmd) {
return CompletableFuture.runAsync(() -> handleFailure(id, cmd));
}
}