1616 */
1717package org .apache .logging .log4j .core .test .util ;
1818
19+ import static java .util .concurrent .TimeUnit .MILLISECONDS ;
20+ import static java .util .concurrent .TimeUnit .SECONDS ;
21+ import static org .awaitility .Awaitility .await ;
1922import static org .junit .jupiter .api .Assertions .assertEquals ;
2023import static org .junit .jupiter .api .Assertions .assertFalse ;
2124import static org .junit .jupiter .api .Assertions .assertNotNull ;
2427import static org .junit .jupiter .api .Assertions .assertTrue ;
2528
2629import java .lang .ref .WeakReference ;
30+ import java .lang .reflect .Field ;
31+ import java .util .Map ;
2732import java .util .concurrent .CountDownLatch ;
2833import java .util .concurrent .ExecutorService ;
2934import java .util .concurrent .Executors ;
3237import org .apache .logging .log4j .core .util .internal .InternalLoggerRegistry ;
3338import org .apache .logging .log4j .message .MessageFactory ;
3439import org .apache .logging .log4j .message .SimpleMessageFactory ;
40+ import org .junit .jupiter .api .AfterEach ;
3541import org .junit .jupiter .api .BeforeEach ;
3642import org .junit .jupiter .api .Test ;
43+ import org .junit .jupiter .api .TestInfo ;
3744
3845class InternalLoggerRegistryTest {
46+ private LoggerContext loggerContext ;
3947 private InternalLoggerRegistry registry ;
4048 private MessageFactory messageFactory ;
4149
4250 @ BeforeEach
43- void setUp () {
44- registry = new InternalLoggerRegistry ();
45- messageFactory = new SimpleMessageFactory ();
51+ void setUp (TestInfo testInfo ) throws NoSuchFieldException , IllegalAccessException {
52+ loggerContext = new LoggerContext (testInfo .getDisplayName ());
53+ Field registryField = loggerContext .getClass ().getDeclaredField ("loggerRegistry" );
54+ registryField .setAccessible (true );
55+ registry = (InternalLoggerRegistry ) registryField .get (loggerContext );
56+ messageFactory = SimpleMessageFactory .INSTANCE ;
57+ }
58+
59+ @ AfterEach
60+ void tearDown () {
61+ if (loggerContext != null ) {
62+ loggerContext .stop ();
63+ }
4664 }
4765
4866 @ Test
@@ -52,54 +70,79 @@ void testGetLoggerReturnsNullForNonExistentLogger() {
5270
5371 @ Test
5472 void testComputeIfAbsentCreatesLogger () {
55- Logger logger =
56- registry .computeIfAbsent ("testLogger" , messageFactory , (name , factory ) -> LoggerContext .getContext ()
57- .getLogger (name , factory ));
73+ Logger logger = registry .computeIfAbsent (
74+ "testLogger" , messageFactory , (name , factory ) -> loggerContext .getLogger (name , factory ));
5875 assertNotNull (logger );
5976 assertEquals ("testLogger" , logger .getName ());
6077 }
6178
6279 @ Test
6380 void testGetLoggerRetrievesExistingLogger () {
64- Logger logger =
65- registry .computeIfAbsent ("testLogger" , messageFactory , (name , factory ) -> LoggerContext .getContext ()
66- .getLogger (name , factory ));
81+ Logger logger = registry .computeIfAbsent (
82+ "testLogger" , messageFactory , (name , factory ) -> loggerContext .getLogger (name , factory ));
6783 assertSame (logger , registry .getLogger ("testLogger" , messageFactory ));
6884 }
6985
7086 @ Test
7187 void testHasLoggerReturnsCorrectStatus () {
7288 assertFalse (registry .hasLogger ("testLogger" , messageFactory ));
73- registry .computeIfAbsent ("testLogger" , messageFactory , ( name , factory ) -> LoggerContext . getContext ()
74- .getLogger (name , factory ));
89+ registry .computeIfAbsent (
90+ "testLogger" , messageFactory , ( name , factory ) -> loggerContext .getLogger (name , factory ));
7591 assertTrue (registry .hasLogger ("testLogger" , messageFactory ));
7692 }
7793
7894 @ Test
79- void testExpungeStaleEntriesRemovesGarbageCollectedLoggers () throws InterruptedException {
80- Logger logger =
81- registry .computeIfAbsent ("testLogger" , messageFactory , (name , factory ) -> LoggerContext .getContext ()
82- .getLogger (name , factory ));
83-
84- WeakReference <Logger > weakRef = new WeakReference <>(logger );
85- logger = null ; // Dereference to allow GC
95+ void testExpungeStaleWeakReferenceEntries () {
96+ String loggerNamePrefix = "testLogger_" ;
97+ int numberOfLoggers = 1000 ;
98+
99+ for (int i = 0 ; i < numberOfLoggers ; i ++) {
100+ Logger logger = registry .computeIfAbsent (
101+ loggerNamePrefix + i , messageFactory , (name , factory ) -> loggerContext .getLogger (name , factory ));
102+ logger .info ("Using logger {}" , logger .getName ());
103+ }
86104
87- // Retry loop to give GC time to collect
88- for (int i = 0 ; i < 10 ; i ++) {
105+ await ().atMost (10 , SECONDS ).pollInterval (100 , MILLISECONDS ).untilAsserted (() -> {
89106 System .gc ();
90- Thread .sleep (100 );
91- if (weakRef .get () == null ) {
92- break ;
107+ System .runFinalization ();
108+ registry .computeIfAbsent (
109+ "triggerExpunge" , messageFactory , (name , factory ) -> loggerContext .getLogger (name , factory ));
110+
111+ Map <MessageFactory , Map <String , WeakReference <Logger >>> loggerRefByNameByMessageFactory =
112+ reflectAndGetLoggerMapFromRegistry ();
113+ Map <String , WeakReference <Logger >> loggerRefByName = loggerRefByNameByMessageFactory .get (messageFactory );
114+
115+ boolean isExpungeStaleEntries = true ;
116+ for (int i = 0 ; i < numberOfLoggers ; i ++) {
117+ if (loggerRefByName .containsKey (loggerNamePrefix + i )) {
118+ isExpungeStaleEntries = false ;
119+ break ;
120+ }
93121 }
94- }
122+ assertTrue (
123+ isExpungeStaleEntries ,
124+ "Stale WeakReference entries were not removed from the inner map for MessageFactory" );
125+ });
126+ }
95127
96- // Access the registry to potentially trigger cleanup
97- registry .computeIfAbsent ("tempLogger" , messageFactory , (name , factory ) -> LoggerContext .getContext ()
98- .getLogger (name , factory ));
128+ @ Test
129+ void testExpungeStaleMessageFactoryEntry () {
130+ Logger logger = registry .computeIfAbsent (
131+ "testLogger" , messageFactory , (name , factory ) -> loggerContext .getLogger (name , factory ));
132+ logger .info ("Using logger {}" , logger .getName ());
133+ logger = null ;
99134
100- assertNull (weakRef .get (), "Logger should have been garbage collected" );
101- assertNull (
102- registry .getLogger ("testLogger" , messageFactory ), "Stale logger should be removed from the registry" );
135+ await ().atMost (10 , SECONDS ).pollInterval (100 , MILLISECONDS ).untilAsserted (() -> {
136+ System .gc ();
137+ System .runFinalization ();
138+ registry .getLogger ("testLogger" , messageFactory );
139+
140+ Map <MessageFactory , Map <String , WeakReference <Logger >>> loggerRefByNameByMessageFactory =
141+ reflectAndGetLoggerMapFromRegistry ();
142+ assertNull (
143+ loggerRefByNameByMessageFactory .get (messageFactory ),
144+ "Stale MessageFactory entry was not removed from the outer map" );
145+ });
103146 }
104147
105148 @ Test
@@ -110,8 +153,8 @@ void testConcurrentAccess() throws InterruptedException {
110153
111154 for (int i = 0 ; i < threadCount ; i ++) {
112155 executor .submit (() -> {
113- registry .computeIfAbsent ("testLogger" , messageFactory , ( name , factory ) -> LoggerContext . getContext ()
114- .getLogger (name , factory ));
156+ registry .computeIfAbsent (
157+ "testLogger" , messageFactory , ( name , factory ) -> loggerContext .getLogger (name , factory ));
115158 latch .countDown ();
116159 });
117160 }
@@ -124,4 +167,14 @@ void testConcurrentAccess() throws InterruptedException {
124167 registry .getLogger ("testLogger" , messageFactory ),
125168 "Logger should be accessible after concurrent creation" );
126169 }
170+
171+ private Map <MessageFactory , Map <String , WeakReference <Logger >>> reflectAndGetLoggerMapFromRegistry ()
172+ throws NoSuchFieldException , IllegalAccessException {
173+ Field loggerMapField = registry .getClass ().getDeclaredField ("loggerRefByNameByMessageFactory" );
174+ loggerMapField .setAccessible (true );
175+ @ SuppressWarnings ("unchecked" )
176+ Map <MessageFactory , Map <String , WeakReference <Logger >>> loggerMap =
177+ (Map <MessageFactory , Map <String , WeakReference <Logger >>>) loggerMapField .get (registry );
178+ return loggerMap ;
179+ }
127180}
0 commit comments