Java arrête le service de l'exécuteur testamentaire une fois qu'une de ses tâches assignées échoue pour une raison quelconque

12

J'ai besoin d'une sorte de service qui exécutera quelques tâches simultanément et dans un intervalle de 1 seconde pendant 1 minute.

Si l'une des tâches échoue, je veux arrêter le service et toutes les tâches qui l'ont exécuté avec une sorte d'indicateur que quelque chose s'est mal passé, sinon si après une minute tout s'est bien passé, le service s'arrêtera avec un indicateur que tout s'est bien passé.

Par exemple, j'ai 2 fonctions:

Runnable task1 = ()->{
      int num = Math.rand(1,100);
      if (num < 5){
          throw new Exception("something went wrong with this task,terminate");
      }
}

Runnable task2 = ()->{
      int num = Math.rand(1,100)
      return num < 50;
}



ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
task1schedule = scheduledExecutorService.scheduleAtFixedRate(task1, 1, 60, TimeUnit.SECONDS);
task2schedule = scheduledExecutorService.scheduleAtFixedRate(task2, 1, 60, TimeUnit.SECONDS);

if (!task1schedule || !task2schedule) scheduledExecutorService.shutdown();

Avez-vous des idées sur la façon de résoudre ce problème et de rendre les choses aussi génériques que possible?

tout grand
la source
1
Peu de choses en dehors de la question réelle, Math.randn'est pas une API intégrée. Une implémentation de Runnabledoit avoir une void rundéfinition. Le type de task1/2scheduleserait ScheduledFuture<?>dans le contexte fourni. Passant à la question réelle, comment est-il utilisé awaitTermination? Vous pourriez faire ça comme scheduledExecutorService.awaitTermination(1,TimeUnit.MINUTES);. Sinon, qu'en est- vérifier si l' une des tâches a été annulé avant son achèvement normal: if (task1schedule.isCancelled() || task2schedule.isCancelled()) scheduledExecutorService.shutdown();?
Naman
2
Il n'est pas logique de planifier des tâches à répéter toutes les minutes, mais dites ensuite que vous voulez arrêter les tâches «si après une minute tout s'est bien passé». Étant donné que vous arrêtez l'exécuteur dans les deux cas, la planification d'une tâche qui arrête l'exécuteur après une minute est triviale. Et l'avenir indique déjà si quelque chose s'est mal passé ou non. Vous n'avez pas dit quel autre type d'indicateur vous voulez.
Holger

Réponses:

8

L'idée est que les tâches poussent vers un objet commun TaskCompleteEvent. S'ils poussent une erreur, le planificateur est arrêté et toutes les tâches s'arrêtent.

Vous pouvez vérifier les résultats de chaque itération de tâche dans les cartes "erreurs" et "succès".

public class SchedulerTest {

    @Test
    public void scheduler() throws InterruptedException {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        TaskCompleteEvent taskCompleteEvent = new TaskCompleteEvent(scheduledExecutorService);
        Runnable task1 = () -> {
            int num = new Random().nextInt(100);
            if (num < 5) {
                taskCompleteEvent.message("task1-"+UUID.randomUUID().toString(), "Num "+num+" was obatined. Breaking all the executions.", true);
            }
        };
        Runnable task2 = () -> {
            int num = new Random().nextInt(100);
            taskCompleteEvent.message("task2-"+UUID.randomUUID().toString(), num < 50, false);
        };
        scheduledExecutorService.scheduleAtFixedRate(task1, 0, 1, TimeUnit.SECONDS);
        scheduledExecutorService.scheduleAtFixedRate(task2, 0, 1, TimeUnit.SECONDS);
        scheduledExecutorService.awaitTermination(60, TimeUnit.SECONDS);
        System.out.println("Success: "+taskCompleteEvent.getSuccess());
        System.out.println("Errors: "+taskCompleteEvent.getErrors());
        System.out.println("Went well?: "+taskCompleteEvent.getErrors().isEmpty());
    }

    public static class TaskCompleteEvent {

        private final ScheduledExecutorService scheduledExecutorService;
        private final Map<String, Object> errors = new LinkedHashMap<>();
        private final Map<String, Object> success = new LinkedHashMap<>();

        public TaskCompleteEvent(ScheduledExecutorService scheduledExecutorService) {
            this.scheduledExecutorService = scheduledExecutorService;
        }

        public synchronized void message(String id, Object response, boolean error) {
            if (error) {
                errors.put(id, response);
                scheduledExecutorService.shutdown();
            } else {
                success.put(id, response);
            }
        }

