This is an automated email from the ASF dual-hosted git repository.

ramanathan1504 pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/2.x by this push:
     new de17f604c2 Fix NPE in ConfigurationScheduler by ensuring 
scheduledFuture is chec… (#4118)
de17f604c2 is described below

commit de17f604c2ef46add24b0180746f6ef0a300e94e
Author: Ramanathan <[email protected]>
AuthorDate: Fri Jul 24 15:58:59 2026 +0530

    Fix NPE in ConfigurationScheduler by ensuring scheduledFuture is chec… 
(#4118)
    
    * Fix NPE in ConfigurationScheduler by ensuring scheduledFuture is checked 
for null before accessing its methods; add test for race condition in 
scheduling.
    
    * Restrict JUnit dependencies to below version 6.x to maintain compatib… 
(#4120)
    
    * Restrict JUnit dependencies to below version 6.x to maintain 
compatibility with Java 8
    
    * Update JUnit dependency restrictions for compatibility with Java 8 on 2.x
    
    * Don't override `apache-rat-plugin` version (#4123)
    
    Remove the version override for `apache-rat-plugin` so it falls back to the 
version provided by the ASF Parent POM.
    
    Apache RAT `0.18`, for which Dependabot opened an update PR, contains a bug 
([RAT-552](https://issues.apache.org/jira/browse/RAT-552)) that effectively 
disables the `excludeSubProjects` plugin option whenever `<excludes>` is 
present in the configuration.
    
    Upgrading to `0.18` will require converting our `<excludes>` to 
`<inputExcludes>`, which depends on a new `logging-parent` release.
    
    * Bump actions/stale from 9.1.0 to 10.2.0 in the dependencies group across 
1 directory (#4121)
    
    * Bump actions/stale in the dependencies group across 1 directory
    
    Bumps the dependencies group with 1 update in the / directory: 
[actions/stale](https://github.com/actions/stale).
    
    
    Updates `actions/stale` from 9.1.0 to 10.2.0
    - [Release notes](https://github.com/actions/stale/releases)
    - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
    - 
[Commits](https://github.com/actions/stale/compare/5bef64f19d7facfb25b37b414482c7164d639639...b5d41d4e1d5dceea10e7104786b73624c18a190f)
    
    ---
    updated-dependencies:
    - dependency-name: actions/stale
      dependency-version: 10.2.0
      dependency-type: direct:production
      update-type: version-update:semver-major
      dependency-group: dependencies
    ...
    
    Signed-off-by: dependabot[bot] <[email protected]>
    
    * Generate changelog entries for #4121
    
    ---------
    
    Signed-off-by: dependabot[bot] <[email protected]>
    Co-authored-by: dependabot[bot] 
<49699333+dependabot[bot]@users.noreply.github.com>
    Co-authored-by: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
    
    * Fix KafkaAppender reporting error after successful retry (#4125)
    
    * Fix KafkaAppender reporting error after successful retry
    
    When retryCount is configured and the initial tryAppend() fails, the
    retry loop uses break to exit on success. However, break only exits the
    while loop and execution always reaches the error() call afterward. This
    causes spurious error notifications for transient Kafka failures that
    were successfully recovered by a retry.
    
    Replace break with return so that a successful retry exits append()
    without reporting an error. Retry exceptions are now logged at DEBUG
    level for diagnostics instead of being silently discarded.
    
    Also remove dead code in Builder.getRetryCount() where Integer.valueOf(int)
    was wrapped in a NumberFormatException catch that can never fire.
    
    The bug was introduced in #315.
    
    Signed-off-by: Sebastien Tardif <[email protected]>
    
    * Add changelog entry for KafkaAppender retry fix
    
    Signed-off-by: Sebastien Tardif <[email protected]>
    
    * Use CloseableThreadContext in testRetrySuccessDoesNotReportError
    
    Adopt reviewer suggestion to use try-with-resources CloseableThreadContext
    instead of manual ThreadContext.put/clearMap.
    
    ---------
    
    Signed-off-by: Sebastien Tardif <[email protected]>
    
    * Revamped `ListAppender` for thread-safety (#4111)
    
    Co-authored-by: Björn Michael <[email protected]>
    Co-authored-by: Volkan Yazıcı <[email protected]>
    
    * Improve `CronExpression` tests to cover daylight saving and scheduling 
logic (#4081)
    
    Co-authored-by: Piotr P. Karwasz <[email protected]>
    Co-authored-by: Volkan Yazıcı <[email protected]>
    
    * Fix `createOnDemand` for Rolling File Appender (#4072)
    
    Co-authored-by: Piotr P. Karwasz <[email protected]>
    Co-authored-by: Volkan Yazıcı <[email protected]>
    
    * Add support for max compression delay in compression actions (#4071)
    
    Co-authored-by: Volkan Yazıcı <[email protected]>
    
    * Improve logging for `LinkageError` scenarios involving the LMAX Disruptor 
library (#4124)
    
    Co-authored-by: Volkan Yazıcı <[email protected]>
    
    * Fix changelog issue and PR references for Disruptor initialization error 
logging (#2250, #4124, #4134)
    
    * Fix encoding of `MSGID` and `SD-ID` fields of `StructuredDataMessage` to 
XML (#4136)
    
    * Fix stack trace rendering for exceptions with identity malfunction (#4133)
    
    * Fix circular reference detection for exceptions with colliding 
equals/hashCode implementations
    
    * Add changelog entry for circular reference detection fix with colliding 
equals/hashCode exceptions
    
    * Add changelog entry for circular reference detection fix with colliding 
equals/hashCode exceptions
    
    * Add test for ThrowableProxy serialization with colliding equals/hashCode 
implementations
    
    * Refactor ThrowableProxy serialization test to use modern Java I/O classes
    
    * Use `TestFriendlyException` to exercise the malfunction
    
    * Update changelog
    
    * Improve comments
    
    * Remove redundant change
    
    ---------
    
    Co-authored-by: Volkan Yazıcı <[email protected]>
    
    * Remove the `patternFlags` attribute from `RegexFilter` (#4119)
    
    Co-authored-by: Jeff Thomas <[email protected]>
    Co-authored-by: Volkan Yazıcı <[email protected]>
    
    * Fix `DatePatternConverter` locale parsing when timezone is omitted (#4130)
    
    * Harden `readObject(ObjectInputStream)` method argument checks (#4098)
    
    Signed-off-by: SunWeb3Sec <[email protected]>
    Co-authored-by: Volkan Yazıcı <[email protected]>
    
    * Fix resource leaks in `ConfigurationSource` when loading via URL (#4127)
    
    Signed-off-by: Sebastien Tardif <[email protected]>
    Co-authored-by: Ramanathan <[email protected]>
    Co-authored-by: Piotr P. Karwasz <[email protected]>
    Co-authored-by: Volkan Yazıcı <[email protected]>
    
    * Adopt the Dependabot changelog 'draft trick' (#4148)
    
    Applies the consumer-side changes from logging-parent PR #476:
    
    * `build.yaml` and `codeql-analysis.yaml` now subscribe to the
      `ready_for_review` pull request type, so required checks re-run when a
      Dependabot PR is taken out of draft.
    * `process-dependabot.yaml` drops the `RECURSIVE_TOKEN` PAT secret, which
      is no longer needed now that the reusable workflow parks the PR in
      draft mode instead of pushing with a privileged token.
    
    Assisted-By: Claude Opus 4.8 (1M context) <[email protected]>
    
    * Add Dependabot config for the 2.26.x maintenance branch (#4149)
    
    Configure a Maven update entry targeting the `2.26.x` branch that only
    proposes patch-level upgrades. All dependencies are bundled into a single
    `Maven patch updates` group with a 7-day cooldown.
    
    * Fix handling of non-finite numbers while encoding `MapMessage` to JSON 
(#4163)
    
    * Remove `/log4j-slf4j-impl` Dependabot execution (#4151)
    
    Instead of correcting the target branch, we can safely
    remove it: the only dependency version defined in
    `log4j-slf4j-impl` is SLF4J 1.x, which is highly unlikely
    to see any new releases.
    
    * Upgrade Maven and Maven Wrapper versions to `3.9.16` and `3.3.4`, 
respectively (#4167)
    
    * Adds `maxRandomDelay` configure to Rolling Appenders (#4165)
    
    * Rework random delay for Rolling Appender action chain
    
    * Stabilize `RollingAppenderDirectCronTest`
    
    * Merge `2.26.1` to `2.x` (#4169)
    
    * Add `2.25.5` release notes (#4174)
    
    * Add native tracing fields to LogEvent to eliminate async MDC overhead 
(#4171)
    
    * Add tracing fields to RingBufferLogEvent and related classes
    
    * Add W3C trace context support with pattern converters for trace ID, span 
ID, and trace flags
    
    * Enhance TraceContextProviderService for exception safety and simplify 
trace ID retrieval
    
    * Add tests for tracing fields serialization and pattern converters
    
    * Refactor tracing metadata documentation and improve code comments for 
clarity
    
    * Add native W3C tracing fields to LogEvent and introduce 
TraceContextProvider SPI
    
    * Add benchmark for ContextDataProvider tracing approach
    
    * Enhance TraceContextProviderService to handle SecurityManager 
restrictions gracefully
    
    * Fix NPE in ConfigurationScheduler by ensuring proper reset of scheduled 
futures
    
    * Fix NPE in ConfigurationScheduler for frequent cron schedules
    
    ---------
    
    Signed-off-by: dependabot[bot] <[email protected]>
    Signed-off-by: Sebastien Tardif <[email protected]>
    Signed-off-by: SunWeb3Sec <[email protected]>
    Co-authored-by: Piotr P. Karwasz <[email protected]>
    Co-authored-by: dependabot[bot] 
<49699333+dependabot[bot]@users.noreply.github.com>
    Co-authored-by: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
    Co-authored-by: Sebastien Tardif <[email protected]>
    Co-authored-by: Björn Michael <[email protected]>
    Co-authored-by: Volkan Yazıcı <[email protected]>
    Co-authored-by: Volkan Yazıcı <[email protected]>
    Co-authored-by: Jeff Thomas <[email protected]>
    Co-authored-by: SunWeb3Sec <[email protected]>
---
 .../core/config/ConfigurationSchedulerTest.java    | 83 ++++++++++++++++++++++
 .../log4j/core/config/ConfigurationScheduler.java  | 32 +++++++--
 .../.2.x.x/2073_fix_ConfigurationScheduler_NPE.xml | 13 ++++
 3 files changed, 123 insertions(+), 5 deletions(-)

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 0000000000..06d85ec9ac
--- /dev/null
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSchedulerTest.java
@@ -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);
+        }
+    }
+}
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 c71911990a..26f0b7a6db 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,10 +147,20 @@ public class ConfigurationScheduler extends 
AbstractLifeCycle {
     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;
@@ -231,7 +241,8 @@ public class ConfigurationScheduler extends 
AbstractLifeCycle {
         @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 +262,24 @@ public class ConfigurationScheduler extends 
AbstractLifeCycle {
                         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 + "}";
         }
     }
 
diff --git a/src/changelog/.2.x.x/2073_fix_ConfigurationScheduler_NPE.xml 
b/src/changelog/.2.x.x/2073_fix_ConfigurationScheduler_NPE.xml
new file mode 100644
index 0000000000..ead17f8cfa
--- /dev/null
+++ b/src/changelog/.2.x.x/2073_fix_ConfigurationScheduler_NPE.xml
@@ -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>
\ No newline at end of file

Reply via email to