并发使用 XPathExpression

Using XPathExpression concurrently

提问人:Dmytro Marchenko 提问时间:9/2/2020 最后编辑:Dmytro Marchenko 更新时间:11/18/2023 访问量:1823

问:

我正在尝试并发使用 XPathExpression,但解析时可能不会调用“org.xml.sax.SAXException:FWK005 解析”。 使用 ThreadLocal 无济于事,即使为每个调用创建了新的 XPathExpression,它也会失败。 此 Junit 测试重现了以下问题:

package testxpath;

import java.io.StringReader;
import java.util.stream.IntStream;

import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.junit.jupiter.api.Test;
import org.xml.sax.InputSource;

import lombok.SneakyThrows;

class ConcurrentXpathTest {

    private static final String XPATH = "//*[(local-name()='Node')]";
    private static final String XML = "<Node>Something</Node>";

    @Test
    void testConcurrentXpath() {
        IntStream.range(0, 10000)
            .parallel()
            .forEach(v -> parse());
    }

    @SneakyThrows
    private void parse() {
        XPathExpression xPathExpression = XPathFactory.newInstance().newXPath().compile(XPATH);
        xPathExpression.evaluate(new InputSource(new StringReader(XML)), XPathConstants.NODESET);
    }
}

如何解决这个问题?也许建议一些替代库。

堆栈跟踪:

org.xml.sax.SAXException: FWK005 parse may not be called while parsing.
    at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:271)
    at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:339)
    at com.sun.org.apache.xpath.internal.jaxp.XPathExpressionImpl.evaluate(XPathExpressionImpl.java:291)
    at com.corelogic.clp.tel2.gateway.services.ConcurrentXpathTest.parse(ConcurrentXpathTest.java:43)
    at com.corelogic.clp.tel2.gateway.services.ConcurrentXpathTest.lambda$testConcurrentXpath$0(ConcurrentXpathTest.java:37)
    at java.util.stream.ForEachOps$ForEachOp$OfInt.accept(ForEachOps.java:205)
    at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)
    at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
--------------- linked to ------------------
javax.xml.xpath.XPathExpressionException: org.xml.sax.SAXException: FWK005 parse may not be called while parsing.

    at com.sun.org.apache.xpath.internal.jaxp.XPathExpressionImpl.evaluate(XPathExpressionImpl.java:294)
    at com.corelogic.clp.tel2.gateway.services.ConcurrentXpathTest.parse(ConcurrentXpathTest.java:43)
    at com.corelogic.clp.tel2.gateway.services.ConcurrentXpathTest.lambda$testConcurrentXpath$0(ConcurrentXpathTest.java:37)
    at java.util.stream.ForEachOps$ForEachOp$OfInt.accept(ForEachOps.java:205)
    at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)
    at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: org.xml.sax.SAXException: FWK005 parse may not be called while parsing.
    at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:271)
    at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:339)
    at com.sun.org.apache.xpath.internal.jaxp.XPathExpressionImpl.evaluate(XPathExpressionImpl.java:291)
    ... 12 more
--------------- linked to ------------------
javax.xml.xpath.XPathExpressionException: javax.xml.xpath.XPathExpressionException: org.xml.sax.SAXException: FWK005 parse may not be called while parsing.
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:593)
    at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677)
    at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:735)
    at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:160)
    at java.util.stream.ForEachOps$ForEachOp$OfInt.evaluateParallel(ForEachOps.java:189)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
    at java.util.stream.IntPipeline.forEach(IntPipeline.java:404)
    at java.util.stream.IntPipeline$Head.forEach(IntPipeline.java:560)
    at com.corelogic.clp.tel2.gateway.services.ConcurrentXpathTest.testConcurrentXpath(ConcurrentXpathTest.java:37)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:108)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$4(NodeTestTask.java:112)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:98)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:74)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: javax.xml.xpath.XPathExpressionException: org.xml.sax.SAXException: FWK005 parse may not be called while parsing.
    at com.sun.org.apache.xpath.internal.jaxp.XPathExpressionImpl.evaluate(XPathExpressionImpl.java:294)
    at com.corelogic.clp.tel2.gateway.services.ConcurrentXpathTest.parse(ConcurrentXpathTest.java:43)
    at com.corelogic.clp.tel2.gateway.services.ConcurrentXpathTest.lambda$testConcurrentXpath$0(ConcurrentXpathTest.java:37)
    at java.util.stream.ForEachOps$ForEachOp$OfInt.accept(ForEachOps.java:205)
    at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)
    at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: org.xml.sax.SAXException: FWK005 parse may not be called while parsing.
    at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:271)
    at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:339)
    at com.sun.org.apache.xpath.internal.jaxp.XPathExpressionImpl.evaluate(XPathExpressionImpl.java:291)
    ... 12 more
Java XML XPath

评论

0赞 user207421 9/2/2020
堆栈跟踪真的属于此代码吗?从哪里进来?com.corelogic.clp.tel2.gateway.services.xml.HttpStatusService.getResponseStatus()
0赞 Dmytro Marchenko 9/3/2020
对不起,这是来自实际代码的堆栈跟踪。编辑的帖子,现在堆栈跟踪来自测试
0赞 Alejandro 9/3/2020
为什么不解析一次并发评估呢?

答:

1赞 predi 11/12/2021 #1

这是由于某些 Xerces 版本中的一个错误,即使是 JDK/JRE 附带的版本也是如此。

请参阅此错误报告。据推测,它在 Java 9+ 附带的默认实现中已修复,但您可能对 1.8 不走运。

可能值得检查您正在使用的是哪个 Xerces 实现。

我们偶然发现了与 Apache Xalan 2.7.1 捆绑在一起的 Xerces 的相同内容。返回的对象似乎包含非线程安全的内部状态,但在实例之间共享(或者它生成表现出相同内容的其他对象)。这似乎是异常的根本原因。DocumentBuilderFactory.newInstance()

评论

1赞 tom_mai78101 9/12/2023
不,您链接的错误报告未修复。他们在没有任何解决方案的情况下关闭了票证,因为他们无法重现提到的错误。因此,Java 9+ 仍然会有这个问题。(我在 Java 17 LTS 上,我们现在仍然看到这个问题。
0赞 Patrice Gagnon 11/18/2023 #2

我可以用 JDK 11 和 Xerces 2.12.0 轻松重现它

org.xml.sax.SAXException: FWK005 parse may not be called while parsing.
at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:276) ~[?:?]
at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:339) ~[?:?]
at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:122) ~[?:?]

为了解决这个问题,我只需同步我的构建器:

Document xmlDocument = null;
InputStream stream = new ByteArrayInputStream(xml.getBytes("UTF-8"));
synchronized(builder) {
  xmlDocument = builder.parse(stream);
}