feat: implement hybrid cache store with singleflight
This commit is contained in:
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
abstract class CacheStore {
|
||||
Future<T?> read<T>(String key);
|
||||
Future<void> write<T>(String key, T value);
|
||||
Future<void> remove(String key);
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import 'memory_cache_store.dart';
|
||||
import 'persistent_cache_store.dart';
|
||||
|
||||
class HybridCacheStore {
|
||||
final MemoryCacheStore memory;
|
||||
final PersistentCacheStore persistent;
|
||||
final Map<String, Future<dynamic>> _inflight = <String, Future<dynamic>>{};
|
||||
|
||||
HybridCacheStore({required this.memory, required this.persistent});
|
||||
|
||||
Future<T?> read<T>(String key) async {
|
||||
final memoryValue = await memory.read<T>(key);
|
||||
if (memoryValue != null) {
|
||||
return memoryValue;
|
||||
}
|
||||
final persistentValue = await persistent.read<T>(key);
|
||||
if (persistentValue != null) {
|
||||
await memory.write(key, persistentValue);
|
||||
}
|
||||
return persistentValue;
|
||||
}
|
||||
|
||||
Future<void> write<T>(String key, T value) async {
|
||||
await memory.write<T>(key, value);
|
||||
await persistent.write<T>(key, value);
|
||||
}
|
||||
|
||||
Future<void> remove(String key) async {
|
||||
await memory.remove(key);
|
||||
await persistent.remove(key);
|
||||
}
|
||||
|
||||
Future<T> getOrLoad<T>(String key, {required Future<T> Function() loader}) {
|
||||
final running = _inflight[key];
|
||||
if (running != null) {
|
||||
return running.then((value) => value as T);
|
||||
}
|
||||
|
||||
final future = () async {
|
||||
final cached = await read<T>(key);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
final loaded = await loader();
|
||||
await write<T>(key, loaded);
|
||||
return loaded;
|
||||
}();
|
||||
|
||||
_inflight[key] = future;
|
||||
return future.whenComplete(() {
|
||||
_inflight.remove(key);
|
||||
});
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import 'cache_store.dart';
|
||||
|
||||
class MemoryCacheStore implements CacheStore {
|
||||
final Map<String, Object?> _values = <String, Object?>{};
|
||||
|
||||
@override
|
||||
Future<T?> read<T>(String key) async {
|
||||
final value = _values[key];
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> write<T>(String key, T value) async {
|
||||
_values[key] = value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String key) async {
|
||||
_values.remove(key);
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import 'cache_store.dart';
|
||||
|
||||
class PersistentCacheStore implements CacheStore {
|
||||
final Map<String, Object?> _values = <String, Object?>{};
|
||||
|
||||
@override
|
||||
Future<T?> read<T>(String key) async {
|
||||
final value = _values[key];
|
||||
if (value is T) {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> write<T>(String key, T value) async {
|
||||
_values[key] = value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String key) async {
|
||||
_values.remove(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:social_app/core/cache/hybrid_cache_store.dart';
|
||||
import 'package:social_app/core/cache/memory_cache_store.dart';
|
||||
import 'package:social_app/core/cache/persistent_cache_store.dart';
|
||||
|
||||
void main() {
|
||||
test('same key concurrent load should execute loader once', () async {
|
||||
var calls = 0;
|
||||
final store = HybridCacheStore(
|
||||
memory: MemoryCacheStore(),
|
||||
persistent: PersistentCacheStore(),
|
||||
);
|
||||
|
||||
Future<String> loader() async {
|
||||
calls += 1;
|
||||
await Future<void>.delayed(const Duration(milliseconds: 20));
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
await Future.wait([
|
||||
store.getOrLoad<String>('k', loader: loader),
|
||||
store.getOrLoad<String>('k', loader: loader),
|
||||
]);
|
||||
|
||||
expect(calls, 1);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user