Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ConcurrentModificationException in updateLoggers() #3235

Open
wants to merge 1 commit into
base: 2.24.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,23 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.message.MessageFactory2;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;

class LoggerContextTest {

private static final int LOGGER_COUNT = 1024;
private static final int CONCURRENCY_LEVEL = 16;

@Test
void newInstance_should_honor_name_and_message_factory(final TestInfo testInfo) {
final String testName = testInfo.getDisplayName();
Expand All @@ -37,4 +47,32 @@ void newInstance_should_honor_name_and_message_factory(final TestInfo testInfo)
assertThat((MessageFactory) logger.getMessageFactory()).isSameAs(messageFactory);
}
}

@Test
void getLoggers_can_be_updated_concurrently(final TestInfo testInfo) {
final String testName = testInfo.getDisplayName();
final ExecutorService executorService = Executors.newFixedThreadPool(CONCURRENCY_LEVEL);
try (LoggerContext loggerContext = new LoggerContext(testName)) {
// Create a logger
Collection<Future<?>> tasks = IntStream.range(0, CONCURRENCY_LEVEL)
.mapToObj(i -> executorService.submit(() -> {
// Iterates over loggers
loggerContext.updateLoggers();
// Create some loggers
for (int j = 0; j < LOGGER_COUNT; j++) {
loggerContext.getLogger(testName + "-" + i + "-" + j);
}
// Iterate over loggers again
loggerContext.updateLoggers();
}))
.collect(Collectors.toList());
Assertions.assertDoesNotThrow(() -> {
for (Future<?> task : tasks) {
task.get();
}
});
} finally {
executorService.shutdown();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
Expand Down Expand Up @@ -513,7 +512,7 @@ public Logger getLogger(final String name) {
* @return a collection of the current loggers.
*/
public Collection<Logger> getLoggers() {
return loggerRegistry.getLoggers().collect(Collectors.toList());
return loggerRegistry.getLoggers();
vy marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static java.util.Objects.requireNonNull;

import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
Expand All @@ -27,6 +28,7 @@
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.message.MessageFactory;
Expand Down Expand Up @@ -78,15 +80,19 @@ public InternalLoggerRegistry() {}
}
}

public Stream<Logger> getLoggers() {
public Collection<Logger> getLoggers() {
readLock.lock();
try {
// Return a new collection to allow concurrent iteration over the loggers
//
// https://github.com/apache/logging-log4j2/issues/3234
return loggerRefByNameByMessageFactory.values().stream()
.flatMap(loggerRefByName -> loggerRefByName.values().stream())
.flatMap(loggerRef -> {
@Nullable Logger logger = loggerRef.get();
return logger != null ? Stream.of(logger) : Stream.empty();
});
})
.collect(Collectors.toList());
} finally {
readLock.unlock();
}
Expand Down
10 changes: 10 additions & 0 deletions src/changelog/.2.x.x/3234_concurrent-logger-modification.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://logging.apache.org/xml/ns"
xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
type="fixed">
<issue id="3234" link="https://github.com/apache/logging-log4j2/issues/3234"/>
<description format="asciidoc">
Fix `ConcurrentModificationException`, if multiple threads iterate over the loggers at the same time.
</description>
</entry>