-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #74 from Workiva/DOCPLAT-1525
DOCPLAT-1525 Create least recently used caching strategy
- Loading branch information
Showing
6 changed files
with
174 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import 'dart:async'; | ||
import 'dart:collection'; | ||
|
||
import 'package:w_common/cache.dart'; | ||
|
||
/// A [CachingStrategy] that will hold the last n most recently used [TValue]s. | ||
/// | ||
/// When n = 0 the strategy will remove a [TIdentifier] [TValue] pair immediately | ||
/// on release. | ||
class LeastRecentlyUsedStrategy<TIdentifier, TValue> | ||
extends CachingStrategy<TIdentifier, TValue> { | ||
/// [TIdentifier]s that have been released but not yet removed in order of most | ||
/// to least recently used. | ||
final Queue<TIdentifier> _removalQueue = new Queue<TIdentifier>(); | ||
|
||
/// The number of recently used [TIdentifier] [TValue] pairs to keep in the | ||
/// cache before removing the least recently used pair from the cache. | ||
final int _keep; | ||
|
||
LeastRecentlyUsedStrategy(this._keep) { | ||
if (_keep < 0) { | ||
throw new ArgumentError( | ||
'Cannot keep a negative number of most recently used items in the cache'); | ||
} | ||
} | ||
|
||
@override | ||
Future<Null> onDidRelease( | ||
TIdentifier id, TValue value, Future<Null> remove(TIdentifier id)) async { | ||
// If there are more than _keep items in the queue remove the least recently | ||
// used. | ||
while (_removalQueue.length > _keep) { | ||
await remove(_removalQueue.removeLast()); | ||
} | ||
} | ||
|
||
@override | ||
void onWillGet(TIdentifier id) { | ||
// A get has been called for id, removing it is now unnecessary. | ||
_removalQueue.remove(id); | ||
} | ||
|
||
@override | ||
void onWillRelease(TIdentifier id) { | ||
// id has been released, add it to the front of the removal queue. If | ||
// necessary, the least recently used items will be removed in onDidRemove | ||
// which the cache will call after any pending async value factory | ||
// associated with id completes. Items are added to the removal queue in | ||
// onWillRelease rather than onDidRelease to allow a get called before an | ||
// async value factory completes to cancel an unnecessary removal. | ||
if (!_removalQueue.contains(id)) { | ||
_removalQueue.addFirst(id); | ||
} | ||
} | ||
|
||
@override | ||
void onWillRemove(TIdentifier id) { | ||
// id will be removed, removing it again is unnecessary. | ||
_removalQueue.remove(id); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
test/unit/browser/cache/least_recently_used_strategy_test.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:test/test.dart'; | ||
import 'package:w_common/src/common/cache/cache.dart'; | ||
import 'package:w_common/src/common/cache/least_recently_used_strategy.dart'; | ||
|
||
void main() { | ||
group('MostRecentlyUsedStrategy', () { | ||
var expectedId = 'expectedId'; | ||
var expectedValue = 'expectedValue'; | ||
|
||
for (var i in new Iterable<int>.generate(3)) { | ||
Cache<String, Object> cache; | ||
|
||
setUp(() async { | ||
cache = new Cache<String, Object>(new LeastRecentlyUsedStrategy(i)); | ||
|
||
// install expected item | ||
await cache.get(expectedId, () => expectedValue); | ||
|
||
// install i items into cache | ||
for (var j in new Iterable<int>.generate(i)) { | ||
await cache.get('$j', () => j); | ||
} | ||
}); | ||
|
||
test( | ||
'release should remove released item after $i additional releases ' | ||
'when storing $i most recently used items', () async { | ||
cache.didRemove.listen(expectAsync1((context) { | ||
expect(context.id, expectedId); | ||
expect(context.value, expectedValue); | ||
})); | ||
|
||
// release expected item | ||
await cache.release(expectedId); | ||
|
||
// create i releases, after which expected item (and only expected | ||
// item) should be released | ||
for (var j in new Iterable<int>.generate(i)) { | ||
await cache.release('$j'); | ||
} | ||
}); | ||
|
||
test( | ||
'release after a synchronous getAsync remove getAsync call should ' | ||
'remove released item after $i additional releases when storing $i ' | ||
'most recently used items', () async { | ||
cache.didRemove.listen(expectAsync1((context) { | ||
expect(context.id, expectedId); | ||
expect(context.value, expectedValue); | ||
}, count: 2)); | ||
|
||
var firstGet = cache.getAsync(expectedId, () async => expectedValue); | ||
var remove = cache.remove(expectedId); | ||
var secondGet = cache.getAsync(expectedId, () async => expectedValue); | ||
|
||
// release expected item | ||
var release = cache.release(expectedId); | ||
|
||
await Future.wait([firstGet, remove, secondGet, release]); | ||
|
||
// create i releases, after which expected item (and only expected | ||
// item) should be released | ||
for (var j in new Iterable<int>.generate(i)) { | ||
await cache.release('$j'); | ||
} | ||
}); | ||
} | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters