PAYATHON 2026

IBM i SAX XML 解析约 889570 条记录后无报错停止,如何调试?

排障赵师傅

结论

目前证据不足,无法将“约 889,570 条记录后停止”认定为固定记录数限制或 IBM i 7.3 的已知缺陷。应先补齐错误反馈,再排查文件大小、资源消耗或特定数据是否触发了问题。

先检查两处:

  • 旧 Qxml 封装通过 Qxml_DOMExcData 反馈部分高层错误,只注册 SAX ErrorHandler 可能漏掉失败原因。
  • 按经典接口的定义,QxmlSAXParser_setErrorHandler() 接收 ErrorHandler 对象指针,需要核对示例中直接传入 %paddr(MyErrorHandler) 的写法。

QxmlSAXParser_parse_systemid() 属于旧 alphaWorks XML 封装,与 RPG 内置 XML-SAX 是两套接口,错误处理方式和大小限制不能混用。旧 Qxml 接口及示例说明

先确认“停止”发生在哪一步

在解析调用前后记录日志,并在 document handler 中记录文档结束事件。至少保留以下信息:

  • 调用解析器前的时间、文件路径和实际字节数。
  • 最后完成的业务记录编号和记录主键。
  • 是否收到文档结束事件。
  • QxmlSAXParser_parse_systemid() 是否返回。
  • 返回后立即读取的错误反馈。
观察结果优先检查方向
解析调用没有返回,作业仍在运行查看调用栈,确认是否等待数据库、文件 I/O 或其他资源
解析调用返回,但没有文档结束事件检查封装反馈区、回调异常和提前退出路径
收到文档结束事件,但输出记录不足核对实际输入文件、业务过滤条件、数据库写入及计数逻辑
作业已经结束,调用后的日志未写入检查完整 joblog、调用方和作业结束原因

“输出表只有 889,570 条”不能直接说明“解析器只读取了 889,570 条”。建议分别统计业务元素完成数和成功写入数,将计数位置放在对应业务元素的结束事件中。

补齐旧 Qxml 接口的错误处理

检查 Qxml_DOMExcData

先找到程序实际使用的 /COPY 成员及其绑定的服务程序,核对接口版本。

初始化这套封装时,需要将异常反馈区传给 QxmlInit()。部分 SAX 高层错误会写入 Qxml_DOMExcData,应重点检查其中的 Qxml_DOMRtnCod。具体结构、初始化参数和返回码含义,以本机配套头文件为准。

创建解析器、创建 handler、注册回调和解析完成后,都应检查反馈。发现失败时,先保存完整反馈区,再做清理,避免后续调用覆盖现场。

以下诊断片段可插入现有程序,前提是原型和反馈区已正确声明、初始化:

dcl-s savedDomRc like(Qxml_DOMRtnCod);
dcl-s rpgStatus int(10) inz(0);
dcl-s parseReturned ind inz(*off);

monitor;
    QxmlSAXParser_parse_systemid(saxParser: xmlfile: 37: 0);

    // 在调用其他 Qxml 方法之前保存反馈
    savedDomRc = Qxml_DOMRtnCod;
    parseReturned = *on;
on-error;
    rpgStatus = %status();
    // 在这里持久化 RPG 状态、PSDS 和业务进度
endmon;

两条错误通道需要分别检查:

  • MONITOR / ON-ERROR 捕获能够传播到该位置的 RPG 异常。
  • Qxml_DOMRtnCod 反映封装自身报告的结果。

如果封装在内部捕获异常后只写入反馈区,外层 ON-ERROR 就不会被触发。因此,调用正常返回也不能证明解析成功。

核对 ErrorHandler 对象与回调的注册方式

如果本机使用经典 Qxml 封装,注册方式应类似下面的代码。原型、常量和回调参数必须使用同一版本的头文件:

errorHandler = QxmlErrorHandler_new();

QxmlErrorHandler_setCallback(
    errorHandler: Qxml_WARNINGHNDLR: %paddr(MyWarning));

QxmlErrorHandler_setCallback(
    errorHandler: Qxml_ERRORHNDLR: %paddr(MyError));

QxmlErrorHandler_setCallback(
    errorHandler: Qxml_FATALERRORHNDLR: %paddr(MyFatalError));

QxmlSAXParser_setErrorHandler(saxParser: errorHandler);

代码省略了逐次检查和清理部分,实际程序中每一步都应检查反馈结果。

如果问题中的代码只是简写,实际程序已经创建 ErrorHandler 对象,可以继续排查其他方向;如果确实直接传入了过程地址,应先修正这一处。小文件运行成功,仍不足以证明错误回调路径正确。

还应做一次受控测试:在小型测试 XML 中故意制造标签不匹配,确认错误回调能记录消息,主程序也能识别失败。测试时保留原始大文件不变。

如何判断是否与文件大小有关

