Skip to content

Commit 4c0e433

Browse files
authored
Merge pull request #165 from halissontorres/fix/issue-163-thread-leak
Fix #163 thread leak
2 parents 3f73da7 + 3618315 commit 4c0e433

File tree

2 files changed

+53
-3
lines changed

2 files changed

+53
-3
lines changed

src/main/java/com/password4j/Utils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ static ExecutorService createExecutorService()
665665
{
666666
ExecutorService executorService = Executors.newFixedThreadPool(AVAILABLE_PROCESSORS, runnable -> {
667667
Thread thread = new Thread(THREAD_GROUP, runnable, "password4j-worker-" + THREAD_COUNTER.getAndIncrement());
668-
thread.setDaemon(false);
668+
thread.setDaemon(true);
669669
return thread;
670670
});
671671

src/test/com/password4j/IssuesTest.java

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
import java.util.concurrent.ConcurrentMap;
1515
import java.util.stream.Collectors;
1616

17-
import static org.junit.Assert.assertEquals;
18-
import static org.junit.Assert.assertTrue;
17+
import static org.junit.Assert.*;
1918

2019
public class IssuesTest
2120
{
@@ -178,5 +177,56 @@ private static String printBytesToString(byte[] bytes)
178177
return byteString.toString();
179178
}
180179

180+
/**
181+
* <h2>Issue #163: Tomcat Thread Leak Prevention</h2>
182+
*
183+
* <p><strong>Problem:</strong> Tomcat was reporting thread leak warnings:</p>
184+
* <blockquote>
185+
* "The web application appears to have started a thread named [password4j-worker-X]
186+
* but has failed to stop it. This is very likely to create a memory leak."
187+
* </blockquote>
188+
*
189+
* <p><strong>Root Cause:</strong> Non-daemon threads prevent JVM shutdown and cause
190+
* application server warnings when web applications are undeployed.</p>
191+
*
192+
* <p><strong>Solution:</strong> Changed <code>thread.setDaemon(false)</code> to
193+
* <code>thread.setDaemon(true)</code> in <code>Utils.createExecutorService()</code></p>
194+
*
195+
* <p><strong>Impact:</strong> Daemon threads automatically terminate when the main
196+
* application shuts down, preventing memory leaks in containerized environments.</p>
197+
*
198+
* @see <a href="https://github.com/Password4j/password4j/issues/163" target="_blank">GitHub Issue #163</a>
199+
* @see Utils#createExecutorService()
200+
* @see <a href="https://docs.oracle.com/javase/tutorial/essential/concurrency/daemon.html" target="_blank">Oracle: Daemon Threads</a>
201+
* @since 1.8.1
202+
* @category Threading
203+
* @category MemoryLeak
204+
*/
205+
206+
@Test
207+
public void issue163() {
208+
final String plainText = "The quick brown fox jumps over the lazy dog";
209+
final String hash = Password.hash(plainText)
210+
.withArgon2()
211+
.getResult();
212+
assertNotNull("Password hashing should work", hash);
213+
try {
214+
Thread.sleep(200);
215+
} catch (InterruptedException e) {
216+
Thread.currentThread().interrupt();
217+
}
218+
219+
boolean foundWorkerThread = false;
220+
for (Thread thread : Thread.getAllStackTraces().keySet()) {
221+
if (thread.getName().startsWith("password4j-worker-")) {
222+
foundWorkerThread = true;
223+
224+
// THE FIX: This thread MUST be daemon to prevent Tomcat memory leak warning
225+
final String assertionMessage = "Thread " + thread.getName() + " must be daemon thread to prevent Tomcat leak warning";
226+
assertTrue(assertionMessage, thread.isDaemon());
227+
}
228+
}
229+
assertTrue("Should have found at least one password4j-worker thread", foundWorkerThread);
230+
}
181231

182232
}

0 commit comments

Comments
 (0)