This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.0 by this push:
     new 7db717c3201 branch-4.0: [Chore](utils) make Defer allow throw 
exception #57275 (#57415)
7db717c3201 is described below

commit 7db717c320120688bcb69af293fcbb83b06e2701
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed Oct 29 13:17:02 2025 +0800

    branch-4.0: [Chore](utils) make Defer allow throw exception #57275 (#57415)
    
    Cherry-picked from #57275
    
    Co-authored-by: Pxl <[email protected]>
---
 be/src/util/defer_op.h         | 43 ++++++++++++++++++++++++++--
 be/test/util/defer_op_test.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 105 insertions(+), 2 deletions(-)

diff --git a/be/src/util/defer_op.h b/be/src/util/defer_op.h
index cf404c59203..e7ebffd3c36 100644
--- a/be/src/util/defer_op.h
+++ b/be/src/util/defer_op.h
@@ -17,7 +17,11 @@
 
 #pragma once
 
-#include <functional>
+#include <exception>
+#include <utility>
+
+#include "common/logging.h"
+#include "util/stack_util.h"
 
 namespace doris {
 
@@ -29,12 +33,47 @@ namespace doris {
 // for C++11
 // auto op = [] {};
 // Defer<decltype<op>> (op);
+// Defer runs a closure in its destructor. By default destructors must not
+// let exceptions escape during stack unwinding (that would call
+// std::terminate). This implementation tries to strike a balance:
+// - If the destructor is running while there is an active exception
+//   (std::uncaught_exceptions() > 0), we call the closure but catch and
+//   swallow any exception to avoid terminate.
+// - If there is no active exception, we invoke the closure directly and
+//   allow any exception to propagate to the caller. To permit propagation
+//   we declare the destructor noexcept(false).
 template <class T>
 class Defer {
 public:
     Defer(T& closure) : _closure(closure) {}
     Defer(T&& closure) : _closure(std::move(closure)) {}
-    ~Defer() { _closure(); }
+    // Allow throwing when there is no active exception. If we are currently
+    // unwinding (std::uncaught_exceptions() > 0), swallow exceptions from
+    // the closure to prevent std::terminate.
+    ~Defer() noexcept(false) {
+        if (std::uncaught_exceptions() > 0) {
+            try {
+                _closure();
+            } catch (...) {
+                // swallow: cannot safely rethrow during stack unwind
+                // Log the exception for debugging. Try to get 
std::exception::what()
+                try {
+                    throw;
+                } catch (const std::exception& e) {
+                    LOG(WARNING) << "Exception swallowed in Defer destructor 
during unwind: "
+                                 << e.what() << ", stack trace:\n"
+                                 << get_stack_trace();
+                } catch (...) {
+                    LOG(WARNING) << "Unknown exception swallowed in Defer 
destructor during unwind"
+                                 << ", stack trace:\n"
+                                 << get_stack_trace();
+                }
+            }
+        } else {
+            // No active exception: let any exception escape to caller.
+            _closure();
+        }
+    }
 
 private:
     T _closure;
diff --git a/be/test/util/defer_op_test.cpp b/be/test/util/defer_op_test.cpp
new file mode 100644
index 00000000000..16fe8e97408
--- /dev/null
+++ b/be/test/util/defer_op_test.cpp
@@ -0,0 +1,64 @@
+// 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.
+
+#include "util/defer_op.h"
+
+#include <gtest/gtest.h>
+
+namespace doris {
+
+TEST(DeferOpTest, ThrowEscapesWhenNotUnwinding) {
+    bool threw = false;
+    try {
+        doris::Defer guard([]() { throw std::runtime_error("defer-throws"); });
+        // destructor will run at scope exit and should propagate the exception
+    } catch (const std::runtime_error& e) {
+        threw = true;
+        EXPECT_STREQ(e.what(), "defer-throws");
+    }
+    EXPECT_TRUE(threw);
+}
+
+TEST(DeferOpTest, SwallowDuringUnwind) {
+    // This test ensures that if we're already unwinding, the Defer's exception
+    // does not call std::terminate. To actually run the Defer destructor
+    // during stack unwinding we must create it in a frame that is being
+    // unwound. Creating it inside a catch-handler would be after unwind
+    // completed, so that does not test the intended behavior.
+    bool reached_catch = false;
+
+    auto throwing_func = []() {
+        doris::Defer guard([]() { throw std::runtime_error("inner-defer"); });
+        // throwing here will cause stack unwind while `guard` is destroyed
+        throw std::runtime_error("outer");
+    };
+
+    try {
+        throwing_func();
+    } catch (const std::runtime_error& e) {
+        // We should catch the outer exception here; the inner exception
+        // thrown by the Defer's closure must have been swallowed during
+        // the unwind (otherwise we'd have terminated or a different
+        // exception would propagate).
+        reached_catch = true;
+        EXPECT_STREQ(e.what(), "outer");
+    }
+
+    EXPECT_TRUE(reached_catch);
+}
+
+} // namespace doris


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

Reply via email to