Fixes memory leak in per-thread context

Signed-off-by: Alexander Pinčuk <alexander.v.pinchuk@gmail.com>
diff --git a/hk2-api/src/main/java/org/glassfish/hk2/internal/PerThreadContext.java b/hk2-api/src/main/java/org/glassfish/hk2/internal/PerThreadContext.java
index 0ae3a88..244cbc9 100755
--- a/hk2-api/src/main/java/org/glassfish/hk2/internal/PerThreadContext.java
+++ b/hk2-api/src/main/java/org/glassfish/hk2/internal/PerThreadContext.java
@@ -1,6 +1,7 @@
 /*
- * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2024 Contributors to Eclipse Foundation.
  * Copyright (c) 2023 Payara Foundation and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -17,13 +18,13 @@
 
 package org.glassfish.hk2.internal;
 
+import jakarta.inject.Singleton;
+
 import java.lang.annotation.Annotation;
-import org.glassfish.hk2.utilities.CleanerFactory;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.HashMap;
-
-import jakarta.inject.Singleton;
+import java.util.Map;
 
 import org.glassfish.hk2.api.ActiveDescriptor;
 import org.glassfish.hk2.api.Context;
@@ -31,6 +32,7 @@
 import org.glassfish.hk2.api.PerThread;
 import org.glassfish.hk2.api.ServiceHandle;
 import org.glassfish.hk2.api.Visibility;
+import org.glassfish.hk2.utilities.CleanerFactory;
 import org.glassfish.hk2.utilities.general.Hk2ThreadLocal;
 import org.glassfish.hk2.utilities.reflection.Logger;
 
@@ -39,21 +41,18 @@
  */
 @Singleton @Visibility(DescriptorVisibility.LOCAL)
 public class PerThreadContext implements Context<PerThread> {
-    private final static boolean LOG_THREAD_DESTRUCTION = AccessController.<Boolean>doPrivileged(new PrivilegedAction<Boolean>() {
 
-        @Override
-        public Boolean run() {
-            return Boolean.parseBoolean(System.getProperty("org.hk2.debug.perthreadcontext.log", "false"));
-        }
-        
-    });
+    private final static boolean LOG_THREAD_DESTRUCTION = AccessController.doPrivileged(
+            (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("org.hk2.debug.perthreadcontext.log"));
     
-    private final Hk2ThreadLocal<PerContextThreadWrapper> threadMap =
-            new Hk2ThreadLocal<PerContextThreadWrapper>() {
-        public PerContextThreadWrapper initialValue() {
-            return new PerContextThreadWrapper();
-        }
-    };
+    private final Hk2ThreadLocal<PerThreadContextWrapper> threadMap =
+            new Hk2ThreadLocal<>() {
+
+                @Override
+                public PerThreadContextWrapper initialValue() {
+                    return new PerThreadContextWrapper();
+                }
+            };
 
     /* (non-Javadoc)
      * @see org.glassfish.hk2.api.Context#getScope()
@@ -68,14 +67,12 @@
      */
     @SuppressWarnings("unchecked")
     @Override
-    public <U> U findOrCreate(ActiveDescriptor<U> activeDescriptor,
-            ServiceHandle<?> root) {
+    public <U> U findOrCreate(ActiveDescriptor<U> activeDescriptor, ServiceHandle<?> root) {
         U retVal = (U) threadMap.get().get(activeDescriptor);
         if (retVal == null) {
             retVal = activeDescriptor.create(root);
             threadMap.get().put(activeDescriptor, retVal);
         }
-        
         return retVal;
     }
 
@@ -115,39 +112,58 @@
     public void destroyOne(ActiveDescriptor<?> descriptor) {
         // per-thread instances live for the life of the thread,
         // so we will ignore any request to destroy a descriptor
-        
     }
     
-    private static class PerContextThreadWrapper {
+    private static class PerThreadContextWrapper {
 
-        private final HashMap<ActiveDescriptor<?>, Object> instances = new HashMap<>();
-        private final long id = Thread.currentThread().getId();
+        private final CleanableContext context = new CleanableContext();
 
-        public PerContextThreadWrapper() {
+        public PerThreadContextWrapper() {
             registerStopEvent();
         }
                 
-        public boolean has(ActiveDescriptor<?> d) {
-            return instances.containsKey(d);
+        public boolean has(ActiveDescriptor<?> descriptor) {
+            return context.has(descriptor);
         }
         
-        public Object get(ActiveDescriptor<?> d) {
-            return instances.get(d);
+        public Object get(ActiveDescriptor<?> descriptor) {
+            return context.get(descriptor);
         }
         
-        public void put(ActiveDescriptor<?> d, Object v) {
-            instances.put(d, v);
+        public void put(ActiveDescriptor<?> descriptor, Object value) {
+            context.put(descriptor, value);
         }
         
         public final void registerStopEvent() {
-            CleanerFactory.create().register(this, () -> {
-                instances.clear();
-
-                if (LOG_THREAD_DESTRUCTION) {
-                    Logger.getLogger().debug("Removing PerThreadContext data for thread " + id);
-                }
-            });
+            CleanerFactory.create().register(this, context);
         }
-        
+    }
+
+    private static final class CleanableContext implements Runnable {
+
+        private final Map<ActiveDescriptor<?>, Object> instances = new HashMap<>();
+
+        private final long id = Thread.currentThread().getId();
+
+        public boolean has(ActiveDescriptor<?> descriptor) {
+            return instances.containsKey(descriptor);
+        }
+
+        public Object get(ActiveDescriptor<?> descriptor) {
+            return instances.get(descriptor);
+        }
+
+        public void put(ActiveDescriptor<?> descriptor, Object value) {
+            instances.put(descriptor, value);
+        }
+
+        @Override
+        public void run() {
+            instances.clear();
+
+            if (LOG_THREAD_DESTRUCTION) {
+                Logger.getLogger().debug("Removing PerThreadContext data for thread " + id);
+            }
+        }
     }
 }