diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSchedulerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSchedulerTest.java new file mode 100644 index 00000000000..1ae50672e9e --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSchedulerTest.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.core.config; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Date; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.core.util.CronExpression; +import org.junit.jupiter.api.Test; + +class ConfigurationSchedulerTest { + @Test + void testScheduleWithCronRaceCondition() throws Exception { + ConfigurationScheduler scheduler = new ConfigurationScheduler(); + scheduler.incrementScheduledItems(); + scheduler.start(); + CronExpression cron = new CronExpression("* * * * * ?"); + CountDownLatch latch = new CountDownLatch(1); + Runnable task = latch::countDown; + scheduler.scheduleWithCron(cron, new Date(), task); + assertTrue(latch.await(2, TimeUnit.SECONDS), "Task did not run in time"); + scheduler.stop(1, TimeUnit.SECONDS); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java index c71911990a9..5e765ff8fb8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java @@ -147,13 +147,15 @@ public CronScheduledFuture scheduleWithCron(final CronExpression cronExpressi public CronScheduledFuture scheduleWithCron( final CronExpression cronExpression, final Date startDate, final Runnable command) { final Date fireDate = cronExpression.getNextValidTimeAfter(startDate == null ? new Date() : startDate); + final CronScheduledFuture[] placeholder = new CronScheduledFuture[1]; final CronRunnable runnable = new CronRunnable(command, cronExpression); + placeholder[0] = new CronScheduledFuture<>(null, fireDate); + runnable.setScheduledFuture(placeholder[0]); final ScheduledFuture future = schedule(runnable, nextFireInterval(fireDate), TimeUnit.MILLISECONDS); - final CronScheduledFuture cronScheduledFuture = new CronScheduledFuture<>(future, fireDate); - runnable.setScheduledFuture(cronScheduledFuture); + placeholder[0].reset(future, fireDate); LOGGER.debug( "{} scheduled cron expression {} to fire at {}", name, cronExpression.getCronExpression(), fireDate); - return cronScheduledFuture; + return placeholder[0]; } /** @@ -231,7 +233,8 @@ public void setScheduledFuture(final CronScheduledFuture future) { @Override public void run() { try { - final long millis = scheduledFuture.getFireTime().getTime() - System.currentTimeMillis(); + Date fireTime = (scheduledFuture != null) ? scheduledFuture.getFireTime() : null; + long millis = (fireTime != null) ? (fireTime.getTime() - System.currentTimeMillis()) : 0L; if (millis > 0) { LOGGER.debug("{} Cron thread woke up {} millis early. Sleeping", name, millis); try { @@ -251,13 +254,18 @@ public void run() { name, cronExpression.getCronExpression(), fireDate); - scheduledFuture.reset(future, fireDate); + if (scheduledFuture != null) { + scheduledFuture.reset(future, fireDate); + } } } @Override public String toString() { - return "CronRunnable{" + cronExpression.getCronExpression() + " - " + scheduledFuture.getFireTime(); + String fireTimeStr = (scheduledFuture != null && scheduledFuture.getFireTime() != null) + ? scheduledFuture.getFireTime().toString() + : "unassigned"; + return "CronRunnable{" + cronExpression.getCronExpression() + " - " + fireTimeStr + "}"; } }