记录数和文本行数都不能代表解析器实际处理的数据量。应记录文件字节数,并比较不同输入的停止位置。

IBM 对 RPG 内置 XML 解析器公布的限制包括:

  • 单字节 CCSID:最多处理 2147483408 个字符。
  • UCS-2:最多处理 1073741704 个 UCS-2 字符。

这些数值适用于 RPG 内置 XML 解析器,不能直接作为旧 QxmlSAXParser_parse_systemid() 的限制,也不能简化为“UCS-2 文件超过 1 GB 就会失败”。IBM:XML 解析器限制

可以对当前程序做以下对照实验:

  1. 保留业务记录,缩短非关键文本,观察停止时的记录数是否改变。
  2. 将停止位置附近的记录组成一个独立、格式完整的小 XML 文件。
  3. 暂时移除回调中的数据库写入、字符串转换和业务处理,只保留计数。

根据结果缩小排查范围:

  • 如果总在同一条业务数据附近失败,优先检查该数据和回调逻辑。
  • 如果不同输入都在相近处理量附近失败,优先检查资源累计或内部容量边界。
  • 如果纯计数可以完成,恢复业务处理后失败,重点排查回调中的操作。

拆分测试文件时,要保留根元素、命名空间和必要声明,不能直接按行截断 XML。

编码参数不要盲目改成 819

需要分别核对以下几项:

  • XML 文件的实际字节编码。
  • XML 声明中的 encoding。
  • IFS 文件的 CCSID 属性。
  • 传给 API 的路径字符串编码。

按经典 Qxml 封装的参数定义,下面调用中的 37 指定的是 xmlfile 所指向的文件名字符串的 CCSID:

QxmlSAXParser_parse_systemid(saxParser: xmlfile: 37: 0);

文件内容使用 ISO-8859-1,并不意味着这里必须改成 819。第四个参数也应使用本机原型规定的类型;经典调用用数值 0 表示文件名字符串以空字符结尾。Qxml 文件名参数说明

修改 XML 声明或 IFS CCSID 标签,并不会转换文件字节。做转换实验时,应保存原始文件,并记录转换前后的编码、字节数和解析结果。

进一步收集作业和内存证据

在实际执行解析的作业中提高日志级别,重现问题后打印 joblog:

CHGJOB JOB(*) LOG(4 00 *SECLVL) LOGCLPGM(*YES)
DSPJOBLOG JOB(*) OUTPUT(*PRINT)

批处理场景需要指定目标作业,不能只修改当前交互作业。这些命令可以收集更完整的作业消息,但不会自动将封装反馈区写入日志,反馈区仍需由程序记录。IBM 作业日志收集示例

使用带调试信息的构建,在接近停止位置时观察最后一个回调,以及解析调用能否返回。如果作业仍然存在,先查看调用栈,确认它正在执行什么。

检查内存时,应关注处理过程中是否有持续累积的情况:

  • 每条记录创建的转换字符串或临时对象是否释放。
  • 数组、缓存、日志缓冲区是否持续增长。
  • 是否将解析器提供的临时指针保存到后续回调中使用。
  • 数据库错误是否被回调中的错误处理吞掉。

一次 DUMP(A) 未发现异常,只能说明当时没有观察到明显问题,仍不能排除逐条泄漏或更早发生的内存破坏。

ACTGRP(*CALLER) 本身不足以解释这个停止点。可以在新的测试作业中比较首次运行和重复运行的行为,同时核对初始化与清理是否配对。

需要重写时,可考虑 RPG 内置 XML-SAX

内置 XML-SAX 有另一套明确的错误接口。以下调用片段假定 XMLHandler 和通信区已经按规定声明:

xml-sax(e) %handler(XMLHandler: commArea)
           %xml(ifsPath: 'doc=file');

if %error();
    rpgStatus = %status();
    // 保存状态及通信区中的异常信息,向调用方报告失败
endif;

错误处理需要同时覆盖:

  • XML-SAX(E) 后的 %ERROR() 和 %STATUS()。
  • handler 收到的 *XML_EXCEPTION。

IBM 文档列出的相关状态包括 00351、00352、00354。如果在解析开始前失败,例如无法打开文件,handler 可能根本收不到事件,因此必须保留调用后的检查。IBM:XML-SAX 错误处理

处理 *XML_EXCEPTION 时,保存 exceptionId 和长度参数。该事件的数据指针参数无效,不要读取。调试内置 handler 时可使用 DEBUG(*XMLSAX)。IBM:XML 事件及调试说明

迁移时仍需核对字符转换、实体处理和业务回调行为,并用原始大文件验收。旧程序应先补上解析返回后保存 Qxml_DOMExcData 的处理。完成检查则需同时满足:收到文档结束事件、无错误反馈、业务数量符合预期。

备注:内容仅供参考。