        public synchronized Map<String, Object> getErrors() {
            return errors;
        }

        public synchronized Map<String, Object> getSuccess() {
            return success;
        }

    }

}
ravenskater
la source
2

Vous avez juste besoin d'ajouter une tâche supplémentaire dont le travail consiste à surveiller toutes les autres tâches en cours d'exécution - et lorsqu'une des tâches surveillées échoue, il doit définir un sémaphore (indicateur) que l'assassin peut inspecter.

    ScheduledExecutorService executor = (ScheduledExecutorService) Executors.newScheduledThreadPool(2);

    // INSTANTIATE THE REMOTE-FILE-MONITOR:
    RemoteFileMonitor monitor = new RemoteFileMonitor(remotesource, localtarget);

    // THIS TimerTask PERIODICALLY TRIGGERS THE RemoteFileMonitor: 
    TimerTask remote = new TimerTask() {

        // RUN FORREST... RUN !
        public void run() {

            try { 

                kae.trace("TimerTask::run() --> Calling RemoteFileMonitor.check()");
                monitor.check();

            } catch (Exception ex) {

                // NULL TRAP: ALLOWS US TO CONTINUE AND RETRY:

            }

        }

    };

    // THIS TimerTask PERIODICALLY TRIES TO KILL THE REMOTE-FILE-MONITOR:
    TimerTask assassin = new TimerTask() {

        // WHERE DO BAD FOLKS GO WHEN THEY DIE ? 
        private final LocalDateTime death = LocalDateTime.now().plus(ConfigurationOptions.getPollingCycleTime(), ChronoUnit.MINUTES);

        // RUN FORREST... RUN !
        public void run() {

            // IS THERE LIFE AFTER DEATH ???
            if (LocalDateTime.now().isAfter(death)) {

                // THEY GO TO A LAKE OF FIRE AND FRY:
                kae.error(ReturnCode.MONITOR_POLLING_CYCLE_EXCEEDED);                   

            }

        }

    };

    // SCHEDULE THE PERIODIC EXECUTION OF THE RemoteFileMonitor: (remote --> run() monitor --> check())
    executor.scheduleAtFixedRate(remote, delay, interval, TimeUnit.MINUTES);

    // SCHEDULE PERIODIC ASSASSINATION ATTEMPTS AGAINST THE RemoteFileMonitor: (assassin --> run() --> after death --> die())
    executor.scheduleAtFixedRate(assassin, delay, 60L, TimeUnit.SECONDS);

    // LOOP UNTIL THE MONITOR COMPLETES:
    do {

        try {

            // I THINK I NEED A NAP:
            Thread.sleep(interval * 10);                

        } catch (InterruptedException e) {

            // FAIL && THEN cleanexit();
            kae.error(ReturnCode.MONITORING_ERROR, "Monitoring of the XXXXXX-Ingestion site was interrupted");

        }

        // NOTE: THE MONITOR IS SET TO 'FINISHED' WHEN THE DONE-File IS DELIVERED AND RETRIEVED:
    } while (monitor.isNotFinished());

    // SHUTDOWN THE MONITOR TASK:
    executor.shutdown();
Greg Patnude
la source
2
La classe TimerTaskn'a aucun lien avec ScheduledExecutorService; cela arrive juste à mettre en œuvre Runnable. De plus, cela n'a aucun sens de planifier une tâche périodique, juste pour vérifier si une heure particulière ( ConfigurationOptions.getPollingCycleTime()) a été atteinte. Vous en avez un ScheduledExecutorService, vous pouvez donc lui dire de planifier la tâche à l'heure souhaitée.
Holger
L'implémentation dans l'exemple que j'ai utilisé était de tuer une tâche en cours d'exécution après un certain temps si la tâche ne s'était pas terminée. Le cas d'utilisation était le suivant: si le serveur distant n'a pas déposé de fichier dans les 2 heures - arrêtez la tâche. c'est ce que le PO a demandé.
Greg Patnude
Avez-vous lu et compris mon commentaire? Peu importe ce que fait le code, il utilise une classe découragée sans raison, remplacez simplement TimerTaskparRunnable et vous avez résolu le problème, sans changer ce que fait le code. De plus, il suffit de l'utiliser executor.schedule(assassin, ConfigurationOptions.getPollingCycleTime(), ChronoUnit.MINUTES);et il s'exécutera une fois à l'heure souhaitée, par conséquent, la if(LocalDateTime.now().isAfter(death))vérification est obsolète. Encore une fois, cela ne change pas ce que fait le code, en plus de le faire beaucoup plus simple et plus efficace.
Holger