yujun777 commented on code in PR #68662:
URL: https://github.com/apache/doris/pull/68662#discussion_r4142375683


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertOverwriteTableCommand.java:
##########
@@ -386,12 +440,87 @@ private static void 
failBetweenTheTwoHalvesOfAnOverwrite(TableIf targetTable) th
         throw new UserException("debug point: " + 
DEBUG_POINT_FAIL_BETWEEN_THE_HALVES_OF_AN_OVERWRITE);
     }
 
+    /**
+     * Cancels this overwrite when the debug point names the table it targets; 
see the constants above.
+     * Nothing here decides what a cancelled overwrite means -- the call sites 
do, and they differ: the ones
+     * before the rows are durable take the statement back, the one after them 
does not.
+     *
+     * <p>One lookup, because a point is consumed by the lookup that reads it: 
reading it twice with
+     * {@code execute=1} armed would have the first read spend the allowance 
and the point be gone before the
+     * second, which would silently leave the overwrite uncancelled.
+     */
+    private void cancelTheOverwriteAt(String debugPointName, TableIf 
targetTable) {
+        if 
(!targetTable.getName().equals(DebugPointUtil.getDebugParamOrDefault(
+                debugPointName, "table_name", ""))) {
+            return;
+        }
+        LOG.info("debug point {} cancels the overwrite of {}", debugPointName, 
targetTable.getName());
+        cancel();
+    }
+
+    /**
+     * Publishes this overwrite by swapping the temp partitions in, with a 
last look at the cancellation flag
+     * taken under the lock the swap contends for.
+     *
+     * <p>{@link #run} reads the flag before the swap is issued, and the swap 
then waits for the table's write
+     * lock, so a cancellation that arrives during that wait is the one place 
a check before the swap cannot
+     * see. Reading it again here costs nothing and is where the wait happens: 
for a cancellation with nothing
+     * committed there is nothing durable to publish, so refusing to swap 
costs the statement and leaves the
+     * rows the client asked to keep -- while a swap that went ahead would 
replace them with empty partitions.
+     *
+     * <p>Only a local table is wrapped: a remote table swaps on the frontend 
that owns it, where this lock
+     * says nothing.
+     */
+    private void publishTheOverwrite(TableIf targetTable, List<String> 
partitionNames,
+            List<String> tempPartitionNames, InsertCommandContext insertCtx, 
ConnectContext ctx) throws UserException {
+        if (!(targetTable instanceof OlapTable) || targetTable instanceof 
RemoteOlapTable) {
+            InsertOverwriteUtil.replacePartition(targetTable, partitionNames, 
tempPartitionNames,
+                    isForceDropPartition());
+            return;
+        }

Review Comment:
   Fixed. The wrapper's `writeLockIfExist()` early return skipped the swap 
while `run` went on to `taskSuccess`, and that was a change from what the 
utility did: for a dropped table the utility returned from inside a `try` whose 
`finally { writeUnlock(); }` then unlocked a lock that return never took, so 
the call raised and the statement failed. `publishTheOverwrite` now raises 
deliberately, with a message naming the table, so the outcome is the one a 
client whose swap never happened is owed -- the catch drops the temp partitions 
of the dropped table and takes the task back.
   
   One note on the other route, which I left alone rather than widen this PR: 
the auto-detect swap goes through `InsertOverwriteManager.taskGroupSuccess` 
into the same utility, so a target dropped under it fails the statement the 
same way, with that same unlock-after-early-return shape.
   



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/InsertOverwriteTableCommand.java:
##########
@@ -386,12 +440,87 @@ private static void 
failBetweenTheTwoHalvesOfAnOverwrite(TableIf targetTable) th
         throw new UserException("debug point: " + 
DEBUG_POINT_FAIL_BETWEEN_THE_HALVES_OF_AN_OVERWRITE);
     }
 
