{"record":{"id":"1f89050df5516f4a","repo":"CarGuo/GSYVideoPlayer","slug":"error-recreate-zero-size-file-s","errorCode":null,"errorMessage":"Error recreate zero-size file %s","messagePattern":"Error recreate zero-size file (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/Files.java","lineNumber":78,"sourceCode":"\n    static void modify(File file) throws IOException {\n        long size = file.length();\n        if (size == 0) {\n            recreateZeroSizeFile(file);\n            return;\n        }\n\n        RandomAccessFile accessFile = new RandomAccessFile(file, \"rwd\");\n        accessFile.seek(size - 1);\n        byte lastByte = accessFile.readByte();\n        accessFile.seek(size - 1);\n        accessFile.write(lastByte);\n        accessFile.close();\n    }\n\n    private static void recreateZeroSizeFile(File file) throws IOException {\n        if (!file.delete() || !file.createNewFile()) {\n            throw new IOException(\"Error recreate zero-size file \" + file);\n        }\n    }\n\n    private static final class LastModifiedComparator implements Comparator<File> {\n\n        @Override\n        public int compare(File lhs, File rhs) {\n            return compareLong(lhs.lastModified(), rhs.lastModified());\n        }\n\n        private int compareLong(long first, long second) {\n            return Long.compare(first, second);\n        }\n    }\n\n}\n","sourceCodeStart":60,"sourceCodeEnd":95,"githubUrl":"https://github.com/CarGuo/GSYVideoPlayer/blob/e5d74d3aa9d7fb1393a879e33ee380f8f41354f1/gsyVideoPlayer-proxy_cache/src/main/java/com/danikula/videocache/file/Files.java#L60-L95","documentation":"This IOException is thrown by Files.recreateZeroSizeFile (Files.java:76-80) when the cache file being touched has length 0 and either File.delete() or File.createNewFile() returns false. It is reached from Files.modify() (Files.java:61-66), which the LRU disk-usage logic invokes via Files.setLastModifiedNow() when File.setLastModified() fails on the device (a known Android quirk the code comments on). The library tries to refresh a zero-length cache file's timestamp by deleting and recreating it; if the filesystem refuses either step it throws rather than silently leaving stale LRU ordering.","triggerScenarios":"Proxy cache disk trimming runs (HttpProxyCacheServer with an LruDiskUsage), File.setLastModified(now) returns false on the device, the touched cache file's length() == 0, and then file.delete() or file.createNewFile() fails. Typical concrete causes: the file is held open by another thread/process (e.g. a concurrent download writing it), the cache directory was deleted or unmounted between the listFiles() scan and the touch (external storage ejected), or the app lost write access to getExternalCacheDir()/custom cache dir (permissions revoked, disk full).","commonSituations":"Android devices where setLastModified is broken (comment in Files.java:49 explicitly mentions this, e.g. Nexus 5-class devices); cache directory on external/removable storage that is unmounted while playback runs; a zero-byte .downloaded cache file left over from a previously interrupted/crashed proxy download; two proxy cache servers or a media player plus the trimmer touching the same zero-size file concurrently; runtime storage permission missing after targeting API 30+ scoped storage.","solutions":["Ensure only one HttpProxyCacheServer instance exists (singleton) and that all players share it, so nothing else holds the cache file open while trimming runs.","Verify the cache directory passed to HttpProxyCacheServer.newCacheRootFactory / cacheRootFactory() is on internal storage (context.getCacheDir()) or is one you provably can write to, and that WRITE_EXTERNAL_STORAGE / scoped-storage rules are satisfied.","Delete stale zero-byte files in the cache directory at app start before building the HttpProxyCacheServer, so trim never has to 'recreate' them.","If it persists on specific devices, catch the IOException at the ping/trim call site (e.g. in a custom DiskUsage wrapper or around server building) and log-and-continue: the failure only affects LRU timestamp freshness, not data integrity.","Check disk space and, if the cache dir lives on removable storage, guard playback start with a mounted-state check (Environment.getExternalStorageState())."],"exampleFix":"// before: cache on external storage with no mount/permission guard\nHttpProxyCacheServer proxy = new HttpProxyCacheServer.Builder(context)\n        .cacheRootFactory(new File(Environment.getExternalStorageDirectory(), \"video-cache\"))\n        .build();\n\n// after: cache on internal cache dir (always writable) + prune zero-byte leftovers\nFile cacheDir = new File(context.getCacheDir(), \"video-cache\");\nif (cacheDir.isDirectory()) {\n    File[] stale = cacheDir.listFiles();\n    if (stale != null) {\n        for (File f : stale) {\n            if (f.isFile() && f.length() == 0) {\n                //noinspection ResultOfMethodCallIgnored\n                f.delete();\n            }\n        }\n    }\n}\nHttpProxyCacheServer proxy = new HttpProxyCacheServer.Builder(context)\n        .cacheRootFactory(cacheDir)\n        .build();","handlingStrategy":"try-catch","validationCode":"// Before building the server, ensure the cache dir is writable and has no zero-byte stragglers\nFile cacheDir = new File(context.getCacheDir(), \"video-cache\");\nboolean usable = cacheDir.isDirectory() || cacheDir.mkdirs();\nif (usable) {\n    File[] files = cacheDir.listFiles();\n    if (files != null) {\n        for (File f : files) {\n            if (f.isFile() && f.length() == 0 && !f.delete()) {\n                usable = false; // delete failed -> filesystem problem, surface it now\n                break;\n            }\n        }\n    }\n}\nif (!usable) throw new IllegalStateException(\"Video cache dir not writable: \" + cacheDir);","typeGuard":null,"tryCatchPattern":"// Wrap proxy server construction/usage where the IOException can bubble up (it originates\n// inside disk-usage trimming and is fatal to that request, not to the app).\ntry {\n    HttpProxyCacheServer server = new HttpProxyCacheServer.Builder(context)\n            .cacheRootFactory(cacheDir)\n            .build();\n} catch (IOException e) {\n    if (String.valueOf(e.getMessage()).contains(\"Error recreate zero-size file\")) {\n        // LRU timestamp refresh failed: clear the cache dir and retry once\n        //noinspection ResultOfMethodCallIgnored\n        cacheDir.delete();\n        //noinspection ResultOfMethodCallIgnored\n        cacheDir.mkdirs();\n        server = new HttpProxyCacheServer.Builder(context).cacheRootFactory(cacheDir).build();\n    } else {\n        throw e;\n    }\n}","preventionTips":["Use one shared singleton HttpProxyCacheServer so no second process holds cache files open during LRU trimming.","Prefer context.getCacheDir() (internal storage) for cacheRootFactory; it is always writable and never unmounted.","Prune zero-length files from the cache directory on app start before creating the proxy server.","Do not manually delete or lock files inside the proxy's cache directory from other code paths.","If caching to external storage, check Environment.getExternalStorageState() before starting playback."],"tags":["android","file-system","storage","cache","io","videocache"],"backgroundTag":null,"analyzedSha":"e5d74d3aa9d7fb1393a879e33ee380f8f41354f1","analyzedAt":"2026-08-14T11:56:11.997Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}