ScheduledExecutorService работает на локальном и тестовом сервере, но запускается только один раз на реальном сервере

У меня есть ScheduledExecutorService, чтобы запланировать выполнение задачи каждые 12 часов в приложении tomcat. Задача вызывает конечную точку REST, выполняет некоторые другие вычисления и возвращает результат. Он отлично работает локально и на тестовом сервере, однако он запускается только один раз и никогда не запускается снова на реальном сервере. Все исключения обрабатываются, и выполнение задачи занимает менее 5 секунд. Я также проверил все свойства на сервере, чтобы убедиться, что свойства времени не переопределяются другими свойствами. Чтобы протестировать локально, я сократил время задержки до 10 минут, и это по-прежнему показывает то же поведение, поэтому я исключил тайм-аут как проблему. Я просмотрел другие похожие вопросы, но, похоже, ни один из них не помог с этой проблемой. ScheduledExecutorService запускается только один раз, JAVA ScheduledExecutorService запускается только один раз при вызове задачи ‹V›, ScheduledExecutorService выполняет цикл только один раз, ScheduledExecutorService - Задача перестает выполняться, ScheduledExecutorService - игнорировать уже запущенный runnable, Проблема с расписанием ScheduledExecutorService, Таймер против планирования ScheduledExecutorService

Ниже мой код:

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonPropertyOrder({"name", "number"})
public class Company {

    @JsonProperty("name")
    private String name;
    @JsonProperty("number")
    private int number;

    public String getName() {
        return this.name;
    }

    public int getNumber() {
        return this.number;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(Company.class)
            .append("name", this.name)
            .append("number", this.number).toString();
    }
}

public class CompanyDifference {

    private String name;
    private int difference;

    public CompanyDifference(String name, int difference) {
        this.name = name;
        this.difference = difference;
    }

    public String getName() {
        return this.name;
    }

    public int getDifference() {
        return this.difference;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(CompanyDifference.class)
            .append("name", this.name)
            .append("difference", this.difference).toString();
    }
}

@Singleton
public class TaskRunner {
    public void doTask () {
        try {
            System.out.println("takes 2 sets of json data and returns the difference for each company");

            // takes 2 sets of json data and returns the difference for each company
            ObjectMapper mapper = new ObjectMapper();
            InputStream dataOne = Company.class.getResourceAsStream("/data.json");
            InputStream dataTwo = Company.class.getResourceAsStream("/data2.json");

            Company[] companyDataOne = mapper.readValue(dataOne, Company[].class);
            Company[] companyDataTwo = mapper.readValue(dataTwo, Company[].class);

            // Find the difference for each company and map company name to difference
            Map<String, Integer> mapDifferenceToCompany = new HashMap<>();

            for (int i = 0; i < companyDataOne.length; i++) {
                mapDifferenceToCompany.put(companyDataOne[i].getName(), Math.abs(companyDataOne[i].getNumber() - companyDataTwo[i].getNumber()));
            }

            mapDifferenceToCompany.forEach((key, value) -> System.out.println(String.valueOf(new CompanyDifference(key, value))));
        } catch (IOException e) {
            logger.info(String.format("Error: Failed to convert json to object with exception %s", e));
            throw new TaskSchedulerException("Failed to convert json to object with exception", e);
        } catch (Exception e) {
            logger.info(String.format("Error: Failed with exception %s", e));
            throw new TaskSchedulerException("Failed with exception", e);
        }
    }
}

@Singleton
public class TaskScheduler {

    private final Runnable runnable; 
    private final ScheduledExecutorService executorService;
    private static final Logger logger = LoggerFactory.getLogger(TaskScheduler.class);

    @Inject
    public TaskScheduler(TaskRunner taskRunner, int initialDelay, int period, String timeUnits) {
        this.executorService = Executors.newScheduledThreadPool(1);
        this.runnable = taskRunner::doTask;

        this.scheduledFuture = this.executorService.scheduleAtFixedRate(this.runnable, initialDelay, period,    
             TimeUnit.valueOf(timeUnits));
    }

    public static void main(String[] args) {
        TaskRunner taskRunner = new TaskRunner();
        new TaskScheduler(taskRunner, 1, 10, "MINUTES");
    }
}

Задача выполняется один раз при запуске после начальной задержки в 1 минуту, но не запускает следующую запланированную задачу. Он пытается запустить запланированную задачу, но, похоже, не выполняет задачу, и после изучения свойств ScheduledThreadPoolExecutor на живом сервере я обнаружил, что размер очереди падает до 0 (когда он всегда должен быть 1), что означает, что задача не была Запланированное.

