perf(cache): enforce cover byte budget during foreground use

This commit is contained in:
zarzet
2026-09-06 02:33:43 +07:00
parent dae509078e
commit 3368cf8c5d
5 changed files with 214 additions and 46 deletions
+41
View File
@@ -0,0 +1,41 @@
import 'dart:io';
/// Trims old cached payloads to a byte budget. Repository metadata, symlinks,
/// and recently written files (possibly active downloads) are left alone.
Future<int> trimCacheToByteBudget(
Directory directory, {
required int maxBytes,
required int targetBytes,
Duration minimumAge = const Duration(minutes: 1),
}) async {
assert(targetBytes >= 0 && targetBytes <= maxBytes);
if (!await directory.exists()) return 0;
final files = <(File, FileStat)>[];
var total = 0;
await for (final entry in directory.list(
recursive: true,
followLinks: false,
)) {
if (entry is! File || entry.path.endsWith('.json')) continue;
final stat = await entry.stat();
if (stat.type != FileSystemEntityType.file) continue;
files.add((entry, stat));
total += stat.size;
}
if (total <= maxBytes) return 0;
files.sort((a, b) => a.$2.modified.compareTo(b.$2.modified));
final cutoff = DateTime.now().subtract(minimumAge);
var deleted = 0;
for (final (file, stat) in files) {
if (total <= targetBytes) break;
if (stat.modified.isAfter(cutoff)) continue;
try {
await file.delete();
total -= stat.size;
deleted++;
} on FileSystemException {
// A cache download/eviction may have changed the directory since listing.
}
}
return deleted;
}
+48
View File
@@ -0,0 +1,48 @@
import 'dart:async';
/// Foreground maintenance that waits between completed runs and can be paused
/// safely even while a run is awaiting I/O.
class PeriodicAsyncTask {
PeriodicAsyncTask({required this.interval, required this.run, this.onError});
final Duration interval;
final Future<void> Function() run;
final void Function(Object, StackTrace)? onError;
Timer? _timer;
bool _enabled = false;
int _generation = 0;
Future<void> _pending = Future<void>.value();
void start({required Duration delay}) {
if (_enabled) return;
_enabled = true;
_schedule(delay, _generation);
}
void stop() {
_enabled = false;
_generation++;
_timer?.cancel();
_timer = null;
}
void _schedule(Duration delay, int generation) {
_timer = Timer(delay, () async {
final operation = _pending.then((_) async {
if (!_enabled || generation != _generation) return;
try {
await run();
} catch (error, stack) {
onError?.call(error, stack);
}
});
_pending = operation;
try {
await operation;
} finally {
if (_enabled && generation == _generation) {
_schedule(interval, generation);
}
}
});
}
}