Skip to content

Fix webapp worker thread and classloader leak on undeploy#172

Open
fangornoftheforest wants to merge 1 commit into
Password4j:masterfrom
fangornoftheforest:fix/webapp-thread-leak
Open

Fix webapp worker thread and classloader leak on undeploy#172
fangornoftheforest wants to merge 1 commit into
Password4j:masterfrom
fangornoftheforest:fix/webapp-thread-leak

Conversation

@fangornoftheforest
Copy link
Copy Markdown

Coauthored by Claude

Three issues prevented clean shutdown of Argon2Function and BalloonHashingFunction executors in a servlet container lifecycle:

  1. createExecutorService() registered a new Runtime shutdown hook on every call. Webapps that redeploy accumulated these hooks indefinitely; each hook pinned the undeployed webapp's classloader via its lambda, preventing garbage collection.

  2. No public API existed to stop the workers. Runtime shutdown hooks only fire on JVM exit, not servlet contextDestroyed, so Tomcat's clearReferencesThreads consistently warned about password4j-worker threads on undeploy.

  3. Even if a caller shut the executor down via reflection, the Argon2Function/BalloonHashingFunction INSTANCES caches still held references to instances whose executors had terminated, breaking subsequent hash calls.

Changes:

  • Utils: single managed shutdown hook plus a static registry of executors. Registration is idempotent (guarded by a volatile hook reference).
  • Password.shutdown(): public static entry point to be called from ServletContextListener.contextDestroyed. Shuts down every tracked executor, clears the function-instance caches so future calls rebuild cleanly, and removes the JVM shutdown hook to free the classloader.
  • Argon2Function/BalloonHashingFunction: package-private clearInstances() used by the shutdown path.
  • PasswordTest: regression test covering thread termination and reuse after shutdown.

Fixes #171: Failed to stop password4j-worker thread

Three issues prevented clean shutdown of Argon2Function and
BalloonHashingFunction executors in a servlet container lifecycle:

1. createExecutorService() registered a new Runtime shutdown hook on
   every call. Webapps that redeploy accumulated these hooks
   indefinitely; each hook pinned the undeployed webapp's classloader
   via its lambda, preventing garbage collection.

2. No public API existed to stop the workers. Runtime shutdown hooks
   only fire on JVM exit, not servlet contextDestroyed, so Tomcat's
   clearReferencesThreads consistently warned about password4j-worker
   threads on undeploy.

3. Even if a caller shut the executor down via reflection, the
   Argon2Function/BalloonHashingFunction INSTANCES caches still held
   references to instances whose executors had terminated, breaking
   subsequent hash calls.

Changes:
- Utils: single managed shutdown hook plus a static registry of
  executors. Registration is idempotent (guarded by a volatile hook
  reference).
- Password.shutdown(): public static entry point to be called from
  ServletContextListener.contextDestroyed. Shuts down every tracked
  executor, clears the function-instance caches so future calls
  rebuild cleanly, and removes the JVM shutdown hook to free the
  classloader.
- Argon2Function/BalloonHashingFunction: package-private
  clearInstances() used by the shutdown path.
- PasswordTest: regression test covering thread termination and reuse
  after shutdown.

Fixes Password4j#171

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Failed to stop password4j-worker thread

1 participant