we’ve been implementing a rather simple gradle task to validate XML files to a given Schema, which will only be available through a dependency (from inside a given jar).
To achieve this we implemented it based on a Worker API resolving a custom configuration and running the validation in classloader isolation:
public void execute() {
try (final InputStream schemaStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(getParameters().getSchemaFileName().get())) {
final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
final Schema schema = factory.newSchema(new StreamSource(schemaStream)); // <-- this one is randomly failing!
final Validator validator = schema.newValidator();
for (File xmlFile : getParameters().getSource().get()) {
validator.validate(new StreamSource(xmlFile));
}
}
}
But when using this in a parallelized gradle builds that evaluate multiple file with different Schemas, we are constantly getting the following exception:
org.xml.sax.SAXParseException; schema_reference.4: Failed to read schema document 'null', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>.
at java.xml/com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:204)
at java.xml/com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.error(ErrorHandlerWrapper.java:135)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:396)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:306)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.reportSchemaErr(XSDHandler.java:4257)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.reportSchemaError(XSDHandler.java:4240)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.getSchemaDocument1(XSDHandler.java:2531)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.getSchemaDocument(XSDHandler.java:2238)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.parseSchema(XSDHandler.java:588)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaLoader.loadSchema(XMLSchemaLoader.java:617)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaLoader.loadGrammar(XMLSchemaLoader.java:576)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaLoader.loadGrammar(XMLSchemaLoader.java:542)
at java.xml/com.sun.org.apache.xerces.internal.jaxp.validation.XMLSchemaFactory.newSchema(XMLSchemaFactory.java:276)
at java.xml/javax.xml.validation.SchemaFactory.newSchema(SchemaFactory.java:669)
at my.gradle.plugin.xmlvalidator.tasks.SchemaValidateWorkAction.execute(SchemaValidateWorkAction.java:37)
... 38 more
Caused by: java.io.IOException: Stream closed
at java.base/java.util.zip.InflaterInputStream.ensureOpen(InflaterInputStream.java:68)
at java.base/java.util.zip.InflaterInputStream.read(InflaterInputStream.java:143)
at java.base/java.io.FilterInputStream.read(FilterInputStream.java:133)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityManager$RewindableInputStream.read(XMLEntityManager.java:3062)
at java.xml/com.sun.org.apache.xerces.internal.impl.io.UTF8Reader.read(UTF8Reader.java:303)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.load(XMLEntityScanner.java:1699)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.scanQName(XMLEntityScanner.java:855)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:193)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2710)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:605)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:112)
at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:534)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.opti.SchemaParsingConfig.parse(SchemaParsingConfig.java:640)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.opti.SchemaParsingConfig.parse(SchemaParsingConfig.java:696)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.opti.SchemaDOMParser.parse(SchemaDOMParser.java:530)
at java.xml/com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.getSchemaDocument(XSDHandler.java:2226)
... 45 more
Testing this with org.gradle.parallel=false worked just fine.
Also using processIsolation works, but is like 30times slower.
I am confused as every InputStream we get is a fresh instance nothing should be shared among the tasks and still it’s obviously due to parallel execution.
Set a breakpoint in java.util.zip.InflaterInputStream#close and check where it is called from, as this is the only place where the private field closed is set to true which causes ensureOpen to throw that exception.
A colleague of mine found, that a ClassLoader is closing the stream and with that information he found:
there are quiet some requirements to be met. The classpath isolation needs to address the same jar files, but with that you can randomly run into this issue.
In the end it seems to be a JDK Bug submitted in 2016 (back then classified as a duplicate), so I don’t know if there even is a way of fixing that in Gradle.