{"record":{"id":"ca96c95f73ce52a4","repo":"CarGuo/GSYVideoPlayer","slug":"exo-cache-folder-is-locked-cachepath","errorCode":null,"errorMessage":"Exo cache folder is locked: ${cachePath}","messagePattern":"Exo cache folder is locked: (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"gsyVideoPlayer-exo_player2/src/main/java/tv/danmaku/ijk/media/exo2/ExoSourceManager.java","lineNumber":467,"sourceCode":"        CacheHolder cacheHolder = getOrCreateCacheHolder(context, cacheDir, true, true);\n        if (cacheHolder == null) {\n            return null;\n        }\n        mCurrentCachePath = cacheHolder.cachePath;\n        return cacheHolder;\n    }\n\n    private static synchronized CacheHolder getOrCreateCacheHolder(Context context, File cacheDir, boolean incrementRef, boolean fallbackWhenLocked) {\n        String cachePath = buildCachePath(context, cacheDir);\n        CacheHolder cacheHolder = sCacheHolderMap.get(cachePath);\n        if (cacheHolder == null) {\n            File cacheFolder = new File(cachePath);\n            if (SimpleCache.isCacheFolderLocked(cacheFolder)) {\n                if (fallbackWhenLocked) {\n                    Log.w(TAG, \"Exo cache folder is locked, fallback without cache: \" + cachePath);\n                    return null;\n                }\n                throw new IllegalStateException(\"Exo cache folder is locked: \" + cachePath);\n            }\n            Cache cache = new SimpleCache(cacheFolder, new LeastRecentlyUsedCacheEvictor(sCacheMaxSize),\n                sDatabaseProvider != null ? sDatabaseProvider : new StandaloneDatabaseProvider(context));\n            cacheHolder = new CacheHolder(cachePath, cache);\n            sCacheHolderMap.put(cachePath, cacheHolder);\n        }\n        if (incrementRef) {\n            cacheHolder.refCount++;\n        }\n        return cacheHolder;\n    }\n\n    private static synchronized void releaseCacheHolder(String cachePath) {\n        CacheHolder cacheHolder = sCacheHolderMap.get(cachePath);\n        if (cacheHolder == null) {\n            return;\n        }\n        if (cacheHolder.refCount > 0) {","sourceCodeStart":449,"sourceCodeEnd":485,"githubUrl":"https://github.com/CarGuo/GSYVideoPlayer/blob/e5d74d3aa9d7fb1393a879e33ee380f8f41354f1/gsyVideoPlayer-exo_player2/src/main/java/tv/danmaku/ijk/media/exo2/ExoSourceManager.java#L449-L485","documentation":"ExoPlayer's SimpleCache locks its cache folder with a lock file so only one instance can own it at a time. ExoSourceManager.getOrCreateCacheHolder checks SimpleCache.isCacheFolderLocked before creating a new cache; when the folder is already owned by another SimpleCache instance (typically in another process or a not-yet-released instance) and fallbackWhenLocked is false, it throws IllegalStateException. This protects against the classic 'Another SimpleCache instance uses the folder' crash.","triggerScenarios":"Calling ExoSourceManager.acquireCacheSingleInstance(context, cacheDir, false) (or any path with fallbackWhenLocked=false) while the cache folder is locked: e.g. the app process died without releasing the cache, a :remote or multi-process setup has another SimpleCache on the same folder, or the previous player instance's cache was never released via ExoSourceManager.releaseAllCacheHolder.","commonSituations":"Multi-process apps (webview/player in separate process) sharing one cache dir; app crash leaving a stale lock file; calling Media3CacheExportUtils or CacheHelper before the previous cache holder was released; enabling ExoPlayer caching after the process was killed mid-playback.","solutions":["Use the fallback overload acquireCacheSingleInstance(context, cacheDir, true) so playback continues without cache when the folder is locked","Ensure ExoSourceManager.releaseAllCacheHolder(context) is called in the player release path so the lock is freed before a new instance is created","Give each process its own cache directory (append process name to the cache dir) so two SimpleCache instances never share a folder","If the lock is stale after a crash, delete the lock file (exoplayer_cache_lock / .lock inside the cache dir) on app start before creating the cache"],"exampleFix":"// before\nCache cache = ExoSourceManager.acquireCacheSingleInstance(context, cacheDir, false);\n\n// after - degrade to cache-less playback instead of crashing\nCache cache = ExoSourceManager.acquireCacheSingleInstance(context, cacheDir, true);\nif (cache == null) {\n    // proceed without cache (direct HttpDataSource)\n}","handlingStrategy":"fallback","validationCode":"File cacheDir = new File(context.getCacheDir(), \"exo\");\nif (androidx.media3.database.SimpleCache.isCacheFolderLocked(cacheDir)) {\n    // either release previous holders or pass fallbackWhenLocked=true\n}","typeGuard":null,"tryCatchPattern":"try {\n    cache = ExoSourceManager.acquireCacheSingleInstance(ctx, cacheDir, false);\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"locked\")) {\n        cache = ExoSourceManager.acquireCacheSingleInstance(ctx, cacheDir, true); // null = play without cache\n    } else throw e;\n}","preventionTips":["Always pair cache creation with ExoSourceManager.releaseAllCacheHolder in the player release path","Give each Android process its own ExoPlayer cache directory","Delete stale lock files at app start if the previous session crashed"],"tags":["exoplayer","cache","concurrency","multi-process","android"],"backgroundTag":null,"analyzedSha":"e5d74d3aa9d7fb1393a879e33ee380f8f41354f1","analyzedAt":"2026-08-14T11:56:11.997Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}