Это говорит о том, что когда он пытается запустить запланированную задачу после завершения начальной задачи, он либо удаляет запланированную задачу, либо не может запланировать следующую задачу, потому что текущая задача не завершена. Никаких ошибок или исключений не возникает, поскольку все исключения были обработаны в методе doTask. Планировщик работает должным образом локально и на тестовом сервере, что затрудняет репликацию сценария.

Хотите узнать, не упущено ли что-то в реализации или что может быть причиной того, что она не завершит следующую запланированную задачу, и что приводит к уменьшению размера очереди до 0. Может ли версия java каким-либо образом повлиять на поведение планировщика или есть ли какие-либо причины, по которым это может происходить в живой среде?

Я создал конечную точку REST для отслеживания того, что происходит под капотом, с помощью свойств из ScheduledFuture и ScheduledThreadPoolExecutor.

ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) schedulerService;
this.queueSize = executor.getQueue().size();
this.remainingCapacity = executor.getQueue().remainingCapacity();
this.terminated = schedulerService.isTerminated();
this.shutdown = schedulerService.isShutdown();
this.taskCount = executor.getTaskCount();
this.activeTaskCount = executor.getActiveCount();
this.completedTaskCount = executor.getCompletedTaskCount();
this.keepAliveTime = executor.getKeepAliveTime(TimeUnit.SECONDS);
this.coreThreadTimeOut = executor.allowsCoreThreadTimeOut();
this.cancelled = scheduledFuture.isCancelled();
this.delay = scheduledFuture.getDelay(TimeUnit.MINUTES);

Результат первого запуска после начальной задержки: из этого мы видим, что он выполнил первую задачу, и я действительно получаю требуемый результат расчетов. TaskCount (количество запланированных задач с момента запуска) равно 2, что означает, что вторая задача была запланирована, а размер очереди по-прежнему равен 1, что нормально.

{
  "queueSize": 1,
  "remainingCapacity": 2147483647,
  "terminated": false,
  "shutdown": false,
  "taskCount": 2,
  "activeTaskCount": 0,
  "completedTaskCount": 1,
  "keepAliveTime": 0,
  "coreThreadTimeOut": false, 
  "periodic": true, 
  "cancelled": false
}

Результат после попытки второго запуска: вот где он застревает. CompleteTaskCount равно 2, но я не думаю, что он на самом деле завершает задачу, поскольку я не получаю результата расчетов или каких-либо журналов, чтобы показать, что он либо начал, либо выполнил задачу. Параметр taskCount должен увеличиться до 3, но он застрял в 2, а размер очереди теперь равен 0.

{
  "queueSize": 0,
  "remainingCapacity": 2147483647,
  "terminated": false,
  "shutdown": false,
  "taskCount": 2,
  "activeTaskCount": 0,
  "completedTaskCount": 2,
  "keepAliveTime": 0,
  "coreThreadTimeOut": false, 
  "periodic": true, 
  "cancelled": false
}

Когда я проверяю их на локальном и тестовом сервере, он работает нормально, taskCount увеличивается, как и ожидалось, а размер очереди всегда равен 1, что и ожидалось. Исходя из этого, я могу сказать, что по какой-то причине задача застревает при втором запуске и не завершается, поэтому следующая задача не запланирована.

В javadoc говорится: «Если выполнение этой задачи занимает больше времени, чем ее период, то последующие выполнения могут начаться с опозданием, но не будут выполняться одновременно». Я предполагаю, что поэтому следующая задача не выполняется. Запланированное. Было бы здорово, если бы вы могли объяснить, что может вызвать это.


person Sam Aig    schedule 21.11.2018    source источник
comment
Не могли бы вы рассказать, что на самом деле делает ваш метод doTask?   -  person Amit Bera    schedule 21.11.2018
comment
@Amit Bera: Я включил пример того, что делает метод doTask.   -  person Sam Aig    schedule 22.11.2018
comment
Насколько я могу судить, вы не закрываете потоки. Неужели это так? Блокировка потока, которая зависает, поэтому вторая итерация не будет запущена, поскольку поток все еще открыт и заблокирован?   -  person Frankie    schedule 27.11.2018
comment
Он по-прежнему демонстрирует то же поведение с закрытыми потоками.   -  person Sam Aig    schedule 27.11.2018


Ответы (1)


Проблема была не в реализации, реализация в порядке. После долгой отладки в виртуальной машине, на которой запущено приложение, возникла ошибка. После многократного перезапуска и повторного развертывания службы приложения все возвращается в нормальное состояние. См. Вопрос о шагах, предпринятых при отладке приложения.

person Sam Aig    schedule 06.12.2018