+    /**
+     * Cancels this overwrite when the debug point names the table it targets; 
see the constants above.
+     * Nothing here decides what a cancelled overwrite means -- the call sites 
do, and they differ: the ones
+     * before the rows are durable take the statement back, the one after them 
does not.
+     *
+     * <p>One lookup, because a point is consumed by the lookup that reads it: 
reading it twice with
+     * {@code execute=1} armed would have the first read spend the allowance 
and the point be gone before the
+     * second, which would silently leave the overwrite uncancelled.
+     */
+    private void cancelTheOverwriteAt(String debugPointName, TableIf 
targetTable) {
+        if 
(!targetTable.getName().equals(DebugPointUtil.getDebugParamOrDefault(
+                debugPointName, "table_name", ""))) {
+            return;
+        }
+        LOG.info("debug point {} cancels the overwrite of {}", debugPointName, 
targetTable.getName());
+        cancel();
+    }
+
+    /**
+     * Publishes this overwrite by swapping the temp partitions in, with a 
last look at the cancellation flag
+     * taken under the lock the swap contends for.
+     *
+     * <p>{@link #run} reads the flag before the swap is issued, and the swap 
then waits for the table's write
+     * lock, so a cancellation that arrives during that wait is the one place 
a check before the swap cannot
+     * see. Reading it again here costs nothing and is where the wait happens: 
for a cancellation with nothing
+     * committed there is nothing durable to publish, so refusing to swap 
costs the statement and leaves the
+     * rows the client asked to keep -- while a swap that went ahead would 
replace them with empty partitions.
+     *
+     * <p>Only a local table is wrapped: a remote table swaps on the frontend 
that owns it, where this lock
+     * says nothing.
+     */
+    private void publishTheOverwrite(TableIf targetTable, List<String> 
partitionNames,

Review Comment:
   Real gap, but the decision it asks for is the owning frontend's, so I have 
not taken it here.
   
   The cancellation lives on this FE (the client's session), and 
`replacePartitions` carries no cancellation state, so the local last-chance 
check added in this PR cannot see a KILL that arrives while the owner waits for 
its table write lock in `replacePartitionsImpl` -- the check on this side runs 
before the RPC, and the wait happens after it. Covering it means either 
conveying the verdict (cancelled with nothing committed) in the replacement RPC 
so the owner refuses under its own lock, or moving the whole decision to the 
owner; both are remote-Doris protocol changes that belong with that feature 
rather than with the local overwrite outcome handling this PR is about.
   
   The local half of the same window is covered (`publishTheOverwrite` reads 
the flag under the lock), and the auto-detect route is not reachable with 
nothing committed on this side, as discussed in the earlier thread.
   



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/RemoteOlapInsertExecutor.java:
##########
@@ -208,6 +208,7 @@ protected void onComplete() throws UserException {
                 } else {
                     txnStatus = TransactionStatus.COMMITTED;
                 }
+                markCommitted();
                 LOG.info("commit remote txn success, catalog={}, dbId={}, 
txnId={}, status={}",

Review Comment:
   Agreed that the state is possible, and agreed that the caller cannot tell it 
apart from an aborted commit: after `masterCallWithRetry` loses the reply, this 
FE has no way to know whether the owner committed, and `onFail`'s abort cannot 
take a committed remote transaction back.
   
   I would not fix it inside this PR, for the reason the comment itself states 
as an alternative: "keeping the partitions while the result is unknown" is not 
a stable end state either. The temp partitions it would keep are unpublished, 
they belong to a task the overwrite manager still holds, and the next 
`allTaskFail` (a master transfer, a restart) drops them -- so the loss is 
deferred rather than prevented, with an extra uncertain state in between. What 
prevents it is the property this PR's scope note names: the publication being a 
committed action of the insert transaction (or the consumption position moving 
with the publication), so that "committed" and "published" cannot disagree. 
That is a change to the transaction and journal path, not to this command.
   
   Both remote findings are worth tracking; I would file them as their own 
issue with the reproduction (a lost commit reply / a KILL during the owner's 
swap lock wait) rather than growing this PR.
   



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/insert/OlapInsertExecutor.java:
##########
@@ -240,10 +240,14 @@ protected void onComplete() throws UserException {
                 ctx.getSessionVariable().getInsertVisibleTimeoutMs(), 
txnCommitAttachment,
                 streamUpdateInfos)) {
             txnStatus = TransactionStatus.VISIBLE;
+            markCommitted();
         } else {
             // Keep the committed status so load accounting and insert result 
bookkeeping stay aligned.
             txnStatus = TransactionStatus.COMMITTED;
             publishTimedOutAfterCommit = true;
+            // Committed, visible later: the rows are durable even though the 
session's visibility-timeout
+            // mode may report the timeout as an error. See 
InsertCommandContext#setCommitted.
+            markCommitted();
         }

Review Comment:
   Same family as the remote-commit case above, and it needs the same kind of 
decision, which is why I have not taken it here.
   
   Two things make it a cloud transaction-semantics question rather than an 
overwrite one. First, the outcome is not decidable locally: after 
`KV_TXN_COMMIT_ERR` the meta service may or may not have applied the commit -- 
`CloudInternalCatalog` already treats that code specially elsewhere -- so 
choosing between "publish" and "drop" needs a state query against the meta 
service, or a policy for uncertain commits, and it applies to every cloud load 
that reaches that path, not only to overwrites. Second, dropping the partitions 
on an uncertain commit is wrong in the loss direction and keeping them is wrong 
in the stuck direction: an unpublished leftover has no owner that will ever 
publish it, since the overwrite that would have is gone. The durable answer is 
the same one this PR's scope note names -- make the publication and the commit 
one event (or carry the consumption position with the publication) -- and until 
that exists, an uncertain commit has no correct local resolution.
   
   Happy to file this alongside the remote findings as a separate issue if that 
is useful.
   



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