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]