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]