yuqi1129 commented on code in PR #13792:
URL: https://github.com/apache/gravitino/pull/13792#discussion_r4270321208


##########
core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java:
##########
@@ -230,7 +245,12 @@ public <E extends Entity & HasIdentifier> void put(E e, 
boolean overwritten)
   public <E extends Entity & HasIdentifier> E update(
       NameIdentifier ident, Class<E> type, Entity.EntityType entityType, 
Function<E, E> updater)
       throws IOException, NoSuchEntityException, EntityAlreadyExistsException {
-    E updatedEntity = backend.update(ident, entityType, updater);
+    E updatedEntity;
+    try {
+      updatedEntity = backend.update(ident, entityType, updater);
+    } catch (RuntimeException re) {
+      throw ExceptionUtils.lockWaitTimeoutAsConflict(re, entityType, ident);

Review Comment:
   Fixed in 1821d51f66. Lance updateTableWithCasRetry now checks 
ExceptionUtils.isLockWaitTimeout on the preserved cause and immediately 
rethrows that same conflict, without backoff or another store.update. Genuine 
lost-CAS conflicts keep the existing bounded retry behavior. 
testLoadDoesNotRetryLockWaitTimeout covers both repairTableMetadata and 
recordCheckedEmptyVersion via loadTable, with the MySQL/H2/PostgreSQL codes: 
one update, no backoff, and the original exception/cause. Removing the guard 
makes all six cases fail; restored code passes. This removes the fivefold 
amplification, while one attempt can still consume the backend lock-wait 
timeout.



##########
core/src/main/java/org/apache/gravitino/storage/relational/utils/ExceptionUtils.java:
##########
@@ -20,17 +20,54 @@
 
 import java.io.IOException;
 import java.sql.SQLException;
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.IdentityHashMap;
 import java.util.Locale;
+import java.util.Set;
+import javax.annotation.Nullable;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.exceptions.OptimisticLockException;
 import 
org.apache.gravitino.storage.relational.converters.SQLExceptionConverterFactory;
 
 public class ExceptionUtils {
+
+  /** MySQL error code of a lock wait timeout (ER_LOCK_WAIT_TIMEOUT). */
+  private static final int MYSQL_LOCK_WAIT_TIMEOUT = 1205;
+
+  /** H2 error code of a lock wait timeout (LOCK_TIMEOUT_1, SQLState HYT00). */
+  private static final int H2_LOCK_TIMEOUT = 50200;
+
+  /** PostgreSQL SQLState of a lock that is not available within lock_timeout. 
*/
+  private static final String POSTGRESQL_LOCK_NOT_AVAILABLE = "55P03";
+
+  private static final Set<String> LOCK_WAIT_TIMEOUT_SQL_STATES =
+      Set.of(POSTGRESQL_LOCK_NOT_AVAILABLE);
+
+  private static final Set<Integer> LOCK_WAIT_TIMEOUT_VENDOR_CODES =
+      Set.of(MYSQL_LOCK_WAIT_TIMEOUT, H2_LOCK_TIMEOUT);
+
   private ExceptionUtils() {}
 
+  /**
+   * Converts a failure of a write whose cause is a {@link SQLException} into 
the matching Gravitino
+   * exception. A lock wait timeout becomes an {@link 
OptimisticLockException}, see {@link
+   * #lockWaitTimeoutAsConflict(RuntimeException, String)}. It is classified 
here, before the
+   * backend converter, because the converter reports an unrecognised SQL 
error as an {@link
+   * IOException}, which callers turn into an internal error.
+   *
+   * @param re The failure of the write.
+   * @param type The type of the entity being written.
+   * @param entityName The name of the entity being written.
+   * @throws IOException If the SQL error has no more specific mapping.
+   * @throws OptimisticLockException If the failure contains a lock wait 
timeout.
+   */
   public static void checkSQLException(
       RuntimeException re, Entity.EntityType type, String entityName) throws 
IOException {
+    throwIfLockWaitTimeout(
+        re, String.format("The %s %s", type.name().toLowerCase(Locale.ROOT), 
entityName));

Review Comment:
   The timeout-first precedence was not intentional. Changed in 1821d51f66: 
checkSQLException now gives a duplicate-key violation precedence across the 
cause/next-exception graph, preserving the original failure as cause and the 
existing already-exists message. 
lockWaitTimeoutAsConflict/throwIfLockWaitTimeout also leave mixed chains for 
that classification, so the tag/model association helpers cannot bypass it. The 
regression test checks both chain orders for all three backend code sets; 
removing the precedence makes it fail. Storage service tests passed on H2, 
MySQL and PostgreSQL. I have not reproduced a mixed duplicate-key/timeout chain 
through a production batch path, so this is a defensive classification 
guarantee rather than a claim about a particular driver producing that chain. 
That evidence boundary is also recorded in the PR description.



##########
core/src/main/java/org/apache/gravitino/tag/TagManager.java:
##########
@@ -179,7 +179,7 @@ public Tag alterTag(String metalake, String name, 
TagChange... changes)
       // and the REST layer maps it to a conflict. Log it here so the 
operator-facing record
       // still names the tag and the metalake.
       LOG.warn(
-          "Failed to alter tag {} under metalake {} because it changed 
concurrently",
+          "Failed to alter tag {} under metalake {} because of a concurrent 
operation",

Review Comment:
   Fixed both sibling logs and their comments in 1821d51f66. Job cleanup and 
status-update deferral now describe a concurrent operation, covering both CAS 
losses and lock-wait timeouts without claiming the row changed. The deferral 
behavior is unchanged; all 75 TestJobManager cases passed, including the final 
restored-code run.



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