Copilot commented on code in PR #12547:
URL: https://github.com/apache/gravitino/pull/12547#discussion_r3913107159


##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorManager.java:
##########
@@ -206,12 +238,126 @@ private void loadMetalake() {
           icebergRestUriDiscovery.refresh(usedMetalake, config, 
gravitinoClient);
           loadCatalogs(metalake);
         } catch (Exception e) {
-          LOG.error(e, "Load Metalake %s failed.", usedMetalake);
+          recordMetalakeError(usedMetalake, e);
         }
       }
-    } catch (Exception e) {
-      LOG.error(e, "Error when loading metalake");
+
+      pruneMissingMetalakes(usedMetalakes);
+
+      if (metalakeErrors.isEmpty()) {
+        recordLoadSuccess();
+      } else {
+        // Some metalake failed. The loop reaching its last line is not a 
health signal, so do not
+        // advance the success time or clear the error, or load_status would 
report a healthy loop
+        // while no catalog is being registered at all.
+        recordLoadFailure(
+            String.format(
+                "%d of %d metalakes failed to load: %s",
+                metalakeErrors.size(), usedMetalakes.size(), new 
TreeMap<>(metalakeErrors)),
+            null);
+      }
+    } catch (Throwable t) {
+      // Catch Throwable, not Exception: scheduleWithFixedDelay silently 
cancels the task forever
+      // the first time the runnable throws, and loading a Trino connector 
plugin can raise
+      // NoClassDefFoundError. A dead loop must not look like a healthy one.
+      recordLoadFailure(toErrorMessage(t), t);
+    }
+  }
+
+  private void pruneMissingMetalakes(Set<String> usedMetalakes) {
+    // A metalake that was deleted, or that dropped out of the configuration, 
leaves its catalog
+    // connectors, catalog rows and cached metalake handle behind. Without 
this its catalogs keep
+    // reporting REGISTERED and stay live in Trino even though they no longer 
exist, and
+    // getUsedMetalakes()/getMetalake() keep returning a metalake that isn't 
loaded anymore.
+    for (Map.Entry<String, CatalogConnectorContext> entry : 
catalogConnectors.entrySet()) {
+      GravitinoCatalog catalog = entry.getValue().getCatalog();
+      if (usedMetalakes.contains(catalog.getMetalake())) {
+        continue;
+      }
+      try {
+        unloadCatalog(catalog);
+      } catch (Exception e) {
+        // The metalake is gone but the catalog is still registered in Trino. 
Record it, or the
+        // pruning below would drop the row and the table would report nothing 
at all about a
+        // catalog that still shows up in SHOW CATALOGS.
+        recordCatalogState(
+            CatalogRegistrationState.failed(
+                catalog.getMetalake(),
+                catalog.getName(),
+                entry.getKey(),
+                catalog.getProvider(),
+                "The metalake was removed but the catalog could not be 
unregistered from Trino: "
+                    + toErrorMessage(e)),
+            e);
+      }
     }
+
+    catalogStates.values().removeIf(state -> 
!usedMetalakes.contains(state.getMetalake()));

Review Comment:
   This unconditional pruning also removes the FAILED state recorded just above 
when unregistering a catalog from a vanished metalake fails. The connector then 
remains visible in `SHOW CATALOGS`, but `catalog_status` loses its diagnostic 
row and `load_status` can report success. Retain states while their Trino 
connector still exists, as the per-catalog pruning path already does.



##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConnectorFactory.java:
##########
@@ -134,7 +133,6 @@ public Connector create(
           // failed client initialization leaves a shared manager with a null 
Gravitino client,
           // causing later connector creation attempts to fail with a 
misleading NPE.
           catalogConnectorManager = newCatalogConnectorManager;

Review Comment:
   The manager is published before `start()` succeeds, and the start-attempt 
flag remains true if `start()` throws. A later static `create()` therefore 
skips startup and returns a system connector even though no load task was 
scheduled—the new tests explicitly exercise that dead-connector path. Preserve 
and rethrow the initialization failure, or discard/close the failed manager so 
creation cannot succeed with a stopped load loop.



##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConfig.java:
##########
@@ -350,7 +350,10 @@ public String getURI() {
    * @return the metalake name for used
    */
   public String getMetalake() {
-    return config.getOrDefault(GRAVITINO_METALAKE.key, 
GRAVITINO_METALAKE.defaultValue);
+    // Trimmed so a stray leading/trailing space in the catalog properties 
file does not make this
+    // value silently stop matching the canonical metalake name the load loop 
records states
+    // under, e.g. in the catalog_status/load_status system tables' 
per-metalake filtering.
+    return config.getOrDefault(GRAVITINO_METALAKE.key, 
GRAVITINO_METALAKE.defaultValue).trim();

Review Comment:
   The new whitespace-normalization behavior has no corresponding test in 
`TestGravitinoConfig`. Add a case with leading/trailing whitespace to lock down 
the returned canonical metalake and prevent this system-table filtering fix 
from regressing.



##########
trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogRegister.java:
##########
@@ -348,15 +364,35 @@ static String redactSecrets(String createCatalogCommand) {
 
   // Some JDBC drivers echo the failing statement back in the exception 
message, which for a
   // failed CREATE CATALOG would otherwise carry its embedded credentials into 
every caller and
-  // log line downstream. The original exception is deliberately not kept as 
the cause, since a
-  // logged stack trace prints causes' messages too and would defeat the 
redaction.
+  // log line downstream. Every level of the cause chain is rebuilt with its 
message redacted,
+  // rather than discarded outright, so a deep cause unrelated to credentials 
(e.g. a connector's
+  // own configuration validation error) still reaches the caller.
   private static SQLException redactedSqlException(SQLException e) {
-    String message = e.getMessage() == null ? null : 
redactSecrets(e.getMessage());
-    SQLException redacted = new SQLException(message, e.getSQLState(), 
e.getErrorCode());
+    Throwable redactedCause = e.getCause() == null ? null : 
redactThrowableChain(e.getCause());
+    SQLException redacted =
+        new SQLException(
+            e.getMessage() == null ? null : redactSecrets(e.getMessage()),
+            e.getSQLState(),
+            e.getErrorCode(),
+            redactedCause);
     redacted.setStackTrace(e.getStackTrace());
     return redacted;
   }
 
+  private static Throwable redactThrowableChain(Throwable t) {
+    // Guards against a cause cycle the same way toErrorMessage() in 
CatalogConnectorManager does.
+    Throwable cause = t.getCause();
+    Throwable redactedCause = (cause == null || cause == t) ? null : 
redactThrowableChain(cause);

Review Comment:
   This guard handles only a throwable whose cause is itself. Java also permits 
multi-node cycles such as A → B → A, which recurse here until 
`StackOverflowError` instead of producing the intended redacted exception. 
Track visited throwables by identity (or rebuild the chain iteratively) and 
stop at any repeated node.



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