|
14 | 14 | import java.util.concurrent.ConcurrentMap;
|
15 | 15 | import java.util.stream.Collectors;
|
16 | 16 |
|
17 |
| -import static org.junit.Assert.assertEquals; |
18 |
| -import static org.junit.Assert.assertTrue; |
| 17 | +import static org.junit.Assert.*; |
19 | 18 |
|
20 | 19 | public class IssuesTest
|
21 | 20 | {
|
@@ -178,5 +177,56 @@ private static String printBytesToString(byte[] bytes)
|
178 | 177 | return byteString.toString();
|
179 | 178 | }
|
180 | 179 |
|
| 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 | + } |
181 | 231 |
|
182 | 232 | }
|
0 commit comments