JoegenUSTC opened a new issue, #11706:
URL: https://github.com/apache/gravitino/issues/11706

   ### Version
   
   main branch
   
   ### Describe what's wrong
   
   ### Bug Description
   
   `HiveCatalogCapability.caseSensitiveOnName()` uses a `switch` statement on
   `Capability.Scope` (an enum). The Java compiler generates a synthetic helper
   class `HiveCatalogCapability$1` for every `switch`-on-enum to maintain a
   stable ordinal mapping table.
   
   `caseSensitiveOnName()` is called by `SchemaNormalizeDispatcher` **outside**
   the `IsolatedClassLoader.withClassLoader()` boundary:
   
   ```
   SchemaNormalizeDispatcher.normalizeCaseSensitive()
     → getCapability() → c.capabilities()
         → withClassLoader { catalog.capability() }  ← IsolatedClassLoader 
context; creates HiveCatalogCapability instance
       ← withClassLoader exits; thread context classloader restored to server 
classloader
     → applyCaseSensitive() → capabilities.caseSensitiveOnName(Scope.SCHEMA)
         → JVM attempts to load HiveCatalogCapability$1
   ```
   
   When `HiveCatalogCapability` is correctly loaded by `IsolatedClassLoader`,
   its defining classloader is `IsolatedClassLoader`, and `$1` is loaded from
   `execJars` successfully. However, in certain classloader initialization 
timing
   windows (e.g. concurrent requests during catalog cache rebuild), `$1` is
   requested while the server classloader is involved in the loading chain.
   **The JVM permanently caches this load failure**, and all subsequent calls
   throw `NoClassDefFoundError` until the process is restarted.
   
   ### Root Cause
   
   `SchemaNormalizeDispatcher` calls `Capability.caseSensitiveOnName()` — a
   method on a catalog-side implementation — **after** returning from
   `withClassLoader`. This is an architectural inconsistency: catalog
   implementation code is invoked outside the catalog's isolated classloader
   context.
   
   The immediate trigger is the compiler-generated `$1` synthetic class, which
   only exists because `switch`-on-enum is used. Replacing `switch` with
   `if`-`else` (using `==` on enum constants) eliminates `$1` entirely and makes
   `caseSensitiveOnName()` safe to call from any classloader context.
   
   ### Proposed Fix
   
   Replace `switch` with `if`-`else` in 
`HiveCatalogCapability.caseSensitiveOnName()`:
   
   ```java
   // Before
   switch (scope) {
     case SCHEMA: case TABLE: case COLUMN:
       return CapabilityResult.unsupported("Hive is case insensitive.");
     default:
       return CapabilityResult.SUPPORTED;
   }
   
   // After
   if (scope == Scope.SCHEMA || scope == Scope.TABLE || scope == Scope.COLUMN) {
     return CapabilityResult.unsupported("Hive is case insensitive.");
   }
   return CapabilityResult.SUPPORTED;
   ```
   
   This removes the `$1` synthetic class entirely. `==` on enum constants
   compiles to `if_acmpeq` (reference comparison), which has no synthetic class
   dependency and is safe across classloader boundaries.
   
   ### Additional Context
   
   The same pattern should be reviewed in other `Capability` implementations
   across catalogs, as any `switch`-on-enum in a catalog-side class called from
   `SchemaNormalizeDispatcher` / `TableNormalizeDispatcher` (outside
   `withClassLoader`) would be susceptible to the same issue.
   
   ### Error message and/or stacktrace
   
   java.lang.NoClassDefFoundError: 
org/apache/gravitino/catalog/hive/HiveCatalogCapability$1
           at 
org.apache.gravitino.catalog.hive.HiveCatalogCapability.caseSensitiveOnName(HiveCatalogCapability.java:45)
           at 
org.apache.gravitino.catalog.CapabilityHelpers.applyCaseSensitiveOnName(CapabilityHelpers.java:476)
           at 
org.apache.gravitino.catalog.CapabilityHelpers.applyCaseSensitive(CapabilityHelpers.java:123)
           at 
org.apache.gravitino.catalog.SchemaNormalizeDispatcher.normalizeCaseSensitive(SchemaNormalizeDispatcher.java:94)
           at 
org.apache.gravitino.catalog.SchemaNormalizeDispatcher.loadSchema(SchemaNormalizeDispatcher.java:71)
           at 
org.apache.gravitino.listener.SchemaEventDispatcher.loadSchema(SchemaEventDispatcher.java:116)
           ...
           at 
org.apache.gravitino.utils.PrincipalUtils.doAs(PrincipalUtils.java:44)
   
   ### How to reproduce
   
   + Which Gravitino version to use
   main / any version with Hive catalog
   
   + Steps
   1. Deploy Gravitino with a Hive catalog configured
   2. Call any API that goes through SchemaNormalizeDispatcher
      (e.g. loadSchema, schemaExists, loadTable on first access):
   
      GET /api/metalakes/{metalake}/catalogs/{hive-catalog}/schemas/{schema}
   
   3. Under specific JVM classloader initialization timing, observe:
      NoClassDefFoundError: 
org/apache/gravitino/catalog/hive/HiveCatalogCapability$1
   
   4. All subsequent requests to the same catalog on this process instance
      fail with the same error until restart.
   
   + Root cause
   SchemaNormalizeDispatcher calls capabilities.caseSensitiveOnName() AFTER
   IsolatedClassLoader.withClassLoader() exits (i.e. outside the catalog's
   isolated classloader context). The switch-on-enum in caseSensitiveOnName()
   causes javac to generate HiveCatalogCapability$1. In certain classloader
   initialization timing windows the server classloader is involved in the
   loading chain and cannot find $1; the JVM permanently caches this failure.
   
   + Note
   The bug is intermittent because it depends on classloader initialization
   timing. Once triggered it is permanent within the process lifetime.
   Restarting the process clears the cached failure.
   
   ### Additional context
   
   _No response_


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to