为何调用Stream.take/2会导致正常XML解析流运行异常?
问题
我编写了如下用于解析XML文档的函数:
defp stream_parse!(dict) do dict |> SweetXml.stream_tags!(:entry) |> Stream.map(fn {:entry, doc} -> doc |> SweetXml.xpath( ~x"."s, id: ~x"./id/text()"s, definition: [ ~x"./definition"l, gloss: ~x"./gloss/text()" |> transform_by(&List.to_string/1), origin: ~x"./origin/text()"sl, refs: ~x"./refs/text()"sl ] ) end) end
其中,new_dict是已验证的XML文档的二进制表示。以下代码能成功运行完成,并通过IO.inspect/1输出数十万条完整条目:
new_dict |> stream_parse!() |> Stream.each(&IO.inspect/1) |> Stream.run()
仅添加了Stream.take/2调用的如下代码,却在输出[error] 2781- fatal: :error_scanning_entity_ref错误后程序挂起:
new_dict |> stream_parse!() |> Stream.take(100) |> Stream.each(&IO.inspect/1) |> Stream.run()
请问为何调用Stream.take/2会破坏该处理流程?
分析与解决
问题根源在于流提前终止后,XML解析器未被正确关闭,导致解析状态异常:
- 不使用
Stream.take/2时,流会完整遍历整个XML文档,SweetXml.stream_tags!/2内部的解析器能正常完成扫描流程,自动关闭并清理资源,不会出现异常。 - 当用
Stream.take(100)截断流后,迭代在取到100条数据后就会停止。此时SweetXml的流式解析器还处于XML文档的中间处理状态,没有完成完整的解析生命周期,解析器的内部资源未被正确释放或重置。后续解析器尝试读取剩余的XML片段时,因为流已经终止,数据不完整,就会触发实体引用扫描错误,最终导致程序挂起。
可行的解决思路:
- 用
Stream.resource/3包裹SweetXml.stream_tags!/2,在流终止的回调中显式关闭解析器,确保资源被正确清理。 - 在取完指定数量的条目后,手动完成剩余XML内容的解析(虽然会额外消耗资源,但能避免解析器处于异常状态)。
内容的提问来源于stack exchange,提问作者vaer-k
相关产品推荐
相关产品推荐

