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

   ### Version
   
   main branch
   
   ### Describe what's wrong
   
   ### Bug Description
   
   Since PR #9460 refactored the Hive catalog to use `HiveClient`, several 
classes were
   moved from `org.apache.gravitino.catalog.hive.*` to a new package
   `org.apache.gravitino.hive.*` (e.g., `HiveExceptionConverter`, 
`HiveClientPool`).
   
   However, `IsolatedClassLoader.isCatalogClass()` was not updated to recognize 
the new
   package prefix. As a result, these classes are incorrectly treated as 
**shared classes**
   and delegated to the server classloader for loading.
   
   ### Root Cause
   
   `isCatalogClass()` only checks for `org.apache.gravitino.catalog.*` 
sub-packages:
   
   ```java
   private boolean isCatalogClass(String name) {
       return name.startsWith("org.apache.gravitino.catalog")
           && (name.startsWith("org.apache.gravitino.catalog.hive.")
               || ...);
   }
   ```
   
   Classes under `org.apache.gravitino.hive.*` are not matched, so 
`isSharedClass()`
   returns `true` for them. The server classloader then tries to load these 
classes but
   cannot find them in the server classpath (they only exist under
   `/gravitino/catalogs/hive/libs/`).
   
   In practice, the server classloader falls back to `super.loadClass()` 
(URLClassLoader
   over execJars) and succeeds for the class itself. But the 
**compiler-generated synthetic
   class `$1`** (produced by `switch(enum)` statements) is loaded through the 
defining
   classloader of the enclosing class. If the enclosing class happens to be 
loaded by the
   server classloader in a specific timing window, `$1` is also requested from 
the server
   classloader — which cannot find it. **The JVM permanently caches this load 
failure**,
   causing all subsequent calls to throw `NoClassDefFoundError` until the 
process restarts.
   
   ### Impact
   
   - `NoClassDefFoundError: 
org/apache/gravitino/hive/client/HiveExceptionConverter$1`
   - Affects any API that triggers 
`HiveExceptionConverter.toNoSuchObjectException()`
     (e.g., loading a non-existent table via Hive catalog)
   - Failure is **permanent** within the process lifetime — not recoverable 
without restart
   - Appears intermittent because it depends on classloader initialization 
timing
   
   ### Fix
   
   Add `org.apache.gravitino.hive.` as a recognized catalog class prefix in
   `IsolatedClassLoader.isCatalogClass()`:
   
   ```java
   private boolean isCatalogClass(String name) {
       return name.startsWith("org.apache.gravitino.hive.")   // ← add this
           || (name.startsWith("org.apache.gravitino.catalog")
               && (name.startsWith("org.apache.gravitino.catalog.hive.")
                   || ...));
   }
   ```
   
   ### Steps to Reproduce
   
   1. Deploy Gravitino with Hive catalog
   2. Call an API that triggers `HiveExceptionConverter` with a non-existent 
resource
      (e.g., `loadTable` on a table that does not exist in Hive metastore)
   3. Under specific classloader initialization timing, observe
      `NoClassDefFoundError: 
org/apache/gravitino/hive/client/HiveExceptionConverter$1`
   4. All subsequent calls to the same catalog on this process instance fail 
with the
      same error until restart
   
   ### Related
   
   - Introduced by: #9460
   
   ### Error message and/or stacktrace
   
   java.lang.NoClassDefFoundError: 
org/apache/gravitino/hive/client/HiveExceptionConverter$1
           at 
org.apache.gravitino.hive.client.HiveExceptionConverter.toNoSuchObjectException(HiveExceptionConverter.java:233)
           at 
org.apache.gravitino.hive.client.HiveExceptionConverter.toGravitinoException(HiveExceptionConverter.java:77)
           at 
org.apache.gravitino.catalog.hive.HiveCatalogOperations.loadTable(HiveCatalogOperations.java:...)
           at org.apache.gravitino.catalog.SchemaOperationDispatcher.lambda$...
           at 
org.apache.gravitino.utils.IsolatedClassLoader.withClassLoader(IsolatedClassLoader.java:86)
           ...
   
   ### How to reproduce
   
   + Which Gravitino version to use
   0.9.x / main (after #9460 merged)
   
   + Steps
   1. Deploy Gravitino with a Hive catalog configured
   2. Under specific JVM classloader initialization timing (e.g. first
      request to a catalog that has never been accessed, or after a
      long-running process where classloader init races with a request),
      call any API that triggers HiveExceptionConverter internally —
      for example, load a table that does not exist in Hive Metastore:
   
      GET 
/api/metalakes/{metalake}/catalogs/{hive-catalog}/schemas/{schema}/tables/{non-existent-table}
   
   3. Observe NoClassDefFoundError: 
org/apache/gravitino/hive/client/HiveExceptionConverter$1
   4. All subsequent requests to the same catalog on this process instance
      fail with the same error — the JVM permanently caches the load
      failure until the process is restarted.
   
   + Note
   The bug is intermittent because it depends on whether
   IsolatedClassLoader is initialized before or during the first request.
   Once triggered, it is permanent within the process lifetime.
   Introduced by #9460 which moved HiveExceptionConverter to
   org.apache.gravitino.hive.client.* without updating isCatalogClass().
   
   ### Additional context
   
   + Which Gravitino version to use
   0.9.x / main (after #9460 merged)
   
   + Steps
   1. Deploy Gravitino with a Hive catalog configured
   2. Under specific JVM classloader initialization timing (e.g. first
      request to a catalog that has never been accessed, or after a
      long-running process where classloader init races with a request),
      call any API that triggers HiveExceptionConverter internally —
      for example, load a table that does not exist in Hive Metastore:
   
      GET 
/api/metalakes/{metalake}/catalogs/{hive-catalog}/schemas/{schema}/tables/{non-existent-table}
   
   3. Observe NoClassDefFoundError: 
org/apache/gravitino/hive/client/HiveExceptionConverter$1
   4. All subsequent requests to the same catalog on this process instance
      fail with the same error — the JVM permanently caches the load
      failure until the process is restarted.
   
   + Note
   The bug is intermittent because it depends on whether
   IsolatedClassLoader is initialized before or during the first request.
   Once triggered, it is permanent within the process lifetime.
   Introduced by #9460 which moved HiveExceptionConverter to
   org.apache.gravitino.hive.client.* without updating isCatalogClass().


-- 
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