|
| 1 | +import 'package:mutex/mutex.dart'; |
| 2 | + |
| 3 | +import 'json_cache.dart'; |
| 4 | + |
| 5 | +/// Thread-safe, in-memory [JsonCache] decorator. |
| 6 | +/// |
| 7 | +/// It is a kind of level 1 cache. |
| 8 | +/// |
| 9 | +/// TODO: limit the maximum number of cache entries via "size" parameter in |
| 10 | +/// constructors. |
| 11 | +/// |
| 12 | +/// It encapsulates a slower chache but keeps its own data in-memory. |
| 13 | +class JsonCacheMem implements JsonCache { |
| 14 | + /// Default ctor. [level2] the slower level 2 cache. |
| 15 | + JsonCacheMem(JsonCache level2) : this.main(level2, _shrMem, _shrMutex); |
| 16 | + |
| 17 | + /// Cache with custom memory. |
| 18 | + JsonCacheMem.mem(JsonCache level2, Map<String, Map<String, dynamic>?> mem) |
| 19 | + : this.main(level2, mem, ReadWriteMutex()); |
| 20 | + |
| 21 | + /// Main ctor. |
| 22 | + JsonCacheMem.main( |
| 23 | + JsonCache level2, |
| 24 | + Map<String, Map<String, dynamic>?> mem, |
| 25 | + ReadWriteMutex mutex, |
| 26 | + ) : _level2 = level2, |
| 27 | + _memory = mem, |
| 28 | + _mutex = mutex; |
| 29 | + |
| 30 | + /// Slower cache level. |
| 31 | + final JsonCache _level2; |
| 32 | + |
| 33 | + /// in-memory storage. |
| 34 | + final Map<String, Map<String, dynamic>?> _memory; |
| 35 | + |
| 36 | + /// Mutex lock-guard. |
| 37 | + final ReadWriteMutex _mutex; |
| 38 | + |
| 39 | + /// in-memory shared storage. |
| 40 | + static final Map<String, Map<String, dynamic>> _shrMem = {}; |
| 41 | + |
| 42 | + /// shared mutex. |
| 43 | + static final _shrMutex = ReadWriteMutex(); |
| 44 | + |
| 45 | + /// Frees up storage space in both the level2 cache and in-memory cache. |
| 46 | + @override |
| 47 | + Future<void> clear() async { |
| 48 | + await _mutex.protectWrite(() async { |
| 49 | + await _level2.clear(); |
| 50 | + _memory.clear(); |
| 51 | + }); |
| 52 | + } |
| 53 | + |
| 54 | + /// Updates data located at [key] in both the level2 cache and in-memory |
| 55 | + /// cache. |
| 56 | + @override |
| 57 | + Future<void> refresh(String key, Map<String, dynamic> data) async { |
| 58 | + /// ATTENTION: It is safer to copy the content of [data] before calling an |
| 59 | + /// asynchronous method that will copy it to avoid data races. For example, |
| 60 | + /// if the client code clears [data] right after passing it to this method, |
| 61 | + /// there's a high chance of having _level2 and this object with different |
| 62 | + /// contents. |
| 63 | + /// |
| 64 | + /// In Dart, synchronous code cannot be interrupted, so there is no need to |
| 65 | + /// protect it using mutual exclusion. |
| 66 | + final copy = Map<String, dynamic>.of(data); |
| 67 | + await _mutex.protectWrite(() async { |
| 68 | + await _level2.refresh(key, copy); |
| 69 | + _memory[key] = copy; |
| 70 | + }); |
| 71 | + } |
| 72 | + |
| 73 | + /// Removes data located at [key] from both the level2 cache and in-memory |
| 74 | + /// cache. |
| 75 | + @override |
| 76 | + Future<Map<String, dynamic>?> erase(String key) async { |
| 77 | + return _mutex.protectWrite(() async { |
| 78 | + await _level2.erase(key); |
| 79 | + return _memory.remove(key); |
| 80 | + }); |
| 81 | + } |
| 82 | + |
| 83 | + /// Retrieves the data at [key] or null if there is no data. |
| 84 | + @override |
| 85 | + Future<Map<String, dynamic>?> recover(String key) async { |
| 86 | + return _mutex.protectRead(() async { |
| 87 | + if (!_memory.containsKey(key)) { |
| 88 | + _memory[key] = await _level2.recover(key); |
| 89 | + } |
| 90 | + final cached = _memory[key]; |
| 91 | + return cached == null ? cached : Map<String, dynamic>.of(cached); |
| 92 | + }); |
| 93 | + } |
| 94 | +} |
0 commit comments