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


##########
be/src/exec/sink/writer/vtablet_writer.cpp:
##########
@@ -2068,6 +2068,9 @@ Status VTabletWriter::close(Status exec_status) {
     DBUG_EXECUTE_IF("VTabletWriter.close.sleep", {
         auto sleep_sec = 
DebugPoints::instance()->get_debug_param_or_default<int32_t>(
                 "VTabletWriter.close.sleep", "sleep_sec", 1);
+        auto token = 
DebugPoints::instance()->get_debug_param_or_default<std::string>(

Review Comment:
   Inside `DBUG_EXECUTE_IF`, `dp` has already been fetched by the macro. 
Calling `DebugPoints::instance()->get_debug_param_or_default()` fetches the 
same point again, and `get_debug_point()` advances/removes `execute`-limited 
points. With this new second lookup, a point such as 
`VTabletWriter.close.sleep?sleep_sec=300&execute=2&token=t` enters the block, 
reads `sleep_sec`, then the token read consumes the limit and returns the 
default empty token/removes the point on the first hit. Please read both params 
from the existing `dp` (`dp->param<int32_t>("sleep_sec", 1)` and 
`dp->param<std::string>("token", "")`), and make the same change in 
`VTabletWriterV2.close.sleep`.



##########
regression-test/suites/load_p0/insert/test_insert_fail_fast_after_be_restart.groovy:
##########
@@ -0,0 +1,106 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import org.apache.doris.regression.suite.ClusterOptions
+
+import java.util.concurrent.TimeUnit
+
+suite("test_insert_fail_fast_after_be_restart", "docker") {
+    def options = new ClusterOptions()
+    options.enableDebugPoints()
+    options.setFeNum(1)
+    options.setBeNum(1)
+    options.cloudMode = false
+
+    docker(options) {
+        def tableName = "test_insert_fail_fast_after_be_restart"
+        GetDebugPoint().clearDebugPointsForAllBEs()
+
+        try {
+            sql "DROP TABLE IF EXISTS ${tableName}"
+            sql """
+                CREATE TABLE ${tableName} (
+                    k BIGINT NOT NULL,
+                    v BIGINT NOT NULL
+                )
+                DUPLICATE KEY(k)
+                DISTRIBUTED BY HASH(k) BUCKETS 1
+                PROPERTIES (
+                    "replication_num" = "1"
+                )
+            """
+
+            // Hold the load fragment before it can report completion to FE. 
Restarting the BE
+            // at this point drops that report while the replacement process 
quickly becomes alive.
+            def debugPointToken = "insert_restart_${System.nanoTime()}"
+            def debugPointParams = [sleep_sec: 300, token: debugPointToken]
+            
GetDebugPoint().enableDebugPointForAllBEs("VTabletWriter.close.sleep", 
debugPointParams)
+            
GetDebugPoint().enableDebugPointForAllBEs("VTabletWriterV2.close.sleep", 
debugPointParams)
+
+            def insertFuture = thread {
+                sql "SET enable_nereids_planner = true"
+                sql "SET enable_fallback_to_original_planner = false"
+                sql "SET insert_timeout = 300"
+                try {
+                    sql """
+                        INSERT INTO ${tableName}
+                        SELECT number, number FROM numbers("number" = "1024")
+                    """
+                    return null
+                } catch (Throwable t) {
+                    logger.info("INSERT failed after BE restart as expected: 
${t.message}")
+                    return t.message
+                }
+            }
+
+            def beLogFile = new File(cluster.getBeByIndex(1).getLogFilePath())
+            def debugPointMarkers = [
+                    "hit debug point VTabletWriter.close.sleep, 
token=${debugPointToken}",
+                    "hit debug point VTabletWriterV2.close.sleep, 
token=${debugPointToken}"
+            ]
+            def waitDeadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(60)
+            def debugPointHit = false
+            while (System.nanoTime() < waitDeadline) {
+                if (beLogFile.exists()) {
+                    def beLog = beLogFile.text
+                    if (debugPointMarkers.any { beLog.contains(it) }) {
+                        debugPointHit = true
+                        break
+                    }
+                }
+                sleep(200)
+            }
+            assertTrue(debugPointHit, "INSERT did not reach the writer close 
debug point")
+
+            cluster.restartBackends()
+
+            // LoadProcessor checks backend health every 30 seconds. The 
restarted BE is alive,
+            // so this only finishes before insert_timeout when FE also 
compares process epochs.
+            def errorMessage = insertFuture.get(60, TimeUnit.SECONDS)

Review Comment:
   The tokenized log wait fixes the earlier race where the restart could happen 
before close reached the debug point, but the test can still fail through the 
old backend-down path instead of proving the new epoch check. 
`restartBackends()` may wait up to 120s, while `LoadProcessor.join()` polls 
health every 30s and `getBackendHealthStatus()` checks `!backend.isAlive()` 
before comparing process epochs. On a slow docker restart, the insert can 
return `backend 1 is down`, so these assertions fail without exercising the 
restarted-but-alive epoch branch. Please also synchronize until FE has observed 
the restarted BE as alive with a changed positive epoch before expecting the 
epoch error, or avoid asserting that exact message as the proof.



##########
regression-test/suites/load_p0/insert/test_insert_fail_fast_after_be_restart.groovy:
##########
@@ -0,0 +1,106 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import org.apache.doris.regression.suite.ClusterOptions
+
+import java.util.concurrent.TimeUnit
+
+suite("test_insert_fail_fast_after_be_restart", "docker") {
+    def options = new ClusterOptions()
+    options.enableDebugPoints()
+    options.setFeNum(1)
+    options.setBeNum(1)
+    options.cloudMode = false
+
+    docker(options) {
+        def tableName = "test_insert_fail_fast_after_be_restart"
+        GetDebugPoint().clearDebugPointsForAllBEs()
+
+        try {
+            sql "DROP TABLE IF EXISTS ${tableName}"
+            sql """
+                CREATE TABLE ${tableName} (
+                    k BIGINT NOT NULL,
+                    v BIGINT NOT NULL
+                )
+                DUPLICATE KEY(k)
+                DISTRIBUTED BY HASH(k) BUCKETS 1
+                PROPERTIES (
+                    "replication_num" = "1"
+                )
+            """
+
+            // Hold the load fragment before it can report completion to FE. 
Restarting the BE
+            // at this point drops that report while the replacement process 
quickly becomes alive.
+            def debugPointToken = "insert_restart_${System.nanoTime()}"
+            def debugPointParams = [sleep_sec: 300, token: debugPointToken]
+            
GetDebugPoint().enableDebugPointForAllBEs("VTabletWriter.close.sleep", 
debugPointParams)
+            
GetDebugPoint().enableDebugPointForAllBEs("VTabletWriterV2.close.sleep", 
debugPointParams)
+
+            def insertFuture = thread {
+                sql "SET enable_nereids_planner = true"
+                sql "SET enable_fallback_to_original_planner = false"
+                sql "SET insert_timeout = 300"
+                try {
+                    sql """
+                        INSERT INTO ${tableName}
+                        SELECT number, number FROM numbers("number" = "1024")
+                    """
+                    return null
+                } catch (Throwable t) {
+                    logger.info("INSERT failed after BE restart as expected: 
${t.message}")
+                    return t.message
+                }
+            }
+
+            def beLogFile = new File(cluster.getBeByIndex(1).getLogFilePath())
+            def debugPointMarkers = [
+                    "hit debug point VTabletWriter.close.sleep, 
token=${debugPointToken}",
+                    "hit debug point VTabletWriterV2.close.sleep, 
token=${debugPointToken}"
+            ]
+            def waitDeadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(60)
+            def debugPointHit = false
+            while (System.nanoTime() < waitDeadline) {
+                if (beLogFile.exists()) {
+                    def beLog = beLogFile.text
+                    if (debugPointMarkers.any { beLog.contains(it) }) {
+                        debugPointHit = true
+                        break
+                    }
+                }
+                sleep(200)
+            }
+            assertTrue(debugPointHit, "INSERT did not reach the writer close 
debug point")
+
+            cluster.restartBackends()
+
+            // LoadProcessor checks backend health every 30 seconds. The 
restarted BE is alive,
+            // so this only finishes before insert_timeout when FE also 
compares process epochs.
+            def errorMessage = insertFuture.get(60, TimeUnit.SECONDS)
+            assertNotNull(errorMessage, "INSERT should fail after its BE 
process restarts")
+            assertTrue(errorMessage.contains("process epoch changed"),
+                    "unexpected INSERT error after BE restart: 
${errorMessage}")
+            assertTrue(errorMessage.contains("backend restarted"),
+                    "INSERT error should explain that the backend restarted: 
${errorMessage}")
+
+            assertEquals(0, sql("SELECT COUNT(*) FROM ${tableName}")[0][0] as 
int)
+        } finally {
+            GetDebugPoint().clearDebugPointsForAllBEs()
+            try_sql "DROP TABLE IF EXISTS ${tableName}"

Review Comment:
   Please keep only the pre-test `DROP TABLE IF EXISTS` and leave the table 
after the test for debugging, per the regression-test standard. Since this is 
also a single ordinary test table, the same rule asks us to hardcode 
`test_insert_fail_fast_after_be_restart` in the SQL instead of routing it 
through `def tableName`.



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