github-actions[bot] commented on code in PR #65476:
URL: https://github.com/apache/doris/pull/65476#discussion_r3630030730


##########
fe/fe-core/src/main/java/org/apache/doris/dictionary/DictionaryManager.java:
##########
@@ -556,45 +643,109 @@ private boolean commitNowVersion(ConnectContext ctx, 
Dictionary dictionary) {
                     if (status.getErrorCode() != TStatusCode.OK) {
                         LOG.warn("Failed to commit dictionary " + 
dictionary.getId() + " on be " + be.getAddress()
                                 + " because " + status.getErrorMsg());
-                        allSucceed = false;
+                        allSucceeded = false;
+                    } else {
+                        anySucceeded = true;
                     }
                 } else {
                     LOG.warn("Failed to commit dictionary " + 
dictionary.getId() + " on be " + be.getAddress());
-                    allSucceed = false;
+                    allSucceeded = false;
+                    outcomeUnknown = true;
                 }
+            } catch (Exception e) {
+                dictionary.setLastUpdateResult("commit failed: " + 
e.getMessage());
+                LOG.warn("Failed to commit dictionary {} on BE {}", 
dictionary.getId(), be.getId(), e);
+                allSucceeded = false;
+                outcomeUnknown = true;
             }
-        } catch (Exception e) {
-            dictionary.setLastUpdateResult("commit failed: " + e.getMessage());
-            LOG.warn("Failed to commit dictionary " + dictionary.getId(), e);
-            allSucceed = false;
         }
-        return allSucceed;
+        return new CommitResult(allSucceeded, anySucceeded, outcomeUnknown);
     }
 
-    // abort could to all BE. swallow any failures.
-    private void abortSpecificVersion(ConnectContext ctx, Dictionary 
dictionary, long versionId) {
+    private void abortFailedLoad(ConnectContext ctx, Dictionary dictionary, 
DictionaryStatus oldStatus) {
+        if (ctx.getStatementContext() == null) {
+            dictionary.trySetStatus(oldStatus);
+            return;
+        }
+        boolean partialLoad = 
ctx.getStatementContext().isPartialLoadDictionary();
+        long stagedVersion = partialLoad
+                ? dictionary.getVersion() : dictionary.getVersion() + 1;
+        boolean versionFenced = false;
+        if (!partialLoad && 
ctx.getStatementContext().getUsedBackendsDistributing() != null
+                && 
!ctx.getStatementContext().getUsedBackendsDistributing().isEmpty()) {
+            // Persist the fence before abort so failover cannot reuse a 
version targeted by a delayed RPC.
+            lockRead();
+            try {
+                if (isCurrentDictionary(dictionary)) {
+                    dictionary.increaseVersion();
+                    
Env.getCurrentEnv().getEditLog().logDictionaryIncVersion(dictionary);
+                    versionFenced = true;
+                }
+            } finally {
+                unlockRead();
+            }
+        }
+
+        AbortResult abortResult = abortSpecificVersion(ctx, dictionary, 
stagedVersion);
+        lockRead();
+        try {
+            if (isCurrentDictionary(dictionary)) {
+                if (versionFenced && !abortResult.outcomeUnknown) {
+                    dictionary.decreaseVersion();

Review Comment:
   This rollback is unsafe even when every abort RPC returns OK, because the 
acknowledgement does not fence the asynchronously cancelled sink work. 
`Coordinator.cancelInternal()` sends fragment cancellation asynchronously and 
immediately releases its latches, so a BE can handle the abort before its 
dictionary sink reaches EOS, return OK because no refreshing entry exists, and 
then stage the old attempt at `V+1` afterward. FE now decrements to `V`, so a 
retry reuses `V+1`; if the retry stages fresh data first and the old EOS 
arrives next, `refresh_dict()` unconditionally overwrites the retry's entry and 
the retry commit publishes the old snapshot while FE records the new source 
version. Please keep the generation fenced until remote work is known 
quiescent, or add a BE-side abort tombstone/generation check that rejects a 
late stage, and cover the abort-before-EOS ordering in a lifecycle test.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertIntoTableCommand.java:
##########
@@ -354,6 +411,37 @@ public void beforeComplete(AbstractInsertExecutor 
insertExecutor, StmtExecutor e
         throw new AnalysisException("Insert plan failed. Could not get target 
table lock.");
     }
 
+    private void lockCurrentTarget(ConnectContext ctx, DatabaseIf<?> database, 
TableIf table) {
+        if (!(database instanceof Database) || table instanceof Dictionary) {
+            table.readLock();

Review Comment:
   This fallback locks only the newest external wrapper; it never proves that 
it is the same target generation used to build the plan. External 
database/table cache invalidation rebuilds objects with deterministic 
`Util.genIdByName` IDs, so a same-name/same-schema replacement passes the 
ID/schema checks above. For remote Doris, the first wrapper has already 
produced a `RemoteOlapTable` containing the old partition/tablet graph (only 
its local table ID is rewritten), while this code locks the replacement wrapper 
and the executor/data sink still retain the old graph. A concurrent remote 
drop/recreate can therefore begin the transaction by the replacement name but 
route it with stale tablet metadata. Please validate an external cache 
generation/current object and reject or replan when it changed, and add a 
same-schema external refresh/recreate race test.



##########
fe/fe-core/src/main/java/org/apache/doris/planner/GroupCommitPlanner.java:
##########
@@ -193,26 +194,34 @@ public static void 
executeGroupCommitInsert(ConnectContext ctx, PreparedStatemen
             StatementContext statementContext) throws Exception {
         PrepareCommand prepareCommand = preparedStmtCtx.command;
         InsertIntoTableCommand command = (InsertIntoTableCommand) 
(prepareCommand.getLogicalPlan());
-        OlapTable table = (OlapTable) command.getTable(ctx);
         for (int retry = 0; retry < MAX_RETRY; retry++) {
-            if 
(Env.getCurrentEnv().getGroupCommitManager().isBlock(table.getId())) {
-                String msg = "insert table " + table.getId() + SCHEMA_CHANGE;
-                LOG.info(msg);
-                throw new DdlException(msg);
-            }
             boolean reuse = false;
             GroupCommitPlanner groupCommitPlanner;
-            if (preparedStmtCtx.groupCommitPlanner.isPresent()
-                    && table.getId() == 
preparedStmtCtx.groupCommitPlanner.get().table.getId()
-                    && table.getBaseSchemaVersion() == 
preparedStmtCtx.groupCommitPlanner.get().baseSchemaVersion) {
-                groupCommitPlanner = preparedStmtCtx.groupCommitPlanner.get();
-                reuse = true;
+            OlapTable currentTable = (OlapTable) command.getTable(ctx);
+            checkGroupCommitBlocked(currentTable);

Review Comment:
   This check is only a point-in-time read; prepared group commit never reaches 
`OlapGroupCommitInsertExecutor.beforeExec()` for another admission check. A 
schema change can block the table after this line, let `waitWalFinished()` 
observe zero WAL, and then this method can select a backend and submit its 
cached pre-change plan after that drain decision. Cache misses have the same 
gap because `initPlan(false)` releases its final table lock before 
`GroupCommitPlanner` is built and submitted, and DROP can use that window too. 
Please couple prepared request admission/in-flight accounting atomically with 
the block-and-drain protocol for both cache hits and misses (another unlocked 
lookup/check will still race), and add deterministic races after cache 
validation and after `initPlan(false)` returns.



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


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to