Merge pull request #171 from apache/bugfix/UIMA-6413-Memory-leak-in-FSClassRegistry

[UIMA-6413] Memory leak in FSClassRegistry
diff --git a/uimaj-core/src/main/java/org/apache/uima/internal/util/UIMAClassLoader.java b/uimaj-core/src/main/java/org/apache/uima/internal/util/UIMAClassLoader.java
index 983b59b..f3c4f99 100644
--- a/uimaj-core/src/main/java/org/apache/uima/internal/util/UIMAClassLoader.java
+++ b/uimaj-core/src/main/java/org/apache/uima/internal/util/UIMAClassLoader.java
@@ -29,6 +29,7 @@
 import java.util.StringTokenizer;
 
 import org.apache.uima.cas.impl.FSClassRegistry;
+import org.apache.uima.cas.impl.TypeSystemImpl;
 
 /**
  * UIMAClassLoader is used as extension ClassLoader for UIMA to load additional components like
@@ -284,7 +285,16 @@
   @Override
   public void close() throws IOException {
     isClosed = true;
-    FSClassRegistry.unregister_jcci_classloader(this);
+    // There is a circular dependency between the static initializer blocks of FSClassRegistry and
+    // TypeSystemImpl which requires that the TypeSystemImpl class must be initialized before the
+    // FSClassRegistry to avoid exceptions. The if-statement here is a red-herring because the
+    // actual comparison does not really matter - under normal circumstances, `staticTsi` cannot be
+    // null.
+    // However, what it really does is trigger the static initialization block of TypeSystemImpl
+    // so that the subsequent call to FSClassRegistry does not trigger an exception.
+    if (TypeSystemImpl.staticTsi != null) {
+      FSClassRegistry.unregister_jcci_classloader(this);
+    }
     super.close();
   }
 
diff --git a/uimaj-core/src/test/java/org/apache/uima/cas/impl/FSClassRegistryTest.java b/uimaj-core/src/test/java/org/apache/uima/cas/impl/FSClassRegistryTest.java
index 3f04259..78379f9 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/impl/FSClassRegistryTest.java
+++ b/uimaj-core/src/test/java/org/apache/uima/cas/impl/FSClassRegistryTest.java
@@ -32,13 +32,18 @@
   @Before
   public void setup() {
     System.setProperty(FSClassRegistry.RECORD_JCAS_CLASSLOADERS, "true");
+
+    // Calls to FSClassRegistry will fail unless the static initializer block in TypeSystemImpl
+    // has previously been triggered! During normal UIMA operations, this should not happen,
+    // in particular because FSClassRegistry is not really part of the public UIMA API -
+    // but in the minimal setup here, we need to make sure TypeSystemImpl has been initialized
+    // first.
+    new TypeSystemImpl();
   }
 
   @Test
   public void thatCreatingResourceManagersWithExtensionClassloaderDoesNotFillUpCache()
           throws Exception {
-    // Needed to get the type system code initialized before we call clToType2JCasSize();
-    CasCreationUtils.createCas();
     int numberOfCachedClassloadersAtStart = FSClassRegistry.clToType2JCasSize();
     for (int i = 0; i < 5; i++) {
       ResourceManager resMgr = UIMAFramework.newDefaultResourceManager();
@@ -60,8 +65,6 @@
 
   @Test
   public void thatCreatingResourceManagersWithExtensionPathDoesNotFillUpCache() throws Exception {
-    // Needed to get the type system code initialized before we call clToType2JCasSize();
-    CasCreationUtils.createCas();
     int numberOfCachedClassloadersAtStart = FSClassRegistry.clToType2JCasSize();
     for (int i = 0; i < 5; i++) {
       ResourceManager resMgr = UIMAFramework.newDefaultResourceManager();
diff --git a/uimaj-core/src/test/java/org/apache/uima/resource/impl/ResourceManager_implTest.java b/uimaj-core/src/test/java/org/apache/uima/resource/impl/ResourceManager_implTest.java
index 3b6d58a..2aa2150 100644
--- a/uimaj-core/src/test/java/org/apache/uima/resource/impl/ResourceManager_implTest.java
+++ b/uimaj-core/src/test/java/org/apache/uima/resource/impl/ResourceManager_implTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.uima.resource.impl;
 
+import static org.assertj.core.api.Assertions.assertThatCode;
 import static org.junit.Assert.assertTrue;
 
 import java.io.File;
@@ -266,4 +267,13 @@
       JUnitExtension.handleException(e);
     }
   }
+
+  @Test
+  public void testCreateWithExtensionClassloaderAndDestroy() throws Exception {
+    assertThatCode(() -> {
+      ResourceManager resMgr = UIMAFramework.newDefaultResourceManager();
+      resMgr.setExtensionClassLoader(getClass().getClassLoader(), false);
+      resMgr.destroy();
+    }).doesNotThrowAnyException();
+  }
 }