HMCL-dev/HMCL · error · PngException
ERROR_UNKNOWN_IO_FAILURE
ERROR_UNKNOWN_IO_FAILURE
Error message
e.getMessage()
What it means
Any IOException other than EOF during PNG parsing (stream read failure, socket reset, file I/O error) is wrapped by PngReadHelper.read into PngException with code ERROR_UNKNOWN_IO_FAILURE; the message is the underlying exception's message (the literal 'e.getMessage()' in the log is the thrown expression).
Solutions
- Inspect the cause (PngException.getCause()) to find the real I/O problem.
- For network streams, retry the download with backoff before decoding.
- For files, verify permissions and that the file is not being modified concurrently.
- Handle ERROR_UNKNOWN_IO_FAILURE distinctly from ERROR_EOF so partial vs failed reads are reported differently.
Example fix
// before: raw IOException propagates as opaque failure
PngReadHelper.read(socket.getInputStream(), reader);
// after: retry transient I/O failures
for (int i = 0; i < 3; i++) {
try { return PngReadHelper.read(openStream(), reader); }
catch (PngException e) { if (e.getCode() != PngConstants.ERROR_UNKNOWN_IO_FAILURE) throw e; sleepBackoff(i); }
}
throw new IOException("download failed after retries"); Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check readability before parsing
try (InputStream probe = Files.newInputStream(file)) {
if (probe.read() == -1) throw new IOException("file is empty/unreadable: " + file);
} Try / catch
try {
return PngReadHelper.read(is, reader);
} catch (PngException e) {
if (e.getCode() == PngConstants.ERROR_UNKNOWN_IO_FAILURE) {
Throwable cause = e.getCause();
throw new IOException("I/O failure while reading PNG: " + (cause == null ? e.getMessage() : cause.getMessage()), e);
}
throw e;
} Prevention
- Always inspect PngException.getCause() to find the real I/O problem.
- For network streams use buffered streams and retry with backoff.
- Ensure files are closed/unlocked by other writers before reading.
- Wrap flaky sources (sockets, ciphers) in BufferedInputStream to stabilize reads.
When it happens
Trigger: Underlying stream fails mid-read: closed socket, broken pipe on network streams, disk read error on FileInputStream, or a custom InputStream throwing from read().
Common situations: Reading a PNG over an unstable HTTP connection; file removed/locked while being read; wrapped streams (CipherInputStream, GZIPInputStream) failing with their own IOExceptions.
Related errors
- acTL chunk length must be
- ARGB8888 doesn't support PNG mode
- Asset index file malformed
- Bad exit code
- bKGD chunk received before IHDR chunk
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/ed35ec8fc1c76948.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/PngReadHelper.java:81
while (!finished) {
int length = source.readInt();
int code = source.readInt();
finished = reader.readChunk(source, code, length);
}
if (source.available() > 0) { // Should trailing data after IEND always be error or can configure as warning?
throw new PngException(PngConstants.ERROR_EOF_EXPECTED, String.format("Completed IEND but %d byte(s) remain", source.available()));
}
reader.finishedChunks(source);
return reader.getResult();
} catch (EOFException e) {
throw new PngException(PngConstants.ERROR_EOF, "Unexpected EOF", e);
} catch (IOException e) {
throw new PngException(PngConstants.ERROR_UNKNOWN_IO_FAILURE, e.getMessage(), e);
}
}
/**
* Number of bytes per row is key to processing scanlines.
* <p>
* TODO: should this by on the header?
*/
public static int calculateBytesPerRow(int pixelsPerRow, byte bitDepth, PngColourType colourType, byte interlaceMethod) {
if (interlaceMethod != 0) {
throw new IllegalStateException("Interlaced images not yet supported");
} else {
int numComponentsPerPixel = colourType.componentsPerPixel; // or "channels". e.g. "gray and alpha" means two components.
int bitsPerComponent = bitDepth; // e.g. "4" means 4 bits for gray, 4 bits for alpha
int bitsPerPixel = bitsPerComponent * numComponentsPerPixel; // e.g. total of 8 bits per pixel
int bitsPerRow = bitsPerPixel * pixelsPerRow;View on GitHub (pinned to 24702dc5a0)