From e20198ff94efbce22ea0a26e7710e7e99fc648ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Michael?= Date: Tue, 9 Sep 2025 14:52:47 +0200 Subject: [PATCH 1/7] ListAppender: Synchronize on list while iterating * Returned snapshots no longer require an unmodifiable view --- .../log4j/core/test/appender/ListAppender.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java index f941db12674..f36d4082cb8 100644 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java @@ -195,14 +195,18 @@ public ListAppender clear() { return this; } - /** Returns an immutable snapshot of captured log events */ + /** Returns a snapshot of captured log events */ public List getEvents() { - return Collections.unmodifiableList(new ArrayList<>(events)); + synchronized (events) { + return new ArrayList<>(events); + } } - /** Returns an immutable snapshot of captured messages */ + /** Returns a snapshot of captured messages */ public List getMessages() { - return Collections.unmodifiableList(new ArrayList<>(messages)); + synchronized (messages) { + return new ArrayList<>(messages); + } } /** @@ -215,9 +219,11 @@ public List getMessages(final int minSize, final long timeout, final Tim return getMessages(); } - /** Returns an immutable snapshot of captured data */ + /** Returns a snapshot of captured data */ public List getData() { - return Collections.unmodifiableList(new ArrayList<>(data)); + synchronized (data) { + return new ArrayList<>(data); + } } public static ListAppender createAppender( From dd8fdedbc3428cb7f7d3e6cb33f4e348b174c1cf Mon Sep 17 00:00:00 2001 From: Ramanathan Date: Thu, 30 Apr 2026 22:55:06 +0530 Subject: [PATCH 2/7] Revamp ListAppender: full thread-safety, improved JavaDoc, and tests - Remove synchronizedList wrappers; replace with plain ArrayLists - Annotate all public methods as synchronized on `this` - Replace Awaitility polling in getMessages(int,long,TimeUnit) with wait/notifyAll - Update class-level JavaDoc to reflect thread-safety guarantee - Rewrite countDownLatch field JavaDoc with a self-contained example - Remove redundant explicit generic type arguments (IDE cleanup) - Add ListAppenderTest: unit tests + @RepeatedTest(10) concurrency tests that hammer append with 10 workers x 1000 deterministic events each and verify getEvents/getMessages are consistent Fixes #3926 --- .../core/test/appender/ListAppender.java | 98 ++++----- .../core/test/appender/ListAppenderTest.java | 198 ++++++++++++++++++ .../.2.x.x/3926_revamp_list_appender.xml | 16 ++ 3 files changed, 259 insertions(+), 53 deletions(-) create mode 100644 log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/appender/ListAppenderTest.java create mode 100644 src/changelog/.2.x.x/3926_revamp_list_appender.xml diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java index f36d4082cb8..3de9a571955 100644 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java @@ -36,28 +36,27 @@ import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; import org.apache.logging.log4j.core.layout.SerializedLayout; -import org.awaitility.Awaitility; /** * This appender is primarily used for testing. Use in a real environment is discouraged as the - * List could eventually grow to cause an OutOfMemoryError. + * lists could eventually grow to cause an {@link OutOfMemoryError}. * - * This appender is not thread-safe. + *

This appender is thread-safe: all public methods are {@code synchronized} on {@code this}. + * Callers waiting for a minimum number of messages can use + * {@link #getMessages(int, long, TimeUnit)} which releases the monitor while waiting.

* - * This appender will use {@link Layout#toByteArray(LogEvent)}. + *

This appender will use {@link Layout#toByteArray(LogEvent)}.

* * @see org.apache.logging.log4j.core.test.junit.LoggerContextRule#getListAppender(String) ILC.getListAppender */ @Plugin(name = "List", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true) public class ListAppender extends AbstractAppender { - // Use Collections.synchronizedList rather than CopyOnWriteArrayList because we expect - // more frequent writes than reads. - final List events = Collections.synchronizedList(new ArrayList()); + final List events = new ArrayList<>(); - private final List messages = Collections.synchronizedList(new ArrayList()); + private final List messages = new ArrayList<>(); - final List data = Collections.synchronizedList(new ArrayList()); + final List data = new ArrayList<>(); private final boolean newLine; @@ -66,30 +65,20 @@ public class ListAppender extends AbstractAppender { private static final String WINDOWS_LINE_SEP = "\r\n"; /** - * CountDownLatch for asynchronous logging tests. Example usage: - *
-     * @Rule
-     * public LoggerContextRule context = new LoggerContextRule("log4j-list.xml");
-     * private ListAppender listAppender;
+     * A {@link CountDownLatch} that is decremented once for every call to {@link #append(LogEvent)}.
      *
-     * @Before
-     * public void before() throws Exception {
-     *     listAppender = context.getListAppender("List");
-     * }
+     * 

Callers may assign a new latch before submitting a known number of log events, + * then await that latch to block until all events have been processed. Example:

+ *
{@code
+     * listAppender.countDownLatch = new CountDownLatch(1);
      *
-     * @Test
-     * public void testSomething() throws Exception {
-     *     listAppender.countDownLatch = new CountDownLatch(1);
+     * logger.info("log one event asynchronously");
      *
-     *     Logger logger = LogManager.getLogger();
-     *     logger.info("log one event asynchronously");
+     * // wait for the appender to finish processing this event (wait max 1 second)
+     * listAppender.countDownLatch.await(1, TimeUnit.SECONDS);
      *
-     *     // wait for the appender to finish processing this event (wait max 1 second)
-     *     listAppender.countDownLatch.await(1, TimeUnit.SECONDS);
-     *
-     *     // now assert something or do follow-up tests...
-     * }
-     * 
+ * // now assert something or do follow-up tests... + * }
*/ public volatile CountDownLatch countDownLatch = null; @@ -117,7 +106,7 @@ public ListAppender( } @Override - public void append(final LogEvent event) { + public synchronized void append(final LogEvent event) { final Layout layout = getLayout(); if (layout == null) { events.add(event.toImmutable()); @@ -131,12 +120,14 @@ public void append(final LogEvent event) { } else { write(layout.toByteArray(event)); } - if (countDownLatch != null) { - countDownLatch.countDown(); + notifyAll(); + final CountDownLatch latch = countDownLatch; + if (latch != null) { + latch.countDown(); } } - void write(final byte[] bytes) { + synchronized void write(final byte[] bytes) { if (raw) { data.add(bytes); return; @@ -174,7 +165,7 @@ void write(final byte[] bytes) { } @Override - public boolean stop(final long timeout, final TimeUnit timeUnit) { + public synchronized boolean stop(final long timeout, final TimeUnit timeUnit) { setStopping(); super.stop(timeout, timeUnit, false); final Layout layout = getLayout(); @@ -188,42 +179,43 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { return true; } - public ListAppender clear() { + public synchronized ListAppender clear() { events.clear(); messages.clear(); data.clear(); return this; } - /** Returns a snapshot of captured log events */ - public List getEvents() { - synchronized (events) { - return new ArrayList<>(events); - } + /** Returns an immutable snapshot of captured log events */ + public synchronized List getEvents() { + return Collections.unmodifiableList(new ArrayList<>(events)); } - /** Returns a snapshot of captured messages */ - public List getMessages() { - synchronized (messages) { - return new ArrayList<>(messages); - } + /** Returns an immutable snapshot of captured messages */ + public synchronized List getMessages() { + return Collections.unmodifiableList(new ArrayList<>(messages)); } /** * Polls the messages list for it to grow to a given minimum size at most timeout timeUnits and return a copy of * what we have so far. */ - public List getMessages(final int minSize, final long timeout, final TimeUnit timeUnit) + public synchronized List getMessages(final int minSize, final long timeout, final TimeUnit timeUnit) throws InterruptedException { - Awaitility.waitAtMost(timeout, timeUnit).until(() -> messages.size() >= minSize); - return getMessages(); + final long deadlineNanos = System.nanoTime() + timeUnit.toNanos(timeout); + while (messages.size() < minSize) { + final long remainingNanos = deadlineNanos - System.nanoTime(); + if (remainingNanos <= 0) { + break; + } + TimeUnit.NANOSECONDS.timedWait(this, remainingNanos); + } + return Collections.unmodifiableList(new ArrayList<>(messages)); } - /** Returns a snapshot of captured data */ - public List getData() { - synchronized (data) { - return new ArrayList<>(data); - } + /** Returns an immutable snapshot of captured data */ + public synchronized List getData() { + return Collections.unmodifiableList(new ArrayList<>(data)); } public static ListAppender createAppender( diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/appender/ListAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/appender/ListAppenderTest.java new file mode 100644 index 00000000000..d69353ea784 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/appender/ListAppenderTest.java @@ -0,0 +1,198 @@ +/* + * 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.test.appender; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.message.SimpleMessage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.RepeatedTest; + +/** + * Tests for {@link ListAppender}. + */ +class ListAppenderTest { + + private static LogEvent createEvent(final int workerId, final int index) { + return Log4jLogEvent.newBuilder() + .setLoggerName("worker-" + workerId) + .setLevel(Level.INFO) + .setMessage(new SimpleMessage("event-" + workerId + "-" + index)) + .build(); + } + + @Test + void appendWithoutLayoutStoresEvents() { + final ListAppender appender = new ListAppender("test"); + appender.start(); + + appender.append(createEvent(0, 0)); + appender.append(createEvent(0, 1)); + + assertThat(appender.getEvents()).hasSize(2); + assertThat(appender.getMessages()).isEmpty(); + } + + @Test + void appendWithLayoutStoresMessages() { + final PatternLayout layout = PatternLayout.newBuilder() + .setPattern("%m") + .build(); + final ListAppender appender = new ListAppender("test", null, layout, false, false); + appender.start(); + + appender.append(createEvent(0, 0)); + appender.append(createEvent(0, 1)); + + assertThat(appender.getMessages()).hasSize(2); + assertThat(appender.getEvents()).isEmpty(); + } + + @Test + void clearResetsAllCollections() { + final ListAppender appender = new ListAppender("test"); + appender.start(); + + appender.append(createEvent(0, 0)); + assertThat(appender.getEvents()).hasSize(1); + + appender.clear(); + + assertThat(appender.getEvents()).isEmpty(); + assertThat(appender.getMessages()).isEmpty(); + assertThat(appender.getData()).isEmpty(); + } + + @Test + void getMessagesWithTimeoutReturnsOnceMinSizeReached() throws InterruptedException { + final PatternLayout layout = PatternLayout.newBuilder() + .setPattern("%m") + .build(); + final ListAppender appender = new ListAppender("test", null, layout, false, false); + appender.start(); + + // Append in a background thread, after a short delay + final Thread producer = new Thread(() -> { + try { + Thread.sleep(50); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + appender.append(createEvent(0, 0)); + }); + producer.start(); + + final List messages = appender.getMessages(1, 5, TimeUnit.SECONDS); + producer.join(); + + assertThat(messages).hasSize(1); + } + + /** + * Hammers {@link ListAppender#append(LogEvent)} concurrently using 10 workers each appending 1,000 deterministic + * events, then verifies that {@link ListAppender#getEvents()} is consistent (no events were lost or duplicated). + */ + @RepeatedTest(10) + void appendIsThreadSafeWithoutLayout() throws InterruptedException { + final int workerCount = 10; + final int eventsPerWorker = 1_000; + + final ListAppender appender = new ListAppender("thread-safe-test"); + appender.start(); + + final ExecutorService executor = Executors.newFixedThreadPool(workerCount); + final CountDownLatch startGate = new CountDownLatch(1); + + for (int w = 0; w < workerCount; w++) { + final int workerId = w; + executor.submit(() -> { + try { + startGate.await(); + for (int i = 0; i < eventsPerWorker; i++) { + appender.append(createEvent(workerId, i)); + } + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + } + + startGate.countDown(); + executor.shutdown(); + assertThat(executor.awaitTermination(30, TimeUnit.SECONDS)) + .as("all workers completed within timeout") + .isTrue(); + + assertThat(appender.getEvents()) + .as("all events were captured without loss or duplication") + .hasSize(workerCount * eventsPerWorker); + } + + /** + * Hammers {@link ListAppender#append(LogEvent)} concurrently using 10 workers each appending 1,000 deterministic + * events with a layout, then verifies that {@link ListAppender#getMessages()} is consistent + * (no messages were lost or duplicated). + */ + @RepeatedTest(10) + void appendIsThreadSafeWithLayout() throws InterruptedException { + final int workerCount = 10; + final int eventsPerWorker = 1_000; + + final PatternLayout layout = PatternLayout.newBuilder() + .setPattern("%m") + .build(); + final ListAppender appender = new ListAppender("thread-safe-layout-test", null, layout, false, false); + appender.start(); + + final ExecutorService executor = Executors.newFixedThreadPool(workerCount); + final CountDownLatch startGate = new CountDownLatch(1); + + for (int w = 0; w < workerCount; w++) { + final int workerId = w; + executor.submit(() -> { + try { + startGate.await(); + for (int i = 0; i < eventsPerWorker; i++) { + appender.append(createEvent(workerId, i)); + } + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + } + + startGate.countDown(); + executor.shutdown(); + assertThat(executor.awaitTermination(30, TimeUnit.SECONDS)) + .as("all workers completed within timeout") + .isTrue(); + + assertThat(appender.getMessages()) + .as("all messages were captured without loss or duplication") + .hasSize(workerCount * eventsPerWorker); + } +} + diff --git a/src/changelog/.2.x.x/3926_revamp_list_appender.xml b/src/changelog/.2.x.x/3926_revamp_list_appender.xml new file mode 100644 index 00000000000..adf624a1a5d --- /dev/null +++ b/src/changelog/.2.x.x/3926_revamp_list_appender.xml @@ -0,0 +1,16 @@ + + + + + Revamped `ListAppender` for thread-safety and clarity: + removed `Collections.synchronizedList` wrappers and replaced with `synchronized` on all public methods, + replaced `Awaitility` polling in `getMessages(int, long, TimeUnit)` with `Object#wait` / `notifyAll`, + improved class and `countDownLatch` Javadoc, + and added unit and concurrency tests. + + From 8255cb7ad61c656e4c809d3bde22a9f9463e55c2 Mon Sep 17 00:00:00 2001 From: Ramanathan Date: Fri, 1 May 2026 21:26:45 +0530 Subject: [PATCH 3/7] Enhance ListAppender for thread-safety add expected message and event key assertions --- .../core/test/appender/ListAppenderTest.java | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/appender/ListAppenderTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/appender/ListAppenderTest.java index d69353ea784..ac6fd5bc7aa 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/appender/ListAppenderTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/test/appender/ListAppenderTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; @@ -28,8 +29,8 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.message.SimpleMessage; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; /** * Tests for {@link ListAppender}. @@ -44,6 +45,26 @@ private static LogEvent createEvent(final int workerId, final int index) { .build(); } + private static List expectedMessages(final int workerCount, final int eventsPerWorker) { + final List expected = new ArrayList<>(workerCount * eventsPerWorker); + for (int workerId = 0; workerId < workerCount; workerId++) { + for (int i = 0; i < eventsPerWorker; i++) { + expected.add("event-" + workerId + "-" + i); + } + } + return expected; + } + + private static List expectedEventKeys(final int workerCount, final int eventsPerWorker) { + final List expected = new ArrayList<>(workerCount * eventsPerWorker); + for (int workerId = 0; workerId < workerCount; workerId++) { + for (int i = 0; i < eventsPerWorker; i++) { + expected.add("worker-" + workerId + ":event-" + workerId + "-" + i); + } + } + return expected; + } + @Test void appendWithoutLayoutStoresEvents() { final ListAppender appender = new ListAppender("test"); @@ -58,9 +79,7 @@ void appendWithoutLayoutStoresEvents() { @Test void appendWithLayoutStoresMessages() { - final PatternLayout layout = PatternLayout.newBuilder() - .setPattern("%m") - .build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%m").build(); final ListAppender appender = new ListAppender("test", null, layout, false, false); appender.start(); @@ -88,9 +107,7 @@ void clearResetsAllCollections() { @Test void getMessagesWithTimeoutReturnsOnceMinSizeReached() throws InterruptedException { - final PatternLayout layout = PatternLayout.newBuilder() - .setPattern("%m") - .build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%m").build(); final ListAppender appender = new ListAppender("test", null, layout, false, false); appender.start(); @@ -119,6 +136,7 @@ void getMessagesWithTimeoutReturnsOnceMinSizeReached() throws InterruptedExcepti void appendIsThreadSafeWithoutLayout() throws InterruptedException { final int workerCount = 10; final int eventsPerWorker = 1_000; + final List expectedEventKeys = expectedEventKeys(workerCount, eventsPerWorker); final ListAppender appender = new ListAppender("thread-safe-test"); appender.start(); @@ -149,6 +167,12 @@ void appendIsThreadSafeWithoutLayout() throws InterruptedException { assertThat(appender.getEvents()) .as("all events were captured without loss or duplication") .hasSize(workerCount * eventsPerWorker); + + assertThat(appender.getEvents()) + .extracting(event -> + event.getLoggerName() + ":" + event.getMessage().getFormattedMessage()) + .as("all expected worker/message combinations are present exactly once") + .containsExactlyInAnyOrderElementsOf(expectedEventKeys); } /** @@ -160,10 +184,9 @@ void appendIsThreadSafeWithoutLayout() throws InterruptedException { void appendIsThreadSafeWithLayout() throws InterruptedException { final int workerCount = 10; final int eventsPerWorker = 1_000; + final List expectedMessages = expectedMessages(workerCount, eventsPerWorker); - final PatternLayout layout = PatternLayout.newBuilder() - .setPattern("%m") - .build(); + final PatternLayout layout = PatternLayout.newBuilder().setPattern("%m").build(); final ListAppender appender = new ListAppender("thread-safe-layout-test", null, layout, false, false); appender.start(); @@ -193,6 +216,9 @@ void appendIsThreadSafeWithLayout() throws InterruptedException { assertThat(appender.getMessages()) .as("all messages were captured without loss or duplication") .hasSize(workerCount * eventsPerWorker); + + assertThat(appender.getMessages()) + .as("all expected messages are present exactly once") + .containsExactlyInAnyOrderElementsOf(expectedMessages); } } - From 5365bcf8db6cbb5c0e1e214293860fdeabb66210 Mon Sep 17 00:00:00 2001 From: Ramanathan Date: Tue, 19 May 2026 19:46:54 +0530 Subject: [PATCH 4/7] Refactor ListAppender to improve thread-safety in message retrieval --- .../log4j/core/test/appender/ListAppender.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java index 3de9a571955..24eef6d996f 100644 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java @@ -36,6 +36,7 @@ import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; import org.apache.logging.log4j.core.layout.SerializedLayout; +import org.awaitility.Awaitility; /** * This appender is primarily used for testing. Use in a real environment is discouraged as the @@ -200,17 +201,14 @@ public synchronized List getMessages() { * Polls the messages list for it to grow to a given minimum size at most timeout timeUnits and return a copy of * what we have so far. */ - public synchronized List getMessages(final int minSize, final long timeout, final TimeUnit timeUnit) + public List getMessages(final int minSize, final long timeout, final TimeUnit timeUnit) throws InterruptedException { - final long deadlineNanos = System.nanoTime() + timeUnit.toNanos(timeout); - while (messages.size() < minSize) { - final long remainingNanos = deadlineNanos - System.nanoTime(); - if (remainingNanos <= 0) { - break; + Awaitility.waitAtMost(timeout, timeUnit).until(() -> { + synchronized (this) { + return messages.size() >= minSize; } - TimeUnit.NANOSECONDS.timedWait(this, remainingNanos); - } - return Collections.unmodifiableList(new ArrayList<>(messages)); + }); + return getMessages(); } /** Returns an immutable snapshot of captured data */ From db59737b1a2f534a0acdb40d34572ab9d597cc59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Thu, 21 May 2026 12:56:33 +0200 Subject: [PATCH 5/7] Improve changelog --- src/changelog/.2.x.x/3926_revamp_list_appender.xml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/changelog/.2.x.x/3926_revamp_list_appender.xml b/src/changelog/.2.x.x/3926_revamp_list_appender.xml index adf624a1a5d..6a0879546c5 100644 --- a/src/changelog/.2.x.x/3926_revamp_list_appender.xml +++ b/src/changelog/.2.x.x/3926_revamp_list_appender.xml @@ -7,10 +7,6 @@ type="changed"> - Revamped `ListAppender` for thread-safety and clarity: - removed `Collections.synchronizedList` wrappers and replaced with `synchronized` on all public methods, - replaced `Awaitility` polling in `getMessages(int, long, TimeUnit)` with `Object#wait` / `notifyAll`, - improved class and `countDownLatch` Javadoc, - and added unit and concurrency tests. + Revamp `ListAppender` for thread-safety and clarity. From 71a3b0ba15ea3876da3c639a2cc20a95bca598b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Thu, 21 May 2026 12:59:26 +0200 Subject: [PATCH 6/7] Remove `notifyAll` --- .../apache/logging/log4j/core/test/appender/ListAppender.java | 1 - 1 file changed, 1 deletion(-) diff --git a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java index 24eef6d996f..7d3aa438693 100644 --- a/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java +++ b/log4j-core-test/src/main/java/org/apache/logging/log4j/core/test/appender/ListAppender.java @@ -121,7 +121,6 @@ public synchronized void append(final LogEvent event) { } else { write(layout.toByteArray(event)); } - notifyAll(); final CountDownLatch latch = countDownLatch; if (latch != null) { latch.countDown(); From 6856fc4260d2e8930d7bfe3ffa26493fb1bae7b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Thu, 21 May 2026 13:02:02 +0200 Subject: [PATCH 7/7] Link to PR in changelog --- src/changelog/.2.x.x/3926_revamp_list_appender.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/changelog/.2.x.x/3926_revamp_list_appender.xml b/src/changelog/.2.x.x/3926_revamp_list_appender.xml index 6a0879546c5..e66a5fdfadf 100644 --- a/src/changelog/.2.x.x/3926_revamp_list_appender.xml +++ b/src/changelog/.2.x.x/3926_revamp_list_appender.xml @@ -6,6 +6,7 @@ https://logging.apache.org/xml/ns/log4j-changelog-0.xsd" type="changed"> + Revamp `ListAppender` for thread-safety and clarity.