为何XSLT代码导致Saxonc崩溃?循环调用偶发故障排查
循环中连续调用自定义的xslt.transform()方法约3次时,有50%概率崩溃无法完成循环。首次执行通常正常,怀疑是循环间未重置new_xslt30_processor或PySaxonProcessor,资源未清理导致。
报错信息
JET RUNTIME HAS DETECTED UNRECOVERABLE ERROR: runtime error Thread C8B63 ["Thread-0"] is terminated without notifying the JVM. Probably, "DetachCurrentThread" function was not called Please, contact the vendor of the application. Core dump will be piped to "/lib/systemd/systemd-coredump %P %u %g %s %t 9223372036854775808 %h" Extra information about error is saved in the "jet_err_822032.txt" file.
代码(xslt.py)
import os import saxonc from typing import Dict, List class Xslt(): @classmethod def transform(cls, xsl_file: str, xml_file: str, output_file: str, parameters: Dict[str,str]) -> bool: try: out = cls.transform_to_string(xsl_file, xml_file, parameters) with open(output_file, "w") as f: f.write(out) return True except Exception as e: print(str(e)) return False @classmethod def transform_to_string(cls, xsl_file: str, xml_file: str, parameters: Dict[str,str]) -> str: with saxonc.PySaxonProcessor(license=True) as saxonproc: xsltproc = saxonproc.new_xslt30_processor() for parameter in parameters: xsltproc.set_parameter(parameter, saxonproc.make_string_value(parameters[parameter])) saxonproc.set_cwd(os.getcwd()) return xsltproc.transform_to_string(source_file=xml_file, stylesheet_file=xsl_file)
运行输出
Error: failed to allocate an object - please check if an exception was thrown proc is nullptr in SaxonProcessor constructor Error: failed to allocate an object - please check if an exception was thrown Error: failed to allocate an object - please check if an exception was thrown Error: failed to allocate an object - please check if an exception was thrown Error: Xslt30Processor not in a clean state. - Exception found
╭─ bash SaxonHE11-4J 羽393ms⠀ 11.0.16 toddmo 104.5.65.251 ╰─ff java -cp saxon-he-11.4.jar net.sf.saxon.Version SAXON-J-HE 11.4 from Saxonica (build 72811)
你的推测完全正确:循环中频繁创建PySaxonProcessor实例会导致JNI线程资源泄漏,触发JET运行时的线程未分离错误。核心解决思路是复用处理器实例,而非每次调用都重新创建。
1. 修改类结构,复用处理器实例
将处理器实例移至类属性,初始化一次后重复使用,避免重复创建JNI资源:
import os import saxonc from typing import Dict, List class Xslt(): # 类级别的处理器实例,仅初始化一次 _saxonproc = None _xsltproc = None @classmethod def init(cls): """初始化处理器,在循环执行前调用一次""" if cls._saxonproc is None: cls._saxonproc = saxonc.PySaxonProcessor(license=True) cls._xsltproc = cls._saxonproc.new_xslt30_processor() cls._saxonproc.set_cwd(os.getcwd()) @classmethod def cleanup(cls): """手动清理资源,循环结束后调用""" if cls._saxonproc is not None: cls._saxonproc.close() cls._saxonproc = None cls._xsltproc = None @classmethod def transform(cls, xsl_file: str, xml_file: str, output_file: str, parameters: Dict[str,str]) -> bool: try: out = cls.transform_to_string(xsl_file, xml_file, parameters) with open(output_file, "w") as f: f.write(out) return True except Exception as e: print(str(e)) return False @classmethod def transform_to_string(cls, xsl_file: str, xml_file: str, parameters: Dict[str,str]) -> str: # 确保处理器已初始化 if cls._xsltproc is None: raise RuntimeError("请先调用Xslt.init()初始化处理器") # 关键:每次调用前清空旧参数,避免状态污染 cls._xsltproc.clear_parameters() for param_name, param_value in parameters.items(): cls._xsltproc.set_parameter(param_name, cls._saxonproc.make_string_value(param_value)) return cls._xsltproc.transform_to_string(source_file=xml_file, stylesheet_file=xsl_file)
2. 正确使用方式
在循环前初始化处理器,循环结束后手动清理:
# 初始化处理器 Xslt.init() # 循环执行转换任务 for task in your_task_list: Xslt.transform(task.xsl_path, task.xml_path, task.output_path, task.params) # 循环结束后释放资源 Xslt.cleanup()
3. 关键注意事项
- 每次调用转换方法前必须调用
clear_parameters(),否则会因参数残留触发Xslt30Processor not in a clean state错误 - 处理器实例必须在循环外初始化,避免重复创建JNI线程导致资源泄漏
- 循环结束后必须调用
cleanup(),确保JNI线程正常终止并释放资源
内容的提问来源于stack exchange,提问作者toddmo

