ErrorRecorder Diagnostics Design Gate
error-recorder-diagnostics-design-gate 用来收口 IErrorRecorder 中风险 deferred 候选:项目已经可以通过 owner 对象复制诊断信息,但仍不能把原生 recorder 当作 public ownership 对象交给 C# 用户。
当前结论
RuntimeEvidenceKind=design-gate。CopiedDiagnosticsReady=True,Runtime、Refitter、Builder、Engine、ExecutionContext、NetworkDefinition 和 EngineInspector 可以通过TensorRtErrorRecorderSnapshot读取 copied diagnostics。RequiredOutputMode=owner-scoped copied diagnostics and interface metadata snapshot。CandidateMethods=IErrorRecorder::getInterfaceInfo, IErrorRecorder::getNbErrors, IErrorRecorder::getErrorCode, IErrorRecorder::getErrorDesc, IErrorRecorder::hasOverflowed, IErrorRecorder::incRefCount, IErrorRecorder::decRefCount。PointerFreeSurfaceReady=True,public API 不暴露、返回或保存 recorder pointer。RecorderPointerExposed=False,RecorderPointerProduced=False,BorrowedRecorderPointerEscaped=False。RefCountPublicOwnershipControl=False,InterfaceInfoPublicOwnershipControl=False。DirectRecorderOwnershipDeferred=True,directIErrorRecorder*ownership、ref-count 和 interface-info 行继续 deferred。CanPromoteWithoutRuntimeProof=False,RuntimeProofBlocked=True,DeferredRowsStillRequired=True,该门禁是 not proof。
已允许的 public 边界
所有已支持 owner 使用 copied snapshot:
TensorRtRuntime.TryGetErrorRecorderSnapshot(out TensorRtErrorRecorderSnapshot snapshot)。TensorRtRefitter.TryGetErrorRecorderSnapshot(out TensorRtErrorRecorderSnapshot snapshot)。TensorRtBuilder.TryGetErrorRecorderSnapshot(out TensorRtErrorRecorderSnapshot snapshot)。TensorRtEngine.TryGetErrorRecorderSnapshot(out TensorRtErrorRecorderSnapshot snapshot)。TensorRtExecutionContext.TryGetErrorRecorderSnapshot(out TensorRtErrorRecorderSnapshot snapshot)。TensorRtNetworkDefinition.TryGetErrorRecorderSnapshot(out TensorRtErrorRecorderSnapshot snapshot)。TensorRtEngineInspector.TryGetErrorRecorderSnapshot(out TensorRtErrorRecorderSnapshot snapshot)。TensorRtErrorRecorderSnapshot.Records是托管集合,记录项为TensorRtErrorRecord。
所有 owner 同时使用 presence / clear 边界:
HasErrorRecorder只返回是否附加 recorder。ClearErrorRecorder()只清除 owner 上的外部 recorder 绑定,不销毁 recorder,不接管生命周期。
Plugin registry inventory 也只使用 copied/presence 语义,不暴露 registry 或 recorder borrowed pointer。
继续 deferred 的内容
以下内容不能因为 design gate ready 而晋级:
IErrorRecorder::incRefCount/decRefCount。- direct
IErrorRecorder::getInterfaceInfoownership。 - 任何
IErrorRecorder*或裸IntPtrpublic API。 - 任何需要用户持有 borrowed recorder lifetime 的 API。
这些接口如果后续要提升,必须先有独立 ownership 模型、跨 ABI no-throw 约束、release ordering 和 package-consumer runtime proof。
下一轮如果只能证明 owner-scoped snapshot,则应继续扩展 TensorRtErrorRecorderSnapshot 证据链,而不是新增 direct IErrorRecorder public wrapper。
验证信号
CallbackAllocatorSafeControlsSmokeRunner --dependency-probe-only 会输出:
SafeControlSurface=error-recorder-diagnostics-design-gate;...ErrorRecorderDiagnosticsDesignGate=error-recorder-diagnostics-design-gate;...CopiedDiagnosticsReady=TruePointerFreeSurfaceReady=TrueRefCountPublicOwnershipControl=FalseRuntimeProofBlocked=True
这些信号只说明 design gate 存在,不是 full runtime smoke passed,也不是 package-consumer runtime proof。
为什么要复制,而不是包装 IErrorRecorder*
TensorRT owner 返回的 error recorder 通常是 borrowed interface。若 C# wrapper 直接保存该指针,就会出现三个无法靠
SafeHandle 自动解决的问题:谁持有 ref count、owner 释放后 recorder 是否仍有效、callback 或 getter 是否可能在
另一个线程发生。当前设计选择 owner-scoped read + managed copy,把风险留在一次受控 bridge 调用内。
sequenceDiagram
participant App as C# caller
participant Owner as TensorRt owner
participant Bridge as stable C ABI
participant Recorder as borrowed IErrorRecorder
App->>Owner: TryGetErrorRecorderSnapshot()
Owner->>Bridge: copy count/overflow/records
Bridge->>Recorder: read while owner is alive
Recorder-->>Bridge: borrowed strings and codes
Bridge-->>Owner: caller-buffer / copied structs
Owner-->>App: TensorRtErrorRecorderSnapshot
Note over App: snapshot survives owner disposal
复制模型分别位于 src/JYPPX.TensorRtSharp/Callbacks/Monitoring/TensorRtErrorRecord.cs、
TensorRtErrorRecorderSnapshot.cs 与 TensorRtErrorRecorderSummary.cs,owner 读取入口位于各自 boundary partial,
例如 src/JYPPX.TensorRtSharp/Builder/TensorRtBuilder.Trt11BoundaryControls.cs、
src/JYPPX.TensorRtSharp/Engine/TensorRtEngine.Trt11BoundaryControls.cs。design-gate 状态由
src/JYPPX.TensorRtSharp/Callbacks/Monitoring/TensorRtErrorRecorderDiagnosticsDesignGate.cs evaluator 与
TensorRtErrorRecorderDiagnosticsDesignGateResult.cs result model 共同表达,而不是由文章手工推断。单独读取 evaluator
不代表完整设计门实现。
Snapshot 数据怎么读
TensorRtErrorRecorderSnapshot 至少表达:
ErrorCount:owner 报告的错误数。Records:复制后的TensorRtErrorRecord集合,每条含 code 与 description。HasOverflowed:原 recorder 的容量是否溢出。- interface metadata 是否可用,以及不可用时的诊断。
Records.Count 可能因 native 侧读取失败或 overflow 与 ErrorCount 不同,因此 summary 额外保留
CopiedRecordCountMatchesErrorCount。调用方不应只检查集合非空,也不应把 description 当稳定机器码;自动化应优先使用
error code、count、overflow 和明确的 bridge status。
if (runtime.TryGetErrorRecorderSnapshot(out TensorRtErrorRecorderSnapshot snapshot))
{
Console.WriteLine($"Errors={snapshot.ErrorCount} " +
$"Copied={snapshot.Records.Count} " +
$"Overflow={snapshot.HasOverflowed}");
foreach (TensorRtErrorRecord record in snapshot.Records)
{
Console.WriteLine($"Code={record.Code} Description={record.Description}");
}
}
这段代码没有保存 recorder,也没有调用 incRefCount/decRefCount。snapshot 生成后是普通托管数据,可以在 owner
释放后用于日志归档。
七类 owner 的一致语义
| Owner | Snapshot 用途 | ClearErrorRecorder() 的含义 |
|---|---|---|
| Runtime | deserialize/runtime 配置诊断 | 清除绑定,不销毁 recorder |
| Builder | network build 诊断 | 清除 builder 绑定 |
| Refitter | entry/set/refit 诊断 | 清除 refitter 绑定 |
| Engine | engine metadata/runtime 诊断 | 清除 engine 绑定 |
| ExecutionContext | enqueue/context 诊断 | 清除 context 绑定 |
| NetworkDefinition | layer/network 创建诊断 | 清除 network 绑定 |
| EngineInspector | inspector readback 诊断 | 清除 inspector 绑定 |
HasErrorRecorder 只说明 owner 当前有 recorder,不说明已有错误,更不说明 snapshot 已验证。clear 操作也不是 dispose;
managed API 不声称拥有 native recorder 的生命周期。
运行 design-gate smoke
$repo = "."
$case = "..\downloads\cases\error-recorder-design-gate"
New-Item -ItemType Directory -Force -Path "$case\logs" | Out-Null
Set-Location $repo
dotnet build .\smoke\CallbackAllocatorSafeControlsSmokeRunner\CallbackAllocatorSafeControlsSmokeRunner.csproj `
-c Debug --no-restore --nologo
dotnet .\smoke\CallbackAllocatorSafeControlsSmokeRunner\bin\Debug\net8.0\CallbackAllocatorSafeControlsSmokeRunner.dll `
--dependency-probe-only 2>&1 | Tee-Object "$case\logs\dependency-probe.log"
dependency-probe-only 路径用于确认 bridge 和 safe-control surface 可发现;它会显式输出 Skipped=True,不能当成 runtime
callback invocation。兼容主机上的完整运行还应保存 resolved line、adapter、每类 owner 的 snapshot/clear marker 和退出码。
失败场景的解释顺序
- 先读 bridge status 和 last error,区分 invalid argument、unsupported line、dependency missing 与 runtime error。
- 再读
HasErrorRecorder;false 时不要伪造空 snapshot 等同于“无错误”。 - snapshot 可用时检查 count、copied count、overflow 和 records。
- 将错误绑定到具体 owner 与操作,例如 build、deserialize、refit 或 enqueue。
- 保留原始 stdout/stderr、TensorRT line、runtime package key 和 host metadata。
如果 owner 已 dispose,正确行为是使用此前复制的 snapshot,而不是重新访问 owner。若需要更长时间的 native recorder
生命周期,必须另行设计 ownership contract,不能偷偷缓存 IntPtr。
Direct ownership 为什么继续 deferred
IErrorRecorder::incRefCount 与 decRefCount 看似只是两个小接口,实际上会把引用计数协议、并发调用、析构顺序和
跨版本 ABI 全部暴露给 public caller。getInterfaceInfo 同样可能返回具有 native 生命周期的接口信息。没有以下证据前,
这些行继续 deferred:
- 跨 TRT8/TRT10/TRT11 的明确 availability 与签名。
- no-throw C ABI adapter 和 ref-count balance 测试。
- owner/recorder attach、detach、dispose 的状态机。
- callback 并发与 shutdown ordering 测试。
- clean package-consumer runtime proof。
copied interface metadata 可以继续通过 owner-safe 路径扩展,但这不会自动解锁 direct ownership。
验收与发布边界
- public API 搜索不到 recorder
IntPtr或 borrowed pointer。 - snapshot records 全部是 managed-owned 数据。
- 每类 owner 的 presence/snapshot/clear 语义一致。
- dependency probe、design gate、runtime invocation 分开记录。
CopiedDiagnosticsReady=True不等于CanPromoteWithoutRuntimeProof=True。performsPublish=false、canPublishPublicly=false、canCloseReleaseIssue=false。
继续阅读:ErrorRecorder Snapshot Guide、 Callback/allocator safety roadmap 与 排障总表。