Aspen Plus V14 COM 并发与退出故障处理记录

KN 技术与实践

本文记录 Aspen Plus V14 在 Windows COM 自动化中的并发归档故障和退出故障,以及当前项目采用的处理方案。

自动化程序由多个 PowerShell STA worker 组成。每个 worker 使用独立的 Aspen COM 对象、BKP 副本、工作目录和临时目录。测试表明,Run2 可以并行执行,故障主要发生在归档写入和进程退出阶段。

处理结论

Run2 保持并行。COM 创建和归档加载通过启动互斥锁串行执行,用于确定进程所有权。SaveAs2 → Reconcile → SaveAs2 通过归档互斥锁整体串行执行。退出时只清理当前 worker 认领的进程。

运行环境与故障签名

项目 内容
Aspen 版本 Aspen Plus V14
自动化方式 PowerShell STA + Apwn.Document COM 对象
并发方式 多个独立 worker,每个 worker 对应独立 Aspen 实例
故障进程 apmain.exe
故障模块 STRGXI2.dll
模块版本 2018.11.1.508
Windows 异常代码 0xc0000409
COM 调用方错误 0x800706BE
Windows 事件 Application Error,Event ID 1000

0xc0000409 对应 STATUS_FAIL_FAST_EXCEPTION。该异常会绕过常规异常处理并终止进程,但仅凭异常代码无法确定 Aspen 内部的具体缺陷,也不能据此判定流程计算结果错误。Microsoft 对 C0000409 的说明

故障一:多个进程同时写入归档

并发评价时,各 worker 的 Run2 可以正常完成。多个 worker 同时调用 SaveAs2 时,STRGXI2.dll 可能异常退出,COM 调用方随后收到 0x800706BE。

典型时序如下:

1
2
3
4
5
6
7
Worker 1  Run2 ─────────────────────────────── 完成
Worker 2 Run2 ─────────────────────────────── 完成

Worker 1 SaveAs2 ─┐
Worker 2 SaveAs2 ─┴─ 同时进入归档层
└─ STRGXI2.dll / 0xc0000409
COM 调用方:0x800706BE

每个 worker 使用独立 BKP 文件不能消除该故障。冲突发生在 Aspen V14 的归档层,而不是文件路径重用。

故障二:COM 退出路径异常

归档并发故障之外,当前环境还存在单实例退出异常。Quit、CloseDocument 和最终释放 COM 引用均可能触发 apmain.exe 异常退出。

关闭路径测试结果如下:

测试路径 是否保存 退出方式 结果
run_only 否 Run2 后调用 Quit 可出现 Event 1000
release_only 否 仅释放 COM 并执行 GC 可出现相同故障签名
CloseDocument 否 仅关闭文档 可能挂起,随后引擎退出
受控清理 结果已验收 只终止 worker 认领的 PID 本轮无残留进程

不执行 Save、不执行 Reconcile、不开启并发时仍能复现退出异常。因此,退出故障与多进程归档冲突是两个独立问题。

并发边界

一次候选评价按以下阶段执行:

1
2
3
4
5
[进程快照 → 创建 COM → 加载 BKP → 进程快照] → [Run2 → 读取并验收结果]
└──────── 启动锁:短暂串行 ────────────────┘ └─ 独立 worker:并行 ─┘

[保存 RESULT → Reconcile → 保存 INITIALIZED] → [释放 COM / 清理进程]
└────────── 归档锁:整体串行 ─────────────┘ └─ 只处理自己的 PID ─┘
阶段 执行方式 保护措施
COM 创建、BKP 加载、进程认领 串行 启动互斥锁
参数写入、Run2、结果读取 并行 每个 worker 使用独立 STA、COM 对象和目录
SaveAs2 → Reconcile → SaveAs2 串行 归档互斥锁覆盖完整序列
退出与异常清理 各 worker 独立执行 只清理当前 worker 认领的 PID

这里使用的是互斥锁,不是死锁。互斥锁用于限制临界区只能由一个 worker 进入;死锁是多个执行单元互相等待且无法继续运行的故障状态。两把互斥锁均设置等待超时,并在 finally 中释放。

启动互斥锁

通过 DCOM 创建的 Aspen 进程不一定表现为当前 pwsh 的普通子进程。程序不能只根据父子进程关系判断 Aspen 实例属于哪个 worker。

启动互斥锁覆盖以下操作:

  1. 获取启动前 Aspen 进程快照;
  2. 创建 Apwn.Document COM 对象;
  3. 调用 InitFromArchive2 加载 worker 的 BKP 副本;
  4. 获取启动后进程快照;
  5. 根据两次快照认领新增 PID。

实现结构如下。代码经过简化,不包含项目路径和业务参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$locked = $startupMutex.WaitOne($startupTimeout)
if (-not $locked) { throw '等待 Aspen 启动锁超时' }

