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

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


The following commit(s) were added to refs/heads/master by this push:
     new 29a1357  LOG4J2-2747: Fix a memory leak using discard and synchronous 
queue-full routes
29a1357 is described below

commit 29a1357b419e1f4efbdf490e686497c67c8d1ff5
Author: Carter Kozak <[email protected]>
AuthorDate: Tue Dec 24 10:29:26 2019 -0500

    LOG4J2-2747: Fix a memory leak using discard and synchronous queue-full 
routes
    
    The ring buffer event translator is reset when routes are chosen which
    don't clear the object.
---
 .../logging/log4j/core/async/AsyncLogger.java      |   3 +
 .../core/async/RingBufferLogEventTranslator.java   |   2 +-
 .../log4j/core/GarbageCollectionHelper.java        |   2 +-
 .../logging/log4j/core/async/BlockingAppender.java |   6 +-
 .../core/async/QueueFullAsyncLoggerTest3.java      | 131 +++++++++++++++++++++
 src/changes/changes.xml                            |   3 +
 6 files changed, 144 insertions(+), 3 deletions(-)

diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java
index 901f2fa..22f3d4c 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java
@@ -238,6 +238,7 @@ public class AsyncLogger extends Logger implements 
EventTranslatorVararg<RingBuf
             AsyncQueueFullMessageUtil.logWarningToStatusLogger();
             logMessageInCurrentThread(translator.fqcn, translator.level, 
translator.marker, translator.message,
                     translator.thrown);
+            translator.clear();
             return;
         }
         final EventRoute eventRoute = 
loggerDisruptor.getEventRoute(translator.level);
@@ -248,8 +249,10 @@ public class AsyncLogger extends Logger implements 
EventTranslatorVararg<RingBuf
             case SYNCHRONOUS:
                 logMessageInCurrentThread(translator.fqcn, translator.level, 
translator.marker, translator.message,
                         translator.thrown);
+                translator.clear();
                 break;
             case DISCARD:
+                translator.clear();
                 break;
             default:
                 throw new IllegalStateException("Unknown EventRoute " + 
eventRoute);
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
index cac5d39..c777cae 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
@@ -69,7 +69,7 @@ public class RingBufferLogEventTranslator implements
     /**
      * Release references held by this object to allow objects to be 
garbage-collected.
      */
-    private void clear() {
+    void clear() {
         setBasicValues(null, // asyncLogger
                 null, // loggerName
                 null, // marker
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java
index c175e67..afb3817 100644
--- 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java
@@ -29,7 +29,7 @@ import static org.junit.Assert.assertTrue;
 
 public final class GarbageCollectionHelper implements Closeable, Runnable {
     private static final OutputStream sink = ByteStreams.nullOutputStream();
-    public final AtomicBoolean running = new AtomicBoolean();
+    private final AtomicBoolean running = new AtomicBoolean();
     private final CountDownLatch latch = new CountDownLatch(1);
     private final Thread gcThread = new Thread(new Runnable() {
         @Override
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java
index 2db6368..21e9ed5 100644
--- 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java
@@ -42,6 +42,7 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent;
 @Plugin(name = "Blocking", category = Core.CATEGORY_NAME, elementType = 
Appender.ELEMENT_TYPE, printObject = true)
 public class BlockingAppender extends AbstractAppender {
     private static final long serialVersionUID = 1L;
+    // logEvents may be nulled to disable event tracking, this is useful in 
scenarios testing garbage collection.
     public List<LogEvent> logEvents = new CopyOnWriteArrayList<>();
     public CountDownLatch countDownLatch = null;
 
@@ -56,7 +57,10 @@ public class BlockingAppender extends AbstractAppender {
         event.getMessage().getFormattedMessage();
 
         // may be a reusable event, make a copy, don't keep a reference to the 
original event
-        logEvents.add(Log4jLogEvent.createMemento(event));
+        List<LogEvent> events = logEvents;
+        if (events != null) {
+            events.add(Log4jLogEvent.createMemento(event));
+        }
 
         if (countDownLatch == null) {
             return;
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest3.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest3.java
new file mode 100644
index 0000000..2ec737c
--- /dev/null
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest3.java
@@ -0,0 +1,131 @@
+/*
+ * 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.async;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.categories.AsyncLoggers;
+import org.apache.logging.log4j.core.GarbageCollectionHelper;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.util.Strings;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests queue full scenarios with pure AsyncLoggers (all loggers async).
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+@Category(AsyncLoggers.class)
+public class QueueFullAsyncLoggerTest3 extends QueueFullAbstractTest {
+
+    @BeforeClass
+    public static void beforeClass() {
+        //FORMAT_MESSAGES_IN_BACKGROUND
+        System.setProperty("log4j.format.msg.async", "true");
+        System.setProperty("log4j2.asyncQueueFullPolicy", "discard");
+
+        System.setProperty("AsyncLogger.RingBufferSize", "128"); // minimum 
ringbuffer size
+        System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY,
+                "log4j2-queueFull.xml");
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY);
+    }
+
+    @Rule
+    public LoggerContextRule context = new LoggerContextRule(
+            "log4j2-queueFull.xml", AsyncLoggerContextSelector.class);
+
+    @Before
+    public void before() throws Exception {
+        blockingAppender = context.getRequiredAppender("Blocking", 
BlockingAppender.class);
+    }
+
+
+    @Test(timeout = 15000)
+    public void discardedMessagesShouldBeGarbageCollected() throws 
InterruptedException {
+        final Logger logger = LogManager.getLogger(this.getClass());
+
+        blockingAppender.logEvents = null;
+        blockingAppender.countDownLatch = new CountDownLatch(1);
+        int count = 200;
+        CountDownLatch garbageCollectionLatch = new CountDownLatch(count);
+        for (int i = 0; i < count; i++) {
+            logger.info(new 
CountdownOnGarbageCollectMessage(garbageCollectionLatch));
+        }
+        blockingAppender.countDownLatch.countDown();
+
+        final GarbageCollectionHelper gcHelper = new GarbageCollectionHelper();
+        gcHelper.run();
+        try {
+            assertTrue("Parameter should have been garbage collected", 
garbageCollectionLatch.await(30, TimeUnit.SECONDS));
+        } finally {
+            gcHelper.close();
+        }
+    }
+
+    private static final class CountdownOnGarbageCollectMessage implements 
Message {
+
+        private final CountDownLatch latch;
+
+        CountdownOnGarbageCollectMessage(CountDownLatch latch) {
+            this.latch = latch;
+        }
+
+        @Override
+        public String getFormattedMessage() {
+            return "formatted";
+        }
+
+        @Override
+        public String getFormat() {
+            return null;
+        }
+
+        @Override
+        public Object[] getParameters() {
+            return new Object[0];
+        }
+
+        @Override
+        public Throwable getThrowable() {
+            return null;
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            latch.countDown();
+            super.finalize();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index e06353d..10c2340 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -178,6 +178,9 @@
       <action issue="LOG4J2-2745" dev="ckozak" type="update">
         Update log4j-slf4j18-impl slf4j version to 1.8.0-beta4 from 
1.8.0-alpha2.
       </action>
+      <action issue="LOG4J2-2747" dev="ckozak" type="fix">
+        Fix a memory leak using fully asynchronous logging when the queue is 
full using the 'discard' asynchronous queue full strategy.
+      </action>
     </release>
     <release version="2.13.0" date="2019-12-11" description="GA Release 
2.13.0">
       <action issue="LOG4J2-2058" dev="rgoers" type="fix">

Reply via email to