整体错误处理添加

This commit is contained in:
2026-09-02 16:33:09 +08:00
parent 787d35ec33
commit 4c77135783
14 changed files with 89 additions and 38 deletions
+2
View File
@@ -104,6 +104,8 @@ exception
`fast_fail` 只改变 Unknown Failure 的处置方式, **不得改变 Known Result / Unknown Failure 的分类。**
Unknown Failure Policy 是进程级唯一配置,必须在启动异步工作前设置,任务在途时不得切换。实际失败点只向统一处理函数提交原始 `std::exception_ptr`,不得再逐次传入策略或在模块内保存策略副本。
## 4. 第三方库处理规则
第三方库自身使用返回码还是异常,不决定本系统的处理方式。
@@ -1,8 +1,15 @@
#include "Failure_Policy.hpp"
#include <atomic>
namespace aethera {
[[noreturn]] void Failure_Policy::handle_unknown_failure(Unknown_Failure_Policy policy, std::exception_ptr failure) {
if (policy == Unknown_Failure_Policy::exception && failure) std::rethrow_exception(failure);
namespace {
std::atomic<Unknown_Failure_Policy> unknown_failure_policy{Unknown_Failure_Policy::fast_fail};
}
void Failure_Policy::configure(Unknown_Failure_Policy policy) noexcept {
unknown_failure_policy.store(policy, std::memory_order_relaxed);
}
[[noreturn]] void Failure_Policy::handle_unknown_failure(std::exception_ptr failure) {
if (unknown_failure_policy.load(std::memory_order_relaxed) == Unknown_Failure_Policy::exception && failure) std::rethrow_exception(failure);
std::terminate();
}
} // namespace aethera
@@ -7,6 +7,9 @@ enum struct Unknown_Failure_Policy : std::uint8_t {
exception /* 原样重抛 Unknown Failure,由上层隔离边界处理。 */
};
struct Failure_Policy final {
[[noreturn]] static void handle_unknown_failure(Unknown_Failure_Policy policy, std::exception_ptr failure);
/* 设置进程级策略;必须在启动异步工作前完成,不得在任务在途时切换。 */
static void configure(Unknown_Failure_Policy policy) noexcept;
/* 按当前进程级策略处理原始 Unknown Failure;空 failure 无法传播,只能终止。 */
[[noreturn]] static void handle_unknown_failure(std::exception_ptr failure);
};
} // namespace aethera
+5 -4
View File
@@ -1,4 +1,5 @@
#pragma once
#include "Error_handling_specification/Failure_Policy.hpp"
#include <algorithm>
#include <concurrentqueue.h>
#include <deque>
@@ -72,7 +73,7 @@ template <typename Node_Facade> Dag<Node_Facade>::Dag(Dag&&) noexcept = default;
template <typename Node_Facade> auto Dag<Node_Facade>::operator=(Dag&&) noexcept -> Dag& = default;
template <typename Node_Facade> template <typename Callback> void Dag<Node_Facade>::for_each_topological(Callback&& callback) {
const auto order = detail::dag_topological_indices(d->nodes, d->edges);
if (!order) throw std::logic_error("authoritative DAG is invalid");
if (!order) Failure_Policy::handle_unknown_failure(std::make_exception_ptr(std::logic_error("authoritative DAG is invalid")));
for (const auto index : *order) std::invoke(callback, d->nodes[index].id, d->nodes[index].object);
}
template <typename Node_Facade> template <typename Callback> void Dag<Node_Facade>::for_each_dependency(Callback&& callback) const { for (const auto& edge : d->edges) std::invoke(callback, edge.target, edge.source); }
@@ -144,7 +145,7 @@ template <typename Node_Facade> void Owned_Dag<Node_Facade>::Internal::advance()
}
template <typename Node_Facade> void Owned_Dag<Node_Facade>::set(Graph graph) {
typename Internal::Private::Deferred_Command command = [graph = std::move(graph)](Graph& current) mutable { current = std::move(graph); };
if (!internal.d->queue.enqueue(std::move(command))) throw std::bad_alloc{};
if (!internal.d->queue.enqueue(std::move(command))) Failure_Policy::handle_unknown_failure(std::make_exception_ptr(std::bad_alloc{}));
}
template <typename Node_Facade> void Owned_Dag<Node_Facade>::set(Edit edit, Edit_Completion completion) {
typename Internal::Private::Deferred_Command command = [edit = std::move(edit), completion = std::move(completion)](Graph& current) mutable {
@@ -255,10 +256,10 @@ template <typename Node_Facade> void Owned_Dag<Node_Facade>::set(Edit edit, Edit
transaction_open = false;
} catch (...) {
if (transaction_open) rollback();
throw;
Failure_Policy::handle_unknown_failure(std::current_exception());
}
if (completion) completion({});
};
if (!internal.d->queue.enqueue(std::move(command))) throw std::bad_alloc{};
if (!internal.d->queue.enqueue(std::move(command))) Failure_Policy::handle_unknown_failure(std::make_exception_ptr(std::bad_alloc{}));
}
} // namespace aethera
@@ -1,4 +1,5 @@
#include "Throttled_Latest_Only.hpp"
#include "Error_handling_specification/Failure_Policy.hpp"
#include <algorithm>
#include <concepts>
#include <exception>
@@ -35,7 +36,7 @@ auto Throttled_Latest_only::Private::start() -> std::expected<void, Start_Result
}
catch (...) {
rollback_start();
throw;
Failure_Policy::handle_unknown_failure(std::current_exception());
}
timer_started(scheduled_timer_id);
return {};
@@ -88,7 +89,7 @@ auto Throttled_Latest_only::Private::prepare_start() -> std::expected<std::chron
}
catch (...) {
rollback_start();
throw;
Failure_Policy::handle_unknown_failure(std::current_exception());
}
}
void Throttled_Latest_only::Private::rollback_start() {
@@ -145,7 +146,7 @@ auto Throttled_Latest_only::Private::stop(Stop_Completion completion) -> std::ex
}
catch (...) {
cancel_failed();
throw;
Failure_Policy::handle_unknown_failure(std::current_exception());
}
return {};
}
@@ -214,7 +215,7 @@ void Throttled_Latest_only::Private::frame_due() {
const auto result = scene->render(frames[selected_index].frame, [this, selected_index](proxy<scene::Frame>& completed_frame, std::exception_ptr failure) {
if (failure) {
abandon_render(selected_index);
std::rethrow_exception(failure);
Failure_Policy::handle_unknown_failure(failure);
}
rendered(selected_index, completed_frame);
});
@@ -223,7 +224,7 @@ void Throttled_Latest_only::Private::frame_due() {
}
catch (...) {
abandon_render(selected_index);
throw;
Failure_Policy::handle_unknown_failure(std::current_exception());
}
}
void Throttled_Latest_only::Private::record_render_submission(scene::Render_Result result) {
@@ -296,7 +297,7 @@ void Throttled_Latest_only::Private::dispatch_send(std::size_t slot_index) {
}
catch (...) {
abandon_send(slot_index);
throw;
Failure_Policy::handle_unknown_failure(std::current_exception());
}
}
void Throttled_Latest_only::Private::sent(std::size_t slot_index, proxy<scene::Frame>& completed_frame) {
@@ -1,4 +1,5 @@
#include "frame_policy/src/Throttled_Latest_Only.hpp"
#include "Error_handling_specification/Failure_Policy.hpp"
#include "statistics/Sliding_Statistics.hpp"
#include <gtest/gtest.h>
@@ -458,6 +459,7 @@ TEST(Throttled_Latest_Only, Reports_Invalid_Configuration) {
}
TEST(Throttled_Latest_Only, Render_Failure_Releases_Slot_Before_It_Escapes) {
aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::exception);
auto control = std::make_shared<Frame_Policy_Test_Control>();
auto policy = make_policy(control, 60.0);
ASSERT_TRUE(policy->start());
@@ -477,6 +479,7 @@ TEST(Throttled_Latest_Only, Render_Failure_Releases_Slot_Before_It_Escapes) {
}
TEST(Throttled_Latest_Only, Synchronous_Completions_And_Failure_Restore_Slot) {
aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::exception);
auto control = std::make_shared<Frame_Policy_Test_Control>();
control->complete_render_synchronously = true;
control->throw_from_send = true;
@@ -1,5 +1,6 @@
#include "Task_Graph.hpp"
#include "detail/Taskflow_Execution.ipp"
#include "Error_handling_specification/Failure_Policy.hpp"
#include <algorithm>
#include <atomic>
#include <exception>
@@ -151,7 +152,7 @@ detail::Task_Graph_Execution::~Task_Graph_Execution() {
}
}
std::shared_ptr<detail::Task_Graph_Execution> detail::Task_Graph_Execution::create(Task_Graph& graph) {
if (!graph.d) throw std::invalid_argument("Task graph is moved from");
if (!graph.d) Failure_Policy::handle_unknown_failure(std::make_exception_ptr(std::invalid_argument("Task graph is moved from")));
auto execution = std::make_shared<Task_Graph_Execution>(Construction_Key{});
execution->root = graph.d;
std::unordered_set<Task_Graph::Private*> visited;
@@ -228,7 +229,7 @@ auto Task_Graph::add(std::string task_name, std::function<void()> work) -> std::
}
catch (...) {
private_data.get().record_failure(std::current_exception());
throw;
Failure_Policy::handle_unknown_failure(std::current_exception());
}
});
const auto node_id = d->unique_node_id(task_name);
@@ -247,7 +248,7 @@ auto Task_Graph::add_condition(std::string task_name, std::function<int()> work)
}
catch (...) {
private_data.get().record_failure(std::current_exception());
throw;
Failure_Policy::handle_unknown_failure(std::current_exception());
}
});
const auto node_id = d->unique_node_id(task_name);
@@ -11,18 +11,22 @@
#include <utility>
#include <vector>
namespace aethera {
detail::Taskflow_Completion_Boundary::Taskflow_Completion_Boundary(Unknown_Failure_Policy selected_failure_policy, Taskflow_Exception_Boundary selected_exception_boundary) : failure_policy(selected_failure_policy), exception_boundary(std::move(selected_exception_boundary)) {}
detail::Taskflow_Completion_Boundary::Taskflow_Completion_Boundary(Taskflow_Exception_Boundary selected_exception_boundary) : exception_boundary(std::move(selected_exception_boundary)) {}
void detail::Taskflow_Completion_Boundary::invoke(Taskflow_Completion& completion, std::exception_ptr failure) const noexcept {
try {
completion(std::move(failure));
}
catch (...) {
if (failure_policy == Unknown_Failure_Policy::fast_fail) Failure_Policy::handle_unknown_failure(failure_policy, std::current_exception());
try {
exception_boundary(std::current_exception());
Failure_Policy::handle_unknown_failure(std::current_exception());
}
catch (...) {
std::terminate();
try {
exception_boundary(std::current_exception());
}
catch (...) {
std::terminate();
}
}
}
}
@@ -179,8 +183,9 @@ public:
? Initialize_Task_Runtime_Result::already_initialized
: Initialize_Task_Runtime_Result::initialization_in_progress;
}
Failure_Policy::configure(failure_policy);
try {
auto next_completion_boundary = std::make_unique<detail::Taskflow_Completion_Boundary>(failure_policy, std::move(exception_boundary));
auto next_completion_boundary = std::make_unique<detail::Taskflow_Completion_Boundary>(std::move(exception_boundary));
auto next_executor = std::make_unique<tf::Executor>(workers);
(void)next_executor->make_observer<Task_Observer>();
completion_boundary = std::move(next_completion_boundary);
@@ -191,7 +196,7 @@ public:
phase.store(
Runtime_Phase::uninitialized,
std::memory_order_release);
throw;
Failure_Policy::handle_unknown_failure(std::current_exception());
}
return Initialize_Task_Runtime_Result::initialized;
}
@@ -1,5 +1,6 @@
#include "Taskflow_Observation.hpp"
#include "detail/Taskflow_Execution.ipp"
#include "Error_handling_specification/Failure_Policy.hpp"
#include <atomic>
#include <chrono>
#include <expected>
@@ -93,7 +94,7 @@ std::expected<Taskflow_Execution_Trace, Take_Taskflow_Observation_Result> Taskfl
: Take_Taskflow_Observation_Result::recording};
}
if (auto failure = d->take_failure()) {
std::rethrow_exception(failure);
Failure_Policy::handle_unknown_failure(failure);
}
auto result = std::move(d->trace);
d->worker_states.clear();
@@ -149,7 +150,7 @@ std::expected<detail::Taskflow_Observation_Execution,
observation.d->phase.store(
Observation_Phase::requested,
std::memory_order_release);
throw;
Failure_Policy::handle_unknown_failure(std::current_exception());
}
return Taskflow_Observation_Execution{std::move(observation.d)};
}
@@ -19,10 +19,9 @@ class Worker;
namespace aethera::detail {
/* Process-lifetime worker boundary. The handler is immutable after runtime initialization and executes on the completing Taskflow worker. */
struct Taskflow_Completion_Boundary final {
Taskflow_Completion_Boundary(Unknown_Failure_Policy failure_policy, Taskflow_Exception_Boundary exception_boundary);
explicit Taskflow_Completion_Boundary(Taskflow_Exception_Boundary exception_boundary);
void invoke(Taskflow_Completion& completion, std::exception_ptr failure) const noexcept;
private:
Unknown_Failure_Policy failure_policy;
Taskflow_Exception_Boundary exception_boundary;
};
/* 单帧执行期间持有根图及全部子图;调用方保证同一张图不并发执行或重建。 */
@@ -169,9 +169,10 @@ TEST(Task_Runtime, Validates_Optional_Exception_Boundary) {
}
TEST(Taskflow_Completion_Boundary, Exception_Policy_Forwards_Completion_Failure) {
aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::exception);
std::exception_ptr forwarded;
aethera::detail::Taskflow_Completion_Boundary boundary(aethera::Unknown_Failure_Policy::exception, [&forwarded](std::exception_ptr failure) { forwarded = std::move(failure); });
aethera::Taskflow_Completion completion = [](std::exception_ptr failure) { std::rethrow_exception(failure); };
aethera::detail::Taskflow_Completion_Boundary boundary([&forwarded](std::exception_ptr failure) { forwarded = std::move(failure); });
aethera::Taskflow_Completion completion = [](std::exception_ptr failure) { aethera::Failure_Policy::handle_unknown_failure(failure); };
boundary.invoke(completion, std::make_exception_ptr(std::runtime_error("task failure")));
ASSERT_TRUE(forwarded);
EXPECT_THROW(std::rethrow_exception(forwarded), std::runtime_error);
@@ -179,15 +180,15 @@ TEST(Taskflow_Completion_Boundary, Exception_Policy_Forwards_Completion_Failure)
#if GTEST_HAS_DEATH_TEST
TEST(Taskflow_Completion_Boundary, Fast_Fail_Policy_Terminates_On_Completion_Failure) {
aethera::detail::Taskflow_Completion_Boundary boundary(aethera::Unknown_Failure_Policy::fast_fail, {});
aethera::detail::Taskflow_Completion_Boundary boundary({});
aethera::Taskflow_Completion completion = [](std::exception_ptr) { throw std::runtime_error("completion failure"); };
EXPECT_DEATH({ boundary.invoke(completion, {}); }, "");
EXPECT_DEATH({ aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::fast_fail); boundary.invoke(completion, {}); }, "");
}
TEST(Taskflow_Completion_Boundary, Exception_Policy_Terminates_If_The_Receiver_Throws) {
aethera::detail::Taskflow_Completion_Boundary boundary(aethera::Unknown_Failure_Policy::exception, [](std::exception_ptr) { throw std::runtime_error("receiver failure"); });
aethera::detail::Taskflow_Completion_Boundary boundary([](std::exception_ptr) { throw std::runtime_error("receiver failure"); });
aethera::Taskflow_Completion completion = [](std::exception_ptr) { throw std::runtime_error("completion failure"); };
EXPECT_DEATH({ boundary.invoke(completion, {}); }, "");
EXPECT_DEATH({ aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::exception); boundary.invoke(completion, {}); }, "");
}
#endif
@@ -1,4 +1,5 @@
#include "Timer_Scheduler.hpp"
#include "Error_handling_specification/Failure_Policy.hpp"
#include "timer-wheel.h"
@@ -41,7 +42,7 @@ struct Timer_Scheduler::Impl {
explicit Impl(proxy<Timer_Time_Source> time_source) :
time_source(std::move(time_source)) {
if (!this->time_source) {
throw std::invalid_argument("Timer time source is unavailable");
Failure_Policy::handle_unknown_failure(std::make_exception_ptr(std::invalid_argument("Timer time source is unavailable")));
}
}
@@ -4,9 +4,10 @@
namespace {
TEST(Failure_Policy, Exception_Rethrows_The_Original_Unknown_Failure) {
aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::exception);
const auto failure = std::make_exception_ptr(std::runtime_error("unknown failure"));
try {
aethera::Failure_Policy::handle_unknown_failure(aethera::Unknown_Failure_Policy::exception, failure);
aethera::Failure_Policy::handle_unknown_failure(failure);
} catch (const std::runtime_error& error) {
EXPECT_STREQ(error.what(), "unknown failure");
return;
@@ -16,10 +17,10 @@ TEST(Failure_Policy, Exception_Rethrows_The_Original_Unknown_Failure) {
TEST(Failure_Policy, Fast_Fail_Terminates) {
const auto failure = std::make_exception_ptr(std::runtime_error("unknown failure"));
EXPECT_DEATH(aethera::Failure_Policy::handle_unknown_failure(aethera::Unknown_Failure_Policy::fast_fail, failure), "");
EXPECT_DEATH({ aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::fast_fail); aethera::Failure_Policy::handle_unknown_failure(failure); }, "");
}
TEST(Failure_Policy, Missing_Unknown_Failure_Terminates) {
EXPECT_DEATH(aethera::Failure_Policy::handle_unknown_failure(aethera::Unknown_Failure_Policy::exception, {}), "");
EXPECT_DEATH({ aethera::Failure_Policy::configure(aethera::Unknown_Failure_Policy::exception); aethera::Failure_Policy::handle_unknown_failure({}); }, "");
}
} // namespace
+27 -2
View File
@@ -4,15 +4,22 @@
#include <vector>
namespace {
PRO_DEF_MEM_DISPATCH(Test_Model_Private_Read, private_value);
struct Test_Model_Private_Facade : aethera::facade_builder::add_convention<Test_Model_Private_Read, int() const>::support_relocation<pro::constraint_level::nothrow>::build {};
struct Test_Model_Private_Facade : aethera::facade_builder
::add_convention<Test_Model_Private_Read, int() const>
::support_relocation<pro::constraint_level::nothrow>
::build {};
PRO_DEF_MEM_DISPATCH(Other_Test_Model_Private_Read, other_private_value);
struct Other_Test_Model_Private_Facade : aethera::facade_builder::add_convention<Other_Test_Model_Private_Read, int() const>::support_relocation<pro::constraint_level::nothrow>::build {};
struct Other_Test_Model_Private_Facade : aethera::facade_builder
::add_convention<Other_Test_Model_Private_Read, int() const>
::support_relocation<pro::constraint_level::nothrow>
::build {};
struct Number_List_Tag {
template <typename Layer> using Value = typename Layer::Numbers;
};
struct Test_Model_Base : aethera::Model_Layer<aethera::Root, aethera::Storage_Registration<aethera::Prop_Tag, aethera::Import_Struct_With_Dirty>, aethera::Storage_Registration<aethera::State_Tag, aethera::Export_Struct>, aethera::Storage_Registration<Number_List_Tag, aethera::Import_List>> {
struct Prop : Prev<aethera::Prop_Tag> {
int base_number{};
int same_name{};
};
struct State : Prev<aethera::State_Tag> {
int base_state{};
@@ -36,6 +43,7 @@ struct Test_Model : aethera::Def<Test_Model, Test_Model_Middle> {
struct Prop : Prev<aethera::Prop_Tag> {
int number{};
int other{};
int same_name{};
};
struct State : Prev<aethera::State_Tag> {
int state{};
@@ -114,6 +122,23 @@ TEST(Model, Registered_Struct_Set_Publishes_To_The_Same_Tagged_Storage) {
properties.internal.advance();
EXPECT_EQ(properties.internal.use()->number, 13);
}
TEST(Model, Same_Name_Properties_In_Base_And_Derived_Layers_Are_Set_Independently) {
Test_Model::Builder builder;
builder
.set<aethera::Import_Struct_With_Dirty, aethera::Prop_Tag>(&Test_Model_Base::Prop::same_name, 29)
.set<aethera::Import_Struct_With_Dirty, aethera::Prop_Tag>(&Test_Model::Prop::same_name, 31);
auto model = builder.build();
const auto* properties = model->d->get<aethera::Prop_Tag>().internal.use();
EXPECT_EQ(static_cast<const Test_Model_Base::Prop&>(*properties).same_name, 29);
EXPECT_EQ(properties->same_name, 31);
model->set<aethera::Import_Struct_With_Dirty, aethera::Prop_Tag>(&Test_Model_Base::Prop::same_name, 37);
model->set<aethera::Import_Struct_With_Dirty, aethera::Prop_Tag>(&Test_Model::Prop::same_name, 41);
auto& storage = model->d->get<aethera::Prop_Tag>();
storage.internal.advance();
properties = storage.internal.use();
EXPECT_EQ(static_cast<const Test_Model_Base::Prop&>(*properties).same_name, 37);
EXPECT_EQ(properties->same_name, 41);
}
TEST(Model, Consumer_Facade_Selects_One_Model_Private_View) {
auto model = aethera::make_model_proxy<Test_Model_Private_Facade, Test_Model>();
EXPECT_EQ(model->private_value(), 41);