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

  1. Inspect the cause (PngException.getCause()) to find the real I/O problem.
  2. For network streams, retry the download with backoff before decoding.
  3. For files, verify permissions and that the file is not being modified concurrently.
  4. 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

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


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)