Folly result 错误溯源机制深入解析:epitaph(墓志铭)注解的用法与原理
Folly result 错误溯源机制深入解析epitaph墓志铭注解的用法与原理【免费下载链接】follyAn open-source C library developed and used at Facebook.项目地址: https://gitcode.com/GitHub_Trending/fol/follyfolly/result是 Folly 中基于 C20 设计的新一代错误/值容器与短路协程方案而epitaph错误注解正是它为返回值传播错误范式补齐的溯源能力当错误在代码中逐层向上传播时通过epitaph()显式附加自定义上下文调用点source_location 格式化消息让日志能回答这个ENOENT到底是正常用户操作还是严重 bug。读完本文你将掌握or_unwind_epitaph/epitaph的完整用法、它与get_exception/get_rich_error的透明交互模型、[via]/[after]格式化输出格式以及stack_epitaph调用栈级注解与未来半自动 epitaph 的设计方向并深入其 epitaph.h 实现原理。为什么需要 epitaph错误溯源与上下文一个ENOENT文件不存在可能意味着正常用户误操作也可能是严重 bug因此应用程序日志必须携带足够的上下文。传统异常体系下调用栈stack trace是自动捕获的但 C 栈展开stack unwinding开销巨大1 微秒以上见原文档脚注 [*]。folly::result的做法恰恰相反不自动、显式地在错误传播路径上附加自定义上下文这就是 epitaph。它带来两个关键优势热路径hot code可以自由选择不加注解避免任何额外开销加注解的开销在后续版本中会被摊销到几纳秒量级当前 V0 实现约 60ns 生命周期成本即构造 析构详见 future_epitaph_in_place.md 中描述的内联设计。在 rich_error.md 中epitaph 被定位为 rich error 的四大能力之一——Provenance溯源把它想成 return-result 范式下可定制的调用栈。基本用法epitaph与or_unwind_epitaph完整 API 见 epitaph.h。最常用的写法是配合短路协程or_unwind一起使用co_await or_unwind_epitaph(resultFn(), in {} due to {}, place, reason); // ... 等价于语法糖展开: co_await or_unwind(epitaph( resultFn(), in {} due to {}, place, reason));消息采用fmt风格格式化字符串。or_unwind_epitaph的实现定义在 or_unwind_epitaph.h本质是or_unwind_owning(epitaph(std::move(r), snl, args...))的一行语法糖因此它也继承了or_unwind的短路传播语义——错误或 stopped 状态会立即向上传播。epitaph()本身提供两个重载error_or_stopped与resultT行为规则如下值路径value stateresultT携带值时原样返回、不做任何注解。从源码看resultT重载首先检查r.has_value()命中即返回成本仅为 1 个分支错误或 stopped 路径包装器为错误附加调用点source_location和消息惰性格式化格式化只在错误或 stopped 结果上执行值路径绝不触发零分配优化字符串字面量且不带格式化参数时既不分配内存也不执行格式化epitaph.h的注释明确Does not throw when no format args are used (literal or empty message)消息可带参数带{}参数时会走fmt此时可能因bad_alloc抛出但不会经由make_exception_ptr_with抛异常。三种注解形式epitaph.h中的 API 注释给出了三个递增的用法示例r epitaph(my_result()) // 仅附加 source location r epitaph(my_result(), ctx) // 附加 location 与字符串字面量零分配 r epitaph(my_result(), fmt {}, a) // 附加格式化消息堆上分配Epitaph 是透明的所有 API 都访问底层错误内部实现上epitaph用不同的类型把错误包了一层rich_errordetail::epitaph_non_value但所有公共 API——get_exceptionEx()、get_rich_error()、get_rich_error_code()等——访问的都是底层underlying原始错误即最初被传播的那个错误对象。考虑如下场景result resultFn() { return error_or_stopped{std::logic_error{oops}}; }这里没有办法把富上下文塞进logic_error内部因此只能包装它。从 epitaph.h 中epitaph_impl的成员可见epitaph 内部实际存储三样东西一个rich_exception_ptr持有原始logic_error可通过underlying_error()以 O(1) 解包供get_exceptionEx()直达底层一个source_location记录epitaph调用点的源码位置一个rich_msg消息可为空、字面量或堆上格式化产物。关于线程安全需要特别注意epitaph.h明确警告如果底层异常派生自rich_error_base底层异常对象可能被修改因此不要并发访问异常对象。重要限制epitaph 不能添加错误码错误码code直接控制程序控制流而 epitaph 本质是可丢弃的注解——代码可能通过抛出、转换为std::exception_ptr等方式意外丢失注解因此 folly 刻意禁止 epitaph 携带错误码从设计上杜绝这一footgun。需要改变错误码时请改用nestable_coded_rich_error见 nestable_coded_rich_error.h它也实现了caused-by嵌套语义。这一点在 rich_error_code.md 中有呼应错误码是独立于注解的查询维度get_rich_error_codeCode(container)在任何支持folly::get_exception的错误容器上都能工作。get_exceptionEx()的返回值是富格式化的在result或error_or_stopped上调用get_rich_error()与get_exceptionEx()时返回的是rich_ptr_to_underlying_errorEx——它看起来像指向底层Ex的指针但比裸Ex*强得多同时支持fmt格式化与(ostream)输出输出内容包含完整的 epitaph 栈auto res epitaph(resultFn(), context); if (auto ex get_exceptionstd::logic_error(res)) { // 注意不是 auto* LOG(INFO) Oh no: ex; // 输出包含 context 与 source location static_assert(std::is_same_vdecltype(*ex), const std::logic_error); }注意if (auto ex ...)中不能写成auto*因为返回的是rich_ptr_to_underlying_error而非裸指针*ex解引用后是const std::logic_error从而保留底层异常的真实类型语义。警惕类型退化Caution把rich_ptrEx转成裸Ex*或把error_or_stopped转成std::exception_ptr/exception_wrapper都会丢失 epitaph只剩底层错误。与 rich error 生态的配合这一设计并非孤立存在。在 rich_error.md 的教程中推荐用户继承rich_error_base或其子类如coded_rich_error定义错误类型并用get_exceptionErr()查询而 epitaph 则是这类 rich error 传播时的传播笔记propagation notes 源码位置。最佳实践摘要如下查询用get_exceptionErr与catch (const Err)不要用get_exceptionrich_errorErrrich_errorBase是 final 叶类无法匹配派生类型且会诱导调用低效的what()捕获兜底时先用get_rich_error()即get_exceptionrich_error_base()再检查std::exception前者更便宜且日志更好日志输出优先用operator或fmt::format而非what()——只有它们能展示 epitaph 栈。格式化输出[via]与[after]分隔符epitaph 栈渲染时使用[via]和[after]两个分隔符OriginalErr [via] last annotation src.cpp:50 [after] first src.cpp:40语义如下[via]出现在 epitaph 栈之前提示其后是注解链[after]用于分隔栈中逐条注解最近一次注解排在最前上例中src.cpp:50的注解是最后附加的故在[after]左侧。为了模拟std::nested_exception任何 rich error 都可以存储一个caused-by错误并通过next_error_for_epitaph()暴露参见 nestable_coded_rich_error.h 的示例。当发生嵌套时由于每个嵌套错误可能各自携带 epitaph 栈你可能会看到多个[via]分隔符。调用栈级注解stack_epitaph除了常规epitaph消息 源码位置epitaph.h 还提供了stack_epitaph它在注解时捕获当前调用栈的原始指令指针raw instruction pointers符号化symbolize延迟到格式化时刻才执行因此捕获成本仅是一次 libunwind 栈遍历约每帧 ~10ns典型 20 帧约 ~200ns相对 throwcatch 的 ~2µs 可忽略默认max_frames256时约用 2KB 临时栈空间。内存布局上每个stack_epitaph约 192 字节3 个缓存行Windows 上多 8B前 17 帧内联存储inline_frames 17超出部分溢出到引用计数的堆分配。通过stack_epitaph{.max_frames 64}或stack_epitaph{.max_frames 64, .inline_frames 8}可自定义对应源码中的detail::stack_epitaph_opts。stack_epitaph已由result_promise::unhandled_exception()自动使用也支持用户手写 catch 子句} catch (...) { co_return stack_epitaph( error_or_stopped::from_current_exception(), context msg); }其非抛异常条件与epitaph类似不带格式化参数字面量或空消息且捕获帧数不超过内联容量时全程不分配。其符号化逻辑实现在 epitaph.cpp 的format_epitaph_stack()优先通过 folly symbolizer 输出#0 function file:line格式无符号信息时回退为十六进制地址该文件还通过FOLLY_EPITAPH_USE_SYMBOLIZER宏控制是否启用 ELFDWARF 符号化Android 上默认关闭以削减 symbolizer 链接依赖。未来方向result 协程的半自动 epitaph当前result协程需要手动在co_await前调用epitaph文档给出了两条演进思路高优先级Hi-priresult协程中抛出的异常会进入result_promise::unhandled_exception但目前不会自动附加 epitaph。这个缺口很容易补齐——throw的成本1µs远大于 epitaph 的开销所以unhandled_exception应当总是附加 epitaph一个特殊的 epitaph 包装类型应捕获异常时的调用栈并通过format_to()暴露。事实上 epitaph.cpp 已实现stack_epitaph_for_unhandled_exception()noexcept用max_frames inline_frames 19保证全部内联、绝不因堆分配而抛出正是这一方向的部分落地。低优先级Lo-pri为所有co_await点自动添加默认 epitaph免去逐点标注。文档设想引入一种特殊的epitaph 函数参数例如result myFn(epitaph_on_co_awaitmy fn_litv, ...);然后用coroutine_traits定制该协程的 promise 类型并借助 promise 构造函数捕获动态上下文——甚至可能自动捕获函数名或其调用者通过rsp。文档标注为TBD属于待定设计。性能账本与总结综合 epitaph.h 与 rich_error.md 的数据epitaph 的成本模型如下值路径仅 1 个分支近乎免费错误路径可能分配一个新的std::exception_ptr当前约 60ns 构造析构有需求时可微优化摊销至每次调用 5ns 以内对照基准throw约 1000nsexception_wrapper优化的std::exception_ptr构造/析构约 30ns、拷贝约 7ns、move 约 1ns在 result.md 中result错误传播被评估为 O(1ns) vsthrow的 ~1µs。核心结论epitaph 是folly::result在显式错误码的效率 异常式上下文之间取得平衡的关键机制。它的三个设计支柱是——透明所有公共 API 直达底层错误、可丢弃因此绝不承载控制流所用的错误码、按需开销热路径零成本、错误路径 ~60ns、未来可摊销至个位数纳秒。配合or_unwind_epitaph短路传播、get_exception富格式化输出与stack_epitaph调用栈捕获它在不引入throw开销的前提下为日志与调试提供了接近传统异常调用栈的诊断信息。脚注 [*] 提到Cthrow性能理论上可通过大量编译器/工具链工作进一步优化CppCon 2025 上有相关探索Khalil Estell 的演示。这也从侧面说明在工具链成熟之前result epitaph 的方案在性能敏感的 C 服务代码中具有现实意义。更完整的性能设计与rich_exception_ptr位打包方案可进一步阅读 rich_exception_ptr.md 与 design_notes.md。【免费下载链接】follyAn open-source C library developed and used at Facebook.项目地址: https://gitcode.com/GitHub_Trending/fol/folly创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →