Stop Spring Запланированное выполнение, если оно зависает после некоторого фиксированного времени

Я использовал Spring Framework Scheduled, чтобы запланировать выполнение моей работы каждые 5 минут с помощью cron. Но когда-то моя работа бесконечно ждет внешнего ресурса, и я не могу установить тайм-аут там. Я не могу использовать fixedDelay, поскольку предыдущий процесс когда-то бесконечно идет в ожидании, и я должен обновлять данные каждые 5 минут.

Итак, я искал любую опцию в Spring Framework Scheduled, чтобы остановить этот процесс/поток после того, как fixed-time либо он успешно выполнил, либо нет.

Ниже я установил параметр, который инициализировал ThreadPoolExecutor с 120 секундами для keepAliveTime, который я ввел в класс @Configuration. Может ли кто-нибудь сказать мне, будет ли это работать так, как я ожидал.

@Bean(destroyMethod="shutdown")
public Executor taskExecutor() {
    int coreThreads = 8;
    int maxThreads = 20;
    final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            coreThreads, maxThreads, 120L, 
            TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()
    );
    threadPoolExecutor.allowCoreThreadTimeOut(true);

    return threadPoolExecutor;
}

Ответ 1

Я не уверен, что это будет работать, как ожидалось. Действительно, keepAlive предназначен для потока IDLE, и я не знаю, находится ли ваш поток, ожидающий ресурсов, в состоянии IDLE. Кроме того, это происходит только тогда, когда количество потоков больше, чем ядро, поэтому вы не можете точно знать, когда это произойдет, если не будете следить за пулом потоков.

keepAliveTime - когда число потоков больше, чем ядро, это максимальное время, в течение которого избыточные незанятые потоки будут ждать новых задач перед завершением.

Что вы можете сделать, это следующее:

public class MyTask {

    private final long timeout;

    public MyTask(long timeout) {
        this.timeout = timeout;
    }

    @Scheduled(cron = "")
    public void cronTask() {
        Future<Object> result = doSomething();
        result.get(timeout, TimeUnit.MILLISECONDS);
    }

    @Async
    Future<Object> doSomething() {
        //what i should do
        //get ressources etc...
    }
}

Не забудьте добавить @EnableAsync

Также возможно сделать то же самое без @Async, реализуя Callable.

Изменение: имейте в виду, что он будет ждать до истечения времени ожидания, но поток, выполняющий задачу, не будет прерван. Вам нужно вызвать Future.cancel, когда возникнет TimeoutException. И в задаче проверьте isInterrupted(), чтобы остановить обработку. Если вы вызываете API, убедитесь, что isInterrupted() проверен.

Ответ 2

allowCoreThreadTimeOut и настройка тайм-аута не помогают, поскольку позволяют просто завершить рабочий поток через некоторое время без работы (см. javadocs)

Вы говорите, что ваша работа бесконечно ждет внешнего ресурса. Я уверен в этом, потому что вы (или какая-либо сторонняя библиотека, которую вы используете) используете сокеты с тайм-аутом по умолчанию. Также имейте в виду, что jvm игнорирует Thread.interrupt(), когда он блокируется в socket.connect/read.

Так что найдите библиотеку сокетов ведьм, используемую в вашей задаче (и как именно она использовалась), и измените ее по умолчанию.

Например: RestTemplate широко используется внутри Spring (в остальных клиентах, в социальных сетях Spring, в OAuth безопасности Spring и т.д.). И есть реализация ClientHttpRequestFactory для создания экземпляров RestTemplate. По умолчанию spring использует SimpleClientHttpRequestFactory, которые используют сокеты JDK. И по умолчанию все таймауты бесконечны.

Так что узнайте, где именно вы зависаете, прочитайте его и настройте его правильно.

PS Если вам не хватает времени и "чувствуете себя счастливым", попробуйте запустить свое приложение, установив в свойствах jvm sun.net.client.defaultConnectTimeout и sun.net.client.defaultReadTimeout некоторые разумные значения (более подробную информацию см. В документации).

Ответ 3

keepAliveTime предназначен только для очистки рабочих потоков, которые не были нужны какое-то время - это не влияет на время выполнения задач, переданных исполнителю.

Если все временные прерывания прерываются, вы можете запустить новый поток и присоединиться к нему с таймаутом, прервав его, если он не завершится вовремя.

public class SomeService {

    @Scheduled(fixedRate = 5 * 60 * 1000)
    public void doSomething() throws InterruptedException {
        Thread taskThread = new TaskThread();
        taskThread.start();
        taskThread.join(120 * 000);
        if(taskThread.isAlive()) {
            // We timed out
            taskThread.interrupt();
        }
    }

    private class TaskThread extends Thread {

        public void run() {
            // Do the actual work here
        }
    }
}

Ответ 4

В Spring Scheduler мы можем использовать ShedLock для выполнения блокировки и выпуска в планировщиках.
Также см. Ссылку .