Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2df0b46
Fix NPE in ConfigurationScheduler by ensuring scheduledFuture is chec…
ramanathan1504 May 7, 2026
50690c6
Restrict JUnit dependencies to below version 6.x to maintain compatib…
ramanathan1504 May 10, 2026
bc3e896
Don't override `apache-rat-plugin` version (#4123)
ppkarwasz May 15, 2026
3be9e4c
Bump actions/stale from 9.1.0 to 10.2.0 in the dependencies group acr…
dependabot[bot] May 18, 2026
2582986
Fix KafkaAppender reporting error after successful retry (#4125)
SebTardif May 20, 2026
0edc9c2
Revamped `ListAppender` for thread-safety (#4111)
ramanathan1504 May 21, 2026
ed78b98
Improve `CronExpression` tests to cover daylight saving and schedulin…
ramanathan1504 May 21, 2026
ba36fe6
Fix `createOnDemand` for Rolling File Appender (#4072)
ramanathan1504 May 21, 2026
e8c23fe
Add support for max compression delay in compression actions (#4071)
ramanathan1504 May 28, 2026
7fe84f9
Improve logging for `LinkageError` scenarios involving the LMAX Disru…
ramanathan1504 May 29, 2026
ba691e0
Fix changelog issue and PR references for Disruptor initialization er…
ramanathan1504 Jun 1, 2026
b02c79d
Fix encoding of `MSGID` and `SD-ID` fields of `StructuredDataMessage`…
vy Jun 5, 2026
fe38807
Fix stack trace rendering for exceptions with identity malfunction (#…
ramanathan1504 Jun 11, 2026
f298ff1
Remove the `patternFlags` attribute from `RegexFilter` (#4119)
ramanathan1504 Jun 11, 2026
3784f08
Fix `DatePatternConverter` locale parsing when timezone is omitted (#…
ramanathan1504 Jun 11, 2026
2ca444b
Harden `readObject(ObjectInputStream)` method argument checks (#4098)
SunWeb3Sec Jun 12, 2026
fcb6e4e
Fix resource leaks in `ConfigurationSource` when loading via URL (#4127)
SebTardif Jun 15, 2026
4f79ad0
Adopt the Dependabot changelog 'draft trick' (#4148)
ppkarwasz Jun 18, 2026
34bc8cc
Add Dependabot config for the 2.26.x maintenance branch (#4149)
ppkarwasz Jun 25, 2026
c7103d5
Fix handling of non-finite numbers while encoding `MapMessage` to JSO…
vy Jun 29, 2026
70d3313
Remove `/log4j-slf4j-impl` Dependabot execution (#4151)
ppkarwasz Jun 29, 2026
16ce65b
Upgrade Maven and Maven Wrapper versions to `3.9.16` and `3.3.4`, res…
vy Jul 1, 2026
c8efe4b
Adds `maxRandomDelay` configure to Rolling Appenders (#4165)
vy Jul 1, 2026
ab98fd5
Merge `2.26.1` to `2.x` (#4169)
ramanathan1504 Jul 2, 2026
9aaf074
Add `2.25.5` release notes (#4174)
ramanathan1504 Jul 6, 2026
1f9a514
Add native tracing fields to LogEvent to eliminate async MDC overhead…
ramanathan1504 Jul 6, 2026
31b0e2d
Fix NPE in ConfigurationScheduler by ensuring proper reset of schedul…
ramanathan1504 Jul 9, 2026
fb8abe6
Fix NPE in ConfigurationScheduler for frequent cron schedules
ramanathan1504 Jul 9, 2026
6d93d44
Merge branch '2.x' into Fixes-NPE-in-ConfigurationScheduler
ramanathan1504 Jul 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ramanathan1504, test passes without your changes. Could you make sure this test fails without the fix?

@ramanathan1504 ramanathan1504 May 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vy , I restored the file log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java. After doing so, the build succeeded—the test case passes perfectly without requiring any modifications to the source file.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test still passes without the fix, so it does not prove the race is fixed. Also, the current placeholder approach can still leave the returned CronScheduledFuture pointing at the already-fired first future if the task runs before scheduleWithCron() calls reset().

Please make the returned future update monotonic. If the first execution already advanced it to the next scheduled run, the initial reset() must not overwrite it with the stale first future.

Could implement the following changes, please?

  • Add a deterministic regression test that fails on current 2.x
  • Verify cancellation/stop still cancels the next scheduled cron execution;
  • Stop the scheduler in a finally block in the test;
  • Add a changelog entry

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.logging.log4j.core.util.CronExpression;
import org.junit.jupiter.api.Test;

class ConfigurationSchedulerTest {

@Test
void testScheduleWithCronRaceCondition() throws Exception {
final CountDownLatch firstRunLatch = new CountDownLatch(1);
final CountDownLatch secondRunLatch = new CountDownLatch(1);

final Runnable command = () -> {
if (firstRunLatch.getCount() > 0) {
firstRunLatch.countDown();
} else {
secondRunLatch.countDown();
}
};

final ConfigurationScheduler scheduler = new ConfigurationScheduler() {
private final AtomicBoolean first = new AtomicBoolean(true);

@Override
public ScheduledFuture<?> schedule(final Runnable cmd, final long delay, final TimeUnit unit) {
if (first.compareAndSet(true, false)) {
final ScheduledFuture<?> future = super.schedule(cmd, 0, TimeUnit.MILLISECONDS);
try {
assertTrue(firstRunLatch.await(5, TimeUnit.SECONDS), "First run failed, likely NPE");

Thread.sleep(100);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
return future;
}
return super.schedule(cmd, delay, unit);
}
};

try {
scheduler.incrementScheduledItems();
scheduler.start();

final CronExpression cron = new CronExpression("0/1 * * * * ?");

final ScheduledFuture<?> future = scheduler.scheduleWithCron(cron, new Date(), command);

future.cancel(false);

assertFalse(
secondRunLatch.await(2, TimeUnit.SECONDS),
"Task ran after cancellation. Monotonic reset is broken.");

} finally {
scheduler.stop(1, TimeUnit.SECONDS);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,20 @@ 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 CronRunnable runnable = new CronRunnable(command, cronExpression);
final ScheduledFuture<?> future = schedule(runnable, nextFireInterval(fireDate), TimeUnit.MILLISECONDS);
final CronScheduledFuture<?> cronScheduledFuture = new CronScheduledFuture<>(future, fireDate);
final CronScheduledFuture<?> cronScheduledFuture = new CronScheduledFuture<>(null, fireDate);
runnable.setScheduledFuture(cronScheduledFuture);

final ScheduledFuture<?> future = schedule(runnable, nextFireInterval(fireDate), TimeUnit.MILLISECONDS);

synchronized (cronScheduledFuture) {
Date currentFireTime = cronScheduledFuture.getFireTime();
if (currentFireTime == null || !currentFireTime.after(fireDate)) {
cronScheduledFuture.reset(future, fireDate);
}
}

LOGGER.debug(
"{} scheduled cron expression {} to fire at {}", name, cronExpression.getCronExpression(), fireDate);
return cronScheduledFuture;
Expand Down Expand Up @@ -231,7 +241,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 {
Expand All @@ -251,13 +262,24 @@ public void run() {
name,
cronExpression.getCronExpression(),
fireDate);
scheduledFuture.reset(future, fireDate);

if (scheduledFuture != null) {
synchronized (scheduledFuture) {
Date currentFireTime = scheduledFuture.getFireTime();
if (currentFireTime == null || currentFireTime.before(fireDate)) {
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 + "}";
}
}

Expand Down
13 changes: 13 additions & 0 deletions src/changelog/.2.x.x/2073_fix_ConfigurationScheduler_NPE.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="https://logging.apache.org/xml/ns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
https://logging.apache.org/xml/ns
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
type="fixed">
<issue id="2073" link="https://github.com/apache/logging-log4j2/issues/2073"/>
<issue id="4118" link="https://github.com/apache/logging-log4j2/pull/4118"/>
<description format="asciidoc">
Fix `NullPointerException` in `ConfigurationScheduler` for a frequently executing cron schedule.
</description>
</entry>
Loading