Skip to content

Commit 0914292

Browse files
committed
Remove unneeded weak reference from prefix logger
We have a custom logger implementation known as a prefix logger that is used to write every message by the logger with a given prefix. This is useful for node-level, index-level, and shard-level messages where we want to log the node name, index name, and shard ID, respectively, if possible. The mechanism that we employ is that of a marker. Log4j has a built-in facility for managing these markers, but its effectively a memory leak because these markers are held in a map and can never be released. This is problematic for us since indices and shards do not necessarily have infinite life spans and so on a node where there are many indices being creted and destroyed, this infinite lifespan can be a problem indeed. To solve this, we use our own cache of markers. This is necessary to prevent too many instances of the marker for the same prefix from being created (just think of all the shard-level components that exist in the system), and to workaround the effective leak in Log4j. These markers are stored as weak references in a weak hash map. It is these weak references that are unneeded. When a key is removed from a weak hash map, the corresponding entry is placed on a reference queue that is eventually cleared. This commit simplifies prefix logger by removing this unnecessary weak reference wrapper. Relates #22460
1 parent 58bc897 commit 0914292

File tree

2 files changed

+58
-8
lines changed

2 files changed

+58
-8
lines changed

core/src/main/java/org/elasticsearch/common/logging/PrefixLogger.java

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,34 +26,69 @@
2626
import org.apache.logging.log4j.spi.ExtendedLogger;
2727
import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;
2828

29-
import java.lang.ref.WeakReference;
3029
import java.util.WeakHashMap;
3130

31+
/**
32+
* A logger that prefixes all messages with a fixed prefix specified during construction. The prefix mechanism uses the marker construct, so
33+
* for the prefixes to appear, the logging layout pattern must include the marker in its pattern.
34+
*/
3235
class PrefixLogger extends ExtendedLoggerWrapper {
3336

34-
// we can not use the built-in Marker tracking (MarkerManager) because the MarkerManager holds
35-
// a permanent reference to the marker; however, we have transient markers from index-level and
36-
// shard-level components so this would effectively be a memory leak
37-
private static final WeakHashMap<String, WeakReference<Marker>> markers = new WeakHashMap<>();
37+
/*
38+
* We can not use the built-in Marker tracking (MarkerManager) because the MarkerManager holds a permanent reference to the marker;
39+
* however, we have transient markers from index-level and shard-level components so this would effectively be a memory leak. Since we
40+
* can not tie into the lifecycle of these components, we have to use a mechanism that enables garbage collection of such markers when
41+
* they are no longer in use.
42+
*/
43+
private static final WeakHashMap<String, Marker> markers = new WeakHashMap<>();
44+
45+
/**
46+
* Return the size of the cached markers. This size can vary as markers are cached but collected during GC activity when a given prefix
47+
* is no longer in use.
48+
*
49+
* @return the size of the cached markers
50+
*/
51+
static int markersSize() {
52+
return markers.size();
53+
}
3854

55+
/**
56+
* The marker for this prefix logger.
57+
*/
3958
private final Marker marker;
4059

60+
/**
61+
* Obtain the prefix for this prefix logger. This can be used to create a logger with the same prefix as this one.
62+
*
63+
* @return the prefix
64+
*/
4165
public String prefix() {
4266
return marker.getName();
4367
}
4468

69+
/**
70+
* Construct a prefix logger with the specified name and prefix.
71+
*
72+
* @param logger the extended logger to wrap
73+
* @param name the name of this prefix logger
74+
* @param prefix the prefix for this prefix logger
75+
*/
4576
PrefixLogger(final ExtendedLogger logger, final String name, final String prefix) {
4677
super(logger, name, null);
4778

4879
final String actualPrefix = (prefix == null ? "" : prefix).intern();
4980
final Marker actualMarker;
5081
// markers is not thread-safe, so we synchronize access
5182
synchronized (markers) {
52-
final WeakReference<Marker> marker = markers.get(actualPrefix);
53-
final Marker maybeMarker = marker == null ? null : marker.get();
83+
final Marker maybeMarker = markers.get(actualPrefix);
5484
if (maybeMarker == null) {
5585
actualMarker = new MarkerManager.Log4jMarker(actualPrefix);
56-
markers.put(actualPrefix, new WeakReference<>(actualMarker));
86+
/*
87+
* We must create a new instance here as otherwise the marker will hold a reference to the key in the weak hash map; as
88+
* those references are held strongly, this would give a strong reference back to the key preventing them from ever being
89+
* collected. This also guarantees that no other strong reference can be held to the prefix anywhere.
90+
*/
91+
markers.put(new String(actualPrefix), actualMarker);
5792
} else {
5893
actualMarker = maybeMarker;
5994
}

qa/evil-tests/src/test/java/org/elasticsearch/common/logging/EvilLoggerTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import java.util.regex.Pattern;
4848

4949
import static org.hamcrest.Matchers.equalTo;
50+
import static org.hamcrest.Matchers.lessThan;
5051
import static org.hamcrest.Matchers.startsWith;
5152

5253
public class EvilLoggerTests extends ESTestCase {
@@ -170,6 +171,20 @@ public void testPrefixLogger() throws IOException, IllegalAccessException, UserE
170171
}
171172
}
172173

174+
public void testPrefixLoggerMarkersCanBeCollected() throws IOException, UserException {
175+
setupLogging("prefix");
176+
177+
final int prefixes = 1 << 19; // to ensure enough markers that the GC should collect some when we force a GC below
178+
for (int i = 0; i < prefixes; i++) {
179+
// this has the side effect of caching a marker with this prefix
180+
Loggers.getLogger("prefix" + i, "prefix" + i);
181+
}
182+
183+
// this will free the weakly referenced keys in the marker cache
184+
System.gc();
185+
assertThat(PrefixLogger.markersSize(), lessThan(prefixes));
186+
}
187+
173188
public void testProperties() throws IOException, UserException {
174189
final Settings.Builder builder = Settings.builder().put("cluster.name", randomAlphaOfLength(16));
175190
if (randomBoolean()) {

0 commit comments

Comments
 (0)