blob: fba6fbfb8af1516f845e95420894a019237c5c38 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:path/path.dart';
/**
* The request that is sent from the main isolate to the clean-up isolate.
*/
class CacheCleanUpRequest {
final String cachePath;
final int maxSizeBytes;
final SendPort replyTo;
CacheCleanUpRequest(this.cachePath, this.maxSizeBytes, this.replyTo);
}
/**
* [ByteStore] that stores values as files and performs cache eviction.
*
* Only the process that manages the cache, e.g. Analysis Server, should use
* this class. Other processes, e.g. Analysis Server plugins, should use
* [FileByteStore] instead and let the main process to perform eviction.
*/
class EvictingFileByteStore implements ByteStore {
static bool _cleanUpSendPortShouldBePrepared = true;
static SendPort _cleanUpSendPort;
final String _cachePath;
final int _maxSizeBytes;
final FileByteStore _fileByteStore;
int _bytesWrittenSinceCleanup = 0;
bool _evictionIsolateIsRunning = false;
EvictingFileByteStore(this._cachePath, this._maxSizeBytes)
: _fileByteStore = new FileByteStore(_cachePath) {
_requestCacheCleanUp();
}
@override
List<int> get(String key) {
return _fileByteStore.get(key);
}
@override
void put(String key, List<int> bytes) {
_fileByteStore.put(key, bytes);
// Update the current size.
_bytesWrittenSinceCleanup += bytes.length;
if (_bytesWrittenSinceCleanup > _maxSizeBytes ~/ 8) {
_requestCacheCleanUp();
}
}
/**
* If the cache clean up process has not been requested yet, request it.
*/
Future<Null> _requestCacheCleanUp() async {
if (_cleanUpSendPortShouldBePrepared) {
_cleanUpSendPortShouldBePrepared = false;
ReceivePort response = new ReceivePort();
await Isolate.spawn(_cacheCleanUpFunction, response.sendPort);
_cleanUpSendPort = await response.first as SendPort;
} else {
while (_cleanUpSendPort == null) {
await new Future.delayed(new Duration(milliseconds: 100), () {});
}
}
if (!_evictionIsolateIsRunning) {
_evictionIsolateIsRunning = true;
try {
ReceivePort response = new ReceivePort();
_cleanUpSendPort.send(new CacheCleanUpRequest(
_cachePath, _maxSizeBytes, response.sendPort));
await response.first;
} finally {
_evictionIsolateIsRunning = false;
_bytesWrittenSinceCleanup = 0;
}
}
}
/**
* This function is started in a new isolate, receives cache folder clean up
* requests and evicts older files from the folder.
*/
static void _cacheCleanUpFunction(SendPort initialReplyTo) {
ReceivePort port = new ReceivePort();
initialReplyTo.send(port.sendPort);
port.listen((request) async {
if (request is CacheCleanUpRequest) {
await _cleanUpFolder(request.cachePath, request.maxSizeBytes);
// Let the client know that we're done.
request.replyTo.send(true);
}
});
}
static Future<Null> _cleanUpFolder(String cachePath, int maxSizeBytes) async {
// Prepare the list of files and their statistics.
List<File> files = <File>[];
Map<File, FileStat> fileStatMap = {};
int currentSizeBytes = 0;
List<FileSystemEntity> resources = new Directory(cachePath).listSync();
for (FileSystemEntity resource in resources) {
if (resource is File) {
try {
FileStat fileStat = await resource.stat();
files.add(resource);
fileStatMap[resource] = fileStat;
currentSizeBytes += fileStat.size;
} catch (_) {}
}
}
files.sort((a, b) {
return fileStatMap[a].accessed.millisecondsSinceEpoch -
fileStatMap[b].accessed.millisecondsSinceEpoch;
});
// Delete files until the current size is less than the max.
for (File file in files) {
if (currentSizeBytes < maxSizeBytes) {
break;
}
try {
await file.delete();
} catch (_) {}
currentSizeBytes -= fileStatMap[file].size;
}
}
}
/**
* [ByteStore] that stores values as files.
*/
class FileByteStore implements ByteStore {
final String _cachePath;
final String _tempName = 'temp_$pid';
FileByteStore(this._cachePath);
@override
List<int> get(String key) {
try {
return _getFileForKey(key).readAsBytesSync();
} catch (_) {
return null;
}
}
@override
void put(String key, List<int> bytes) {
try {
File tempFile = _getFileForKey(_tempName);
tempFile.writeAsBytesSync(bytes);
File file = _getFileForKey(key);
tempFile.renameSync(file.path);
} catch (_) {}
}
File _getFileForKey(String key) {
return new File(join(_cachePath, key));
}
}