try {
$before = Get-AspenProcessSnapshot

$document = New-Object -ComObject Apwn.Document
$document.InitFromArchive2($workerSeed)

$after = Get-AspenProcessSnapshot
$ownedPids = Compare-AspenProcessSnapshot $before $after
}
finally {
$startupMutex.ReleaseMutex()
}

完成进程认领后立即释放启动锁。后续 Run2 不在该锁内执行。

归档互斥锁

通过验收的候选需要保存两份归档:

  • RESULT 保存当前计算结果;
  • INITIALIZED 保存 Reconcile 后可继续使用的初始化状态。

归档互斥锁覆盖两次保存及其中间的 Reconcile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$locked = $archiveMutex.WaitOne($archiveTimeout)
if (-not $locked) { throw '等待 Aspen 归档锁超时' }

try {
$document.SaveAs2($resultArchive, $true)

foreach ($stream in $tearStreams) {
Invoke-Reconcile $document $stream
}

$document.SaveAs2($initializedArchive, $true)
}
finally {
$archiveMutex.ReleaseMutex()
}
归档锁范围

归档锁不能在第一次 SaveAs2 后释放。另一个 worker 如果在 Reconcile 与第二次保存之间进入 SaveAs2,两个进程仍会同时访问归档层。三项操作必须作为一个完整临界区执行。

该互斥锁只限制归档操作,不限制参数写入、Run2 和结果读取。

退出与进程清理

生产配置在结果验收和归档完成后,终止当前 worker 在启动阶段认领的 Aspen 进程。程序不会根据进程名终止全部 Aspen 进程。

简化后的清理逻辑如下:

1
2
3
4
5
6
foreach ($processId in $ownedPids) {
Stop-OwnedAspenProcessTree -ProcessId $processId
}

[Runtime.InteropServices.Marshal]::FinalReleaseComObject($document)
$document = $null

强制终止只用于已确认存在退出缺陷的当前环境,并且必须在结果持久化之后执行。TerminateProcess 会无条件终止进程,DLL 维护的状态可能无法正常释放,因此不能用于未完成保存的 worker,也不能用于未确认所有权的进程。TerminateProcess 文档

COM 线程模型

每个 worker 使用独立的 pwsh -Sta 进程,并在同一 STA 线程中创建和使用 Aspen COM 对象。COM 接口不会在线程之间直接共享。

该结构符合 COM single-threaded apartment 的基本约束。并发来自多个相互隔离的 STA worker,而不是多个线程共同访问一个 COM 对象。Microsoft 关于 Single-Threaded Apartments 的说明

结果验收

退出阶段异常不能直接用于判断计算结果是否有效。结果是否保留取决于故障发生阶段和验收状态。

当前项目使用以下验收条件:

  • Run2 正常结束,计算和结果读取阶段没有 RPC、RFC 或 COM 异常;
  • 关键模块和循环达到收敛状态;
  • 目标函数、产品指标和质量衡算满足设定容差;
  • 结果文件已完整写入,文件大小和哈希检查通过;
  • 源 BKP 在运行前后保持不变;
  • 最终候选在新的 Aspen 进程中进行两次独立 fresh 验证。

计算、结果读取或保存过程中出现 RPC、RFC 或 COM 异常时,当前结果和 worker 均作废。仅在全部验收完成后发生的退出异常不会自动否定已经保存的结果。

并发测试结果

固定模型和测试条件下,2—5 个 worker 均完成了 Run2 并发测试。关键数值签名在 1×10⁻⁸ 的绝对和相对容差内一致。

worker 数 吞吐量/次·min⁻¹ Aspen 峰值工作集/GiB 最低可用内存/GiB
2 2.69 2.03 11.88
3 3.45 2.96 11.10
4 3.91 3.45 10.72
5 4.37 3.90 10.44

从 4 个 worker 增加到 5 个 worker 后,吞吐量提高约 11.8%,同时可用内存继续下降。当前生产配置使用 4 个 worker,为操作系统和结果处理保留内存余量。5 个 worker 是本轮测试覆盖的最大数量,不代表当前机器或 Aspen Plus 的通用并发上限。

适用范围

上述结论来自当前 Aspen Plus V14、模型和机器环境。由于缺少 AspenTech 源码和调试符号,本文只记录可复现的故障边界和已验证的工程处理方式,不对 STRGXI2.dll 的内部根因作进一步判断。

Aspen Plus 补丁、版本、模型规模或硬件环境变化后,需要重新执行以下检查:

  • 单实例计算与退出测试;
  • 无归档互斥锁和有归档互斥锁的对照测试;
  • 不同 worker 数量的吞吐量、内存和数值一致性测试;
  • 最终候选的独立 fresh 验证。