Skip to content

Commit 0bcbec5

Browse files
authored
fix: keep cache info of removed files when another file cannot be deleted (#541)
* fix: wait for cached files to be removed before deleting their cache info The cleanup started removing files without waiting and then deleted the cache info right away, so the cache info could be gone while the files were still on disk. If the app was killed in between, those files were left with nothing referencing them. The cleanup now waits for the file removals first. The cache info of a file that could not be removed is kept, so the next cleanup retries it. * fix: keep cache info only of files that could not be removed Move the partial-failure handling into _removeCachedFile so cleanup, emptyCache and removeCachedFile all delete the cache info of removed files and keep only the failed one. A failing deleteAll is logged instead of replacing the file removal error. --------- Co-authored-by: Jonathan Unsworth <17317065+junsworth@users.noreply.github.com>
1 parent 071f1f3 commit 0bcbec5

4 files changed

Lines changed: 312 additions & 20 deletions

File tree

‎flutter_cache_manager/CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## [3.4.7] - 2026-10-28
2+
3+
* Keeps cache cleanup going when a cached file cannot be deleted: the cache info of the other removed files is still deleted, and only the failed file's cache info is kept so the next cleanup retries it.
4+
15
## [3.4.6] - 2026-10-27
26

37
* Catches unhandled asynchronous errors when updating cache-hit metadata and during scheduled cleanup in `CacheStore` ([#535](https://github.com/Baseflow/flutter_cache_manager/issues/535)).

‎flutter_cache_manager/lib/src/cache_store.dart‎

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -198,34 +198,48 @@ class CacheStore {
198198
}
199199

200200
Future<void> _cleanupCache() async {
201-
final toRemove = <int>[];
201+
final started = <int>{};
202+
final removed = <int>[];
202203
final provider = await _cacheInfoRepository;
204+
final removals = <Future<void>>[];
203205

204206
final overCapacity = await provider.getObjectsOverCapacity(_capacity);
205-
final futures = <Future>[];
206207
for (final cacheObject in overCapacity) {
207-
futures.add(_removeCachedFile(cacheObject, toRemove));
208+
removals.add(
209+
_removeCachedFile(cacheObject, started: started, removed: removed),
210+
);
208211
}
209212

210213
final oldObjects = await provider.getOldObjects(_maxAge);
211214
for (final cacheObject in oldObjects) {
212-
futures.add(_removeCachedFile(cacheObject, toRemove));
215+
removals.add(
216+
_removeCachedFile(cacheObject, started: started, removed: removed),
217+
);
213218
}
214219

215-
await Future.wait(futures);
216-
await provider.deleteAll(toRemove);
220+
await _deleteCacheInfoOfRemoved(
221+
provider: provider,
222+
removals: removals,
223+
removed: removed,
224+
);
217225
}
218226

219227
Future<void> emptyCache() async {
220228
final provider = await _cacheInfoRepository;
221-
final toRemove = <int>[];
229+
final started = <int>{};
230+
final removed = <int>[];
222231
final allObjects = await provider.getAllObjects();
223-
var futures = <Future>[];
232+
final removals = <Future<void>>[];
224233
for (final cacheObject in allObjects) {
225-
futures.add(_removeCachedFile(cacheObject, toRemove));
234+
removals.add(
235+
_removeCachedFile(cacheObject, started: started, removed: removed),
236+
);
226237
}
227-
await Future.wait(futures);
228-
await provider.deleteAll(toRemove);
238+
await _deleteCacheInfoOfRemoved(
239+
provider: provider,
240+
removals: removals,
241+
removed: removed,
242+
);
229243
}
230244

231245
void emptyMemoryCache() {
@@ -234,18 +248,61 @@ class CacheStore {
234248

235249
Future<void> removeCachedFile(CacheObject cacheObject) async {
236250
final provider = await _cacheInfoRepository;
237-
final toRemove = <int>[];
238-
await _removeCachedFile(cacheObject, toRemove);
239-
await provider.deleteAll(toRemove);
251+
final removed = <int>[];
252+
final removal = _removeCachedFile(
253+
cacheObject,
254+
started: <int>{},
255+
removed: removed,
256+
);
257+
await _deleteCacheInfoOfRemoved(
258+
provider: provider,
259+
removals: [removal],
260+
removed: removed,
261+
);
262+
}
263+
264+
/// Waits for [removals] and then deletes the cache info of the files in
265+
/// [removed], even when some removals failed.
266+
Future<void> _deleteCacheInfoOfRemoved({
267+
required CacheInfoRepository provider,
268+
required List<Future<void>> removals,
269+
required List<int> removed,
270+
}) async {
271+
Object? removalError;
272+
StackTrace? removalStack;
273+
try {
274+
await Future.wait(removals);
275+
} catch (e, s) {
276+
removalError = e;
277+
removalStack = s;
278+
}
279+
280+
try {
281+
await provider.deleteAll(removed);
282+
} catch (e, s) {
283+
if (removalError == null) rethrow;
284+
cacheLogger.log(
285+
'CacheStore: Failed to delete cache info with error:\n$e\n$s',
286+
CacheManagerLogLevel.warning,
287+
);
288+
}
289+
290+
if (removalError != null) {
291+
Error.throwWithStackTrace(removalError, removalStack!);
292+
}
240293
}
241294

295+
/// Adds the id to [removed] only once the file is gone, so the cache info of
296+
/// a file that could not be deleted is kept and retried. [started] stops the
297+
/// same object from being removed twice.
242298
Future<void> _removeCachedFile(
243-
CacheObject cacheObject,
244-
List<int> toRemove,
245-
) async {
246-
if (toRemove.contains(cacheObject.id)) return;
299+
CacheObject cacheObject, {
300+
required Set<int> started,
301+
required List<int> removed,
302+
}) async {
303+
final id = cacheObject.id!;
304+
if (!started.add(id)) return;
247305

248-
toRemove.add(cacheObject.id!);
249306
if (_memCache.containsKey(cacheObject.key)) {
250307
_memCache.remove(cacheObject.key);
251308
}
@@ -260,6 +317,7 @@ class CacheStore {
260317
// File has already been deleted. Do nothing #184
261318
}
262319
}
320+
removed.add(id);
263321
}
264322

265323
bool memoryCacheContainsKey(String key) {

‎flutter_cache_manager/pubspec.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: flutter_cache_manager
22
description: Generic cache manager for flutter. Saves web files on the storages of the device and saves the cache info using sqflite.
3-
version: 3.4.6
3+
version: 3.4.7
44
homepage: https://github.com/Baseflow/flutter_cache_manager
55
topics:
66
- cache

0 commit comments

Comments
 (0)