{"record":{"id":"ed35ec8fc1c76948","repo":"HMCL-dev/HMCL","slug":"error-unknown-io-failure","errorCode":"ERROR_UNKNOWN_IO_FAILURE","errorMessage":"e.getMessage()","messagePattern":"e\\.getMessage\\(\\)","errorType":"error_code","errorClass":"PngException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/PngReadHelper.java","lineNumber":81,"sourceCode":"\n            while (!finished) {\n                int length = source.readInt();\n                int code = source.readInt();\n                finished = reader.readChunk(source, code, length);\n            }\n\n            if (source.available() > 0) { // Should trailing data after IEND always be error or can configure as warning?\n                throw new PngException(PngConstants.ERROR_EOF_EXPECTED, String.format(\"Completed IEND but %d byte(s) remain\", source.available()));\n            }\n\n            reader.finishedChunks(source);\n\n            return reader.getResult();\n\n        } catch (EOFException e) {\n            throw new PngException(PngConstants.ERROR_EOF, \"Unexpected EOF\", e);\n        } catch (IOException e) {\n            throw new PngException(PngConstants.ERROR_UNKNOWN_IO_FAILURE, e.getMessage(), e);\n        }\n\n    }\n\n    /**\n     * Number of bytes per row is key to processing scanlines.\n     * <p>\n     * TODO: should this by on the header?\n     */\n    public static int calculateBytesPerRow(int pixelsPerRow, byte bitDepth, PngColourType colourType, byte interlaceMethod) {\n        if (interlaceMethod != 0) {\n            throw new IllegalStateException(\"Interlaced images not yet supported\");\n\n        } else {\n            int numComponentsPerPixel = colourType.componentsPerPixel; // or \"channels\". e.g. \"gray and alpha\" means two components.\n            int bitsPerComponent = bitDepth; // e.g. \"4\" means 4 bits for gray, 4 bits for alpha\n            int bitsPerPixel = bitsPerComponent * numComponentsPerPixel;  // e.g. total of 8 bits per pixel\n            int bitsPerRow = bitsPerPixel * pixelsPerRow;","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/ui/image/apng/reader/PngReadHelper.java#L63-L99","documentation":"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).","triggerScenarios":"Underlying stream fails mid-read: closed socket, broken pipe on network streams, disk read error on FileInputStream, or a custom InputStream throwing from read().","commonSituations":"Reading a PNG over an unstable HTTP connection; file removed/locked while being read; wrapped streams (CipherInputStream, GZIPInputStream) failing with their own IOExceptions.","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."],"exampleFix":"// before: raw IOException propagates as opaque failure\nPngReadHelper.read(socket.getInputStream(), reader);\n// after: retry transient I/O failures\nfor (int i = 0; i < 3; i++) {\n  try { return PngReadHelper.read(openStream(), reader); }\n  catch (PngException e) { if (e.getCode() != PngConstants.ERROR_UNKNOWN_IO_FAILURE) throw e; sleepBackoff(i); }\n}\nthrow new IOException(\"download failed after retries\");","handlingStrategy":"try-catch","validationCode":"// pre-check readability before parsing\ntry (InputStream probe = Files.newInputStream(file)) {\n  if (probe.read() == -1) throw new IOException(\"file is empty/unreadable: \" + file);\n}","typeGuard":null,"tryCatchPattern":"try {\n  return PngReadHelper.read(is, reader);\n} catch (PngException e) {\n  if (e.getCode() == PngConstants.ERROR_UNKNOWN_IO_FAILURE) {\n    Throwable cause = e.getCause();\n    throw new IOException(\"I/O failure while reading PNG: \" + (cause == null ? e.getMessage() : cause.getMessage()), e);\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["png","io","network"],"backgroundTag":"network-request-